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

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 { createRoot } from "react-dom/client";
import { App } from "./app";
import { Providers } from "./providers";
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 { VStack, Heading } from "@chakra-ui/react";
import { Link } from "react-router-dom";
export const HomeView = () => {
return (
<VStack>
<Heading>personal nostr client</Heading>
<>
<span>
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
@@ -13,13 +11,12 @@ export const HomeView = () => {
consistent look and feel, not just in our design specs, but in
production.
</span>
<Link to="/settings">Settings</Link>
<Link to="/user/266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5">
self
</Link>
<Link to="/user/6b0d4c8d9dc59e110d380b0429a02891f1341a0fa2ba1b1cf83a3db4d47e3964">
gigi
</Link>
</VStack>
</>
);
};

View File

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

View File

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

View File

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