UI improvements

This commit is contained in:
hzrd149
2023-02-07 17:04:18 -06:00
parent 3a54499e3a
commit d7832cb893
15 changed files with 301 additions and 50 deletions

View File

@@ -1,23 +1,55 @@
import React from "react"; import React from "react";
import { ChakraProvider } from "@chakra-ui/react"; import { Spinner } from "@chakra-ui/react";
import { HashRouter, Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { HomeView } from "./views/home"; import { HomeView } from "./views/home";
import { UserView } from "./views/user"; import { UserView } from "./views/user";
import { ErrorBoundary } from "./components/error-boundary"; import { ErrorBoundary } from "./components/error-boundary";
import { SettingsView } from "./views/settings"; import { SettingsView } from "./views/settings";
import { GlobalView } from "./views/global";
import { useRelays } from "./providers/relay-provider";
import { Page } from "./components/page";
export const App = () => { export const App = () => {
const { loading } = useRelays();
if (loading) return <Spinner size="xl" />;
return ( return (
<ChakraProvider>
<ErrorBoundary> <ErrorBoundary>
<HashRouter>
<Routes> <Routes>
<Route path="/user/:pubkey" element={<UserView />} /> <Route
<Route path="/settings" element={<SettingsView />} /> path="/user/:pubkey"
<Route path="/" element={<HomeView />} /> element={
<Page>
<UserView />
</Page>
}
/>
<Route
path="/settings"
element={
<Page>
<SettingsView />
</Page>
}
/>
<Route
path="/global"
element={
<Page>
<GlobalView />
</Page>
}
/>
<Route
path="/"
element={
<Page>
<HomeView />
</Page>
}
/>
</Routes> </Routes>
</HashRouter>
</ErrorBoundary> </ErrorBoundary>
</ChakraProvider>
); );
}; };

23
src/components/page.jsx Normal file
View File

@@ -0,0 +1,23 @@
import React from "react";
import { Box, Button, Container, HStack, VStack } from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { ErrorBoundary } from "./error-boundary";
export const Page = ({ children }) => {
const navigate = useNavigate();
return (
<Container size="lg" padding={4}>
<HStack alignItems="flex-start" spacing={4} overflow="hidden">
<VStack style={{ width: "20rem" }} alignItems="stretch" flexShrink={0}>
<Button onClick={() => navigate("/")}>Home</Button>
<Button onClick={() => navigate("/global")}>Global</Button>
<Button onClick={() => navigate("/settings")}>Settings</Button>
</VStack>
<Box flexGrow={1} overflow="auto">
<ErrorBoundary>{children}</ErrorBoundary>
</Box>
</HStack>
</Container>
);
};

View File

@@ -0,0 +1,30 @@
import React from "react";
import {
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from "@chakra-ui/react";
import ReactMarkdown from "react-markdown";
export const PostModal = ({ event, onClose, isOpen }) => (
<Modal isOpen={isOpen} onClose={onClose} size="6xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>{event.pubkey}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<ReactMarkdown>{event.content}</ReactMarkdown>
</ModalBody>
<ModalFooter>
<Button colorScheme="blue" onClick={onClose}>
Close
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);

42
src/components/post.jsx Normal file
View File

@@ -0,0 +1,42 @@
import React from "react";
import {
Box,
Button,
Card,
CardBody,
Heading,
useDisclosure,
VStack,
} from "@chakra-ui/react";
import ReactMarkdown from "react-markdown";
import { Link } from "react-router-dom";
import { PostModal } from "./post-modal";
export const Post = ({ event }) => {
const { isOpen, onClose, onOpen } = useDisclosure();
const isLong = event.content.length > 800;
return (
<Card>
<CardBody>
<VStack alignItems="flex-start" justifyContent="stretch">
<Heading size="sm">
<Link to={`/user/${event.pubkey}`}>{event.pubkey}</Link>
</Heading>
<Box maxHeight="10rem" overflow="hidden" width="100%">
<ReactMarkdown>{event.content}</ReactMarkdown>
</Box>
{isLong && (
<>
<Button size="sm" variant="link" onClick={onOpen}>
Read More
</Button>
<PostModal event={event} isOpen={isOpen} onClose={onClose} />
</>
)}
</VStack>
</CardBody>
</Card>
);
};

View File

@@ -1,6 +1,11 @@
import React from "react"; import React from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { App } from "./app"; import { App } from "./app";
import { Providers } from "./providers";
const root = createRoot(document.getElementById("root")); const root = createRoot(document.getElementById("root"));
root.render(<App />); root.render(
<Providers>
<App />
</Providers>
);

13
src/providers/index.jsx Normal file
View File

@@ -0,0 +1,13 @@
import React from "react";
import { ChakraProvider } from "@chakra-ui/react";
import { RelaysProvider } from "./relay-provider";
import { HashRouter } from "react-router-dom";
import theme from "../theme";
export const Providers = ({ children }) => (
<ChakraProvider theme={theme}>
<HashRouter>
<RelaysProvider>{children}</RelaysProvider>
</HashRouter>
</ChakraProvider>
);

View File

@@ -0,0 +1,52 @@
import React, {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import settingsService from "../services/settings";
const relaysContext = createContext({
relays: [],
loading: true,
overwriteRelays: () => {},
});
export function useRelays() {
return useContext(relaysContext);
}
export const RelaysProvider = ({ children }) => {
const [relays, setRelays] = useState([]);
const [loading, setLoading] = useState(true);
const update = useCallback(async () => {
setRelays(await settingsService.getRelays());
setLoading(false);
}, [setRelays]);
const overwriteRelays = useCallback(
async (urls) => {
if (urls) await settingsService.setRelays(urls);
await update();
},
[update]
);
useEffect(() => {
update();
}, [update]);
return (
<relaysContext.Provider
value={{
relays,
loading,
overwriteRelays,
}}
>
{children}
</relaysContext.Provider>
);
};

0
src/providers/styles.jsx Normal file
View File

17
src/theme/container.js Normal file
View File

@@ -0,0 +1,17 @@
import { defineStyle, defineStyleConfig } from "@chakra-ui/react";
// define custom sizes
const sizes = {
sm: defineStyle({
maxW: "10rem",
}),
md: defineStyle({
maxW: "50rem",
}),
lg: defineStyle({
maxW: "100rem",
}),
};
// export the component theme
export const containerTheme = defineStyleConfig({ sizes });

10
src/theme/index.js Normal file
View File

@@ -0,0 +1,10 @@
import { extendTheme } from "@chakra-ui/react";
import { containerTheme } from "./container";
const theme = extendTheme({
components: {
Container: containerTheme,
},
});
export default theme;

View File

@@ -0,0 +1,39 @@
import React, { useState } from "react";
import { SkeletonText } from "@chakra-ui/react";
import { useSubscription } from "../../helpers/use-subscription";
import { useSignal } from "../../hooks/use-signal";
import { useRelays } from "../../providers/relay-provider";
import { Post } from "../../components/post";
export const GlobalView = () => {
const { relays } = useRelays();
const [events, setEvents] = useState({});
const sub = useSubscription(relays, { kinds: [1], limit: 10 }, []);
useSignal(
sub?.onEvent,
(event) => {
if (event.kind === 1) {
setEvents((dir) => ({ [event.id]: event, ...dir }));
}
},
[setEvents]
);
const timeline = Object.values(events).sort(
(a, b) => a.created_at - b.created_at
);
if (timeline.length === 0) {
return <SkeletonText />;
}
return (
<>
{timeline.map((event) => (
<Post key={event.id} event={event} />
))}
</>
);
};

View File

@@ -1,11 +1,9 @@
import React from "react"; import React from "react";
import { VStack, Heading } from "@chakra-ui/react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
export const HomeView = () => { export const HomeView = () => {
return ( return (
<VStack> <>
<Heading>personal nostr client</Heading>
<span> <span>
There are many benefits to a joint design and development system. Not There are many benefits to a joint design and development system. Not
only does it bring benefits to the design team, but it also brings only does it bring benefits to the design team, but it also brings
@@ -13,13 +11,12 @@ export const HomeView = () => {
consistent look and feel, not just in our design specs, but in consistent look and feel, not just in our design specs, but in
production. production.
</span> </span>
<Link to="/settings">Settings</Link>
<Link to="/user/266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5"> <Link to="/user/266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5">
self self
</Link> </Link>
<Link to="/user/6b0d4c8d9dc59e110d380b0429a02891f1341a0fa2ba1b1cf83a3db4d47e3964"> <Link to="/user/6b0d4c8d9dc59e110d380b0429a02891f1341a0fa2ba1b1cf83a3db4d47e3964">
gigi gigi
</Link> </Link>
</VStack> </>
); );
}; };

View File

@@ -6,30 +6,28 @@ import {
Stack, Stack,
Textarea, Textarea,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import * as settings from "../services/settings"; import { useRelays } from "../providers/relay-provider";
export const SettingsView = () => { export const SettingsView = () => {
const [relayUrls, setRelayUrls] = useState(""); const { relays, overwriteRelays } = useRelays();
const [relayUrls, setRelayUrls] = useState(relays.join("\n"));
const handleSubmit = (event) => { const handleSubmit = async (event) => {
event.preventDefault(); event.preventDefault();
const relays = relayUrls const newRelays = relayUrls
.split("\n") .split("\n")
.filter(Boolean) .filter(Boolean)
.map((url) => url.trim()); .map((url) => url.trim());
if (relays.length > 0) {
settings.setRelays(relays); if (newRelays.length > 0) {
await overwriteRelays(newRelays);
} }
}; };
const resetForm = async () => {
const urls = await settings.getRelays();
setRelayUrls(urls.join("\n"));
};
useEffect(() => { const resetForm = async () => {
resetForm(); setRelayUrls(relays.join("\n"));
}, []); };
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
@@ -41,7 +39,7 @@ export const SettingsView = () => {
required required
size="md" size="md"
rows={10} rows={10}
resize="horizontal" resize="vertical"
/> />
<FormHelperText>One relay per line</FormHelperText> <FormHelperText>One relay per line</FormHelperText>
</FormControl> </FormControl>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React from "react";
import { import {
Heading, Heading,
Tab, Tab,
@@ -8,7 +8,6 @@ import {
Tabs, Tabs,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { ErrorBoundary } from "../../components/error-boundary";
import { UserPostsTab } from "./posts"; import { UserPostsTab } from "./posts";
export const UserView = () => { export const UserView = () => {
@@ -16,15 +15,15 @@ export const UserView = () => {
if (!params.pubkey) { if (!params.pubkey) {
// TODO: better 404 // TODO: better 404
return "no pubkey"; throw new Error("No pubkey");
} }
return ( return (
<ErrorBoundary> <>
<Heading>user {params.pubkey}</Heading> <Heading>{params.pubkey}</Heading>
<Tabs> <Tabs>
<TabList> <TabList>
<Tab>Notes</Tab> <Tab>Posts</Tab>
<Tab>Other</Tab> <Tab>Other</Tab>
<Tab>Relays</Tab> <Tab>Relays</Tab>
</TabList> </TabList>
@@ -41,6 +40,6 @@ export const UserView = () => {
</TabPanel> </TabPanel>
</TabPanels> </TabPanels>
</Tabs> </Tabs>
</ErrorBoundary> </>
); );
}; };

View File

@@ -1,9 +1,9 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Card, CardBody, SkeletonText } from "@chakra-ui/react"; import { SkeletonText } from "@chakra-ui/react";
import ReactMarkdown from "react-markdown";
import settingsService from "../../services/settings"; import settingsService from "../../services/settings";
import { useSignal } from "../../hooks/use-signal"; import { useSignal } from "../../hooks/use-signal";
import { useSubscription } from "../../helpers/use-subscription"; import { useSubscription } from "../../helpers/use-subscription";
import { Post } from "../../components/post";
const relayUrls = await settingsService.getRelays(); const relayUrls = await settingsService.getRelays();
@@ -30,11 +30,5 @@ export const UserPostsTab = ({ pubkey }) => {
return <SkeletonText />; return <SkeletonText />;
} }
return timeline.map((event) => ( return timeline.map((event) => <Post key={event.id} event={event} />);
<Card key={event.id}>
<CardBody>
<ReactMarkdown>{event.content}</ReactMarkdown>
</CardBody>
</Card>
));
}; };