mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-25 11:13:30 +02:00
UI improvements
This commit is contained in:
58
src/app.jsx
58
src/app.jsx
@@ -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
23
src/components/page.jsx
Normal 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>
|
||||
);
|
||||
};
|
30
src/components/post-modal.jsx
Normal file
30
src/components/post-modal.jsx
Normal 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
42
src/components/post.jsx
Normal 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>
|
||||
);
|
||||
};
|
@@ -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
13
src/providers/index.jsx
Normal 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>
|
||||
);
|
52
src/providers/relay-provider.jsx
Normal file
52
src/providers/relay-provider.jsx
Normal 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
0
src/providers/styles.jsx
Normal file
17
src/theme/container.js
Normal file
17
src/theme/container.js
Normal 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
10
src/theme/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { extendTheme } from "@chakra-ui/react";
|
||||
import { containerTheme } from "./container";
|
||||
|
||||
const theme = extendTheme({
|
||||
components: {
|
||||
Container: containerTheme,
|
||||
},
|
||||
});
|
||||
|
||||
export default theme;
|
39
src/views/global/index.jsx
Normal file
39
src/views/global/index.jsx
Normal 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} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -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} />);
|
||||
};
|
||||
|
Reference in New Issue
Block a user