mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 21:31:43 +01:00
update router
This commit is contained in:
parent
07f95cead0
commit
25c3c8ab5b
127
src/app.tsx
127
src/app.tsx
@ -1,14 +1,24 @@
|
||||
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
||||
import React from "react";
|
||||
import { createBrowserRouter, Navigate, Outlet, RouterProvider, useLocation } from "react-router-dom";
|
||||
import { HomeView } from "./views/home";
|
||||
import { UserPage } from "./views/user";
|
||||
import { ErrorBoundary } from "./components/error-boundary";
|
||||
import { Page } from "./components/page";
|
||||
import { SettingsView } from "./views/settings";
|
||||
import { LoginView } from "./views/login";
|
||||
import { ProfileView } from "./views/profile";
|
||||
import { EventPage } from "./views/event";
|
||||
import useSubject from "./hooks/use-subject";
|
||||
import identity from "./services/identity";
|
||||
import { FollowingTab } from "./views/home/following-tab";
|
||||
import { DiscoverTab } from "./views/home/discover-tab";
|
||||
import { GlobalTab } from "./views/home/global-tab";
|
||||
import { normalizeToHex } from "./helpers/nip-19";
|
||||
import UserView from "./views/user";
|
||||
import UserNotesTab from "./views/user/notes";
|
||||
import UserRepliesTab from "./views/user/replies";
|
||||
import UserFollowersTab from "./views/user/followers";
|
||||
import UserRelaysTab from "./views/user/relays";
|
||||
import UserFollowingTab from "./views/user/following";
|
||||
import NoteView from "./views/note";
|
||||
|
||||
const RequireSetup = ({ children }: { children: JSX.Element }) => {
|
||||
let location = useLocation();
|
||||
@ -19,57 +29,72 @@ const RequireSetup = ({ children }: { children: JSX.Element }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
const HomePage = () => (
|
||||
const RootPage = () => (
|
||||
<RequireSetup>
|
||||
<Page>
|
||||
<HomeView />
|
||||
<Outlet />
|
||||
</Page>
|
||||
</RequireSetup>
|
||||
);
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
<Route path="/login" element={<LoginView />} />
|
||||
<Route
|
||||
path="/u/:pubkey"
|
||||
element={
|
||||
<RequireSetup>
|
||||
<UserPage />
|
||||
</RequireSetup>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/n/:id"
|
||||
element={
|
||||
<RequireSetup>
|
||||
<EventPage />
|
||||
</RequireSetup>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<RequireSetup>
|
||||
<Page>
|
||||
<SettingsView />
|
||||
</Page>
|
||||
</RequireSetup>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<RequireSetup>
|
||||
<Page>
|
||||
<ProfileView />
|
||||
</Page>
|
||||
</RequireSetup>
|
||||
}
|
||||
/>
|
||||
<Route path="/*" element={<HomePage />} />
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <RootPage />,
|
||||
children: [
|
||||
{
|
||||
path: "/u/:pubkey",
|
||||
loader: ({ params }) => {
|
||||
if (!params.pubkey) throw new Error("Missing pubkey");
|
||||
const hexKey = normalizeToHex(params.pubkey);
|
||||
if (!hexKey) throw new Error(params.pubkey + " is not a valid pubkey");
|
||||
return { pubkey: hexKey };
|
||||
},
|
||||
element: <UserView />,
|
||||
children: [
|
||||
{ path: "", element: <UserNotesTab /> },
|
||||
{ path: "notes", element: <UserNotesTab /> },
|
||||
{ path: "replies", element: <UserRepliesTab /> },
|
||||
{ path: "followers", element: <UserFollowersTab /> },
|
||||
{ path: "following", element: <UserFollowingTab /> },
|
||||
{ path: "relays", element: <UserRelaysTab /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/n/:id",
|
||||
loader: ({ params }) => {
|
||||
if (!params.id) throw new Error("Missing pubkey");
|
||||
const hex = normalizeToHex(params.id);
|
||||
if (!hex) throw new Error(params.id + " is not a valid event id");
|
||||
return { id: hex };
|
||||
},
|
||||
element: <NoteView />,
|
||||
},
|
||||
{
|
||||
path: "settings",
|
||||
element: <SettingsView />,
|
||||
},
|
||||
{
|
||||
path: "profile",
|
||||
element: <ProfileView />,
|
||||
},
|
||||
{ path: "login", element: <LoginView /> },
|
||||
{
|
||||
path: "",
|
||||
element: <HomeView />,
|
||||
children: [
|
||||
{ path: "", element: <FollowingTab /> },
|
||||
{ path: "following", element: <FollowingTab /> },
|
||||
{ path: "discover", element: <DiscoverTab /> },
|
||||
{ path: "global", element: <GlobalTab /> },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
export const App = () => (
|
||||
<ErrorBoundary>
|
||||
<RouterProvider router={router} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React from "react";
|
||||
import { ChakraProvider, localStorageManager } from "@chakra-ui/react";
|
||||
import { BrowserRouter, HashRouter } from "react-router-dom";
|
||||
import theme from "../theme";
|
||||
|
||||
export const Providers = ({ children }: { children: React.ReactNode }) => (
|
||||
<ChakraProvider theme={theme} colorModeManager={localStorageManager}>
|
||||
<HashRouter>{children}</HashRouter>
|
||||
{children}
|
||||
</ChakraProvider>
|
||||
);
|
||||
|
@ -1,15 +1,17 @@
|
||||
import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
// import { useMatch, useNavigate } from "react-router-dom";
|
||||
import { DiscoverTab } from "./discover-tab";
|
||||
import { FollowingTab } from "./following-tab";
|
||||
import { GlobalTab } from "./global-tab";
|
||||
import { Outlet, useMatches, useNavigate } from "react-router-dom";
|
||||
|
||||
const tabs = [
|
||||
{ label: "Following", path: "/following" },
|
||||
{ label: "Discover", path: "/discover" },
|
||||
{ label: "Global", path: "/global" },
|
||||
];
|
||||
|
||||
export const HomeView = () => {
|
||||
// const navigate = useNavigate();
|
||||
// const followingMatch = useMatch("/following");
|
||||
// const discoverMatch = useMatch("/discover");
|
||||
const navigate = useNavigate();
|
||||
const matches = useMatches();
|
||||
|
||||
// const tabs = ["/following", "/discover", "/global"];
|
||||
const activeTab = tabs.indexOf(tabs.find((t) => matches[matches.length - 1].pathname === t.path) ?? tabs[0]);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
@ -18,24 +20,20 @@ export const HomeView = () => {
|
||||
flexGrow="1"
|
||||
overflow="hidden"
|
||||
isLazy
|
||||
// index={discoverMatch ? 1 : 0}
|
||||
// onChange={(v) => navigate(tabs[v])}
|
||||
index={activeTab}
|
||||
onChange={(v) => navigate(tabs[v].path)}
|
||||
>
|
||||
<TabList>
|
||||
<Tab>Following</Tab>
|
||||
<Tab>Discover</Tab>
|
||||
<Tab>Global</Tab>
|
||||
{tabs.map(({ label }) => (
|
||||
<Tab>{label}</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
<TabPanels overflow="auto" height="100%">
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<FollowingTab />
|
||||
</TabPanel>
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<DiscoverTab />
|
||||
</TabPanel>
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<GlobalTab />
|
||||
</TabPanel>
|
||||
{tabs.map(({ label }) => (
|
||||
<TabPanel key={label} pr={0} pl={0}>
|
||||
<Outlet />
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
|
@ -1,42 +1,13 @@
|
||||
import { Alert, AlertDescription, AlertIcon, AlertTitle, Flex, Spinner, Text } from "@chakra-ui/react";
|
||||
import { Page } from "../../components/page";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { normalizeToHex } from "../../helpers/nip-19";
|
||||
import { Flex, Spinner } from "@chakra-ui/react";
|
||||
import { useLoaderData } from "react-router-dom";
|
||||
import { Note } from "../../components/note";
|
||||
import { useThreadLoader } from "../../hooks/use-thread-loader";
|
||||
import { ThreadPost } from "./thread-post";
|
||||
|
||||
export const EventPage = () => {
|
||||
const params = useParams();
|
||||
let id = normalizeToHex(params.id ?? "");
|
||||
const NoteView = () => {
|
||||
const { id } = useLoaderData() as { id: string };
|
||||
|
||||
if (!id) {
|
||||
return (
|
||||
<Page>
|
||||
<Alert status="error">
|
||||
<AlertIcon />
|
||||
<AlertTitle>Invalid event id</AlertTitle>
|
||||
<AlertDescription>"{params.id}" dose not look like a valid event id</AlertDescription>
|
||||
</Alert>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<EventView eventId={id} />
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export type EventViewProps = {
|
||||
eventId: string;
|
||||
};
|
||||
|
||||
export const EventView = ({ eventId }: EventViewProps) => {
|
||||
const id = normalizeToHex(eventId) ?? "";
|
||||
const { thread, events, rootId, focusId, loading } = useThreadLoader(id, { enabled: !!id });
|
||||
|
||||
if (loading) return <Spinner />;
|
||||
|
||||
let pageContent = <span>Missing Event</span>;
|
||||
@ -76,3 +47,5 @@ export const EventView = ({ eventId }: EventViewProps) => {
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoteView;
|
@ -1,10 +1,10 @@
|
||||
import { Avatar, MenuItem } from "@chakra-ui/react";
|
||||
import { MenuIconButton } from "../../components/menu-icon-button";
|
||||
import { MenuIconButton } from "../../../components/menu-icon-button";
|
||||
|
||||
import { ClipboardIcon, IMAGE_ICONS } from "../../components/icons";
|
||||
import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip-19";
|
||||
import { ClipboardIcon, IMAGE_ICONS } from "../../../components/icons";
|
||||
import { Bech32Prefix, normalizeToBech32 } from "../../../helpers/nip-19";
|
||||
import { useCopyToClipboard } from "react-use";
|
||||
import { truncatedId } from "../../helpers/nostr-event";
|
||||
import { truncatedId } from "../../../helpers/nostr-event";
|
||||
|
||||
export const UserProfileMenu = ({ pubkey }: { pubkey: string }) => {
|
||||
const [_clipboardState, copyToClipboard] = useCopyToClipboard();
|
@ -2,8 +2,10 @@ import { Flex, FormControl, FormLabel, Grid, SkeletonText, Switch, useDisclosure
|
||||
|
||||
import { UserCard } from "./components/user-card";
|
||||
import { useUserFollowers } from "../../hooks/use-user-followers";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
|
||||
export const UserFollowersTab = ({ pubkey }: { pubkey: string }) => {
|
||||
const UserFollowersTab = () => {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const { isOpen, onToggle } = useDisclosure();
|
||||
const followers = useUserFollowers(pubkey, [], isOpen);
|
||||
|
||||
@ -29,3 +31,5 @@ export const UserFollowersTab = ({ pubkey }: { pubkey: string }) => {
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserFollowersTab;
|
||||
|
@ -3,8 +3,10 @@ import { Flex, Grid, SkeletonText, Text } from "@chakra-ui/react";
|
||||
|
||||
import { UserCard } from "./components/user-card";
|
||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
|
||||
export const UserFollowingTab = ({ pubkey }: { pubkey: string }) => {
|
||||
const UserFollowingTab = () => {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const contacts = useUserContacts(pubkey, [], true);
|
||||
|
||||
return (
|
||||
@ -24,3 +26,5 @@ export const UserFollowingTab = ({ pubkey }: { pubkey: string }) => {
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserFollowingTab;
|
||||
|
@ -1,71 +1,37 @@
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertIcon,
|
||||
AlertTitle,
|
||||
Flex,
|
||||
Heading,
|
||||
SkeletonText,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
Tabs,
|
||||
Text,
|
||||
Box,
|
||||
} from "@chakra-ui/react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { UserNotesTab } from "./notes";
|
||||
import { Flex, Heading, SkeletonText, Tab, TabList, TabPanel, TabPanels, Tabs, Text, Box } from "@chakra-ui/react";
|
||||
import { Outlet, useLoaderData, useMatches, useNavigate } from "react-router-dom";
|
||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||
import { UserAvatar } from "../../components/user-avatar";
|
||||
import { getUserDisplayName } from "../../helpers/user-metadata";
|
||||
import { useIsMobile } from "../../hooks/use-is-mobile";
|
||||
import { UserRelaysTab } from "./relays";
|
||||
import { UserFollowingTab } from "./following";
|
||||
import { normalizeToHex } from "../../helpers/nip-19";
|
||||
import { Page } from "../../components/page";
|
||||
import { UserProfileMenu } from "./user-profile-menu";
|
||||
import { UserFollowersTab } from "./followers";
|
||||
import { UserRepliesTab } from "./replies";
|
||||
import { UserProfileMenu } from "./components/user-profile-menu";
|
||||
|
||||
export const UserPage = () => {
|
||||
const params = useParams();
|
||||
let id = normalizeToHex(params.pubkey ?? "");
|
||||
const tabs = [
|
||||
{ label: "Notes", path: "notes" },
|
||||
{ label: "Replies", path: "replies" },
|
||||
{ label: "Followers", path: "followers" },
|
||||
{ label: "Following", path: "following" },
|
||||
{ label: "Relays", path: "relays" },
|
||||
];
|
||||
|
||||
if (!id) {
|
||||
return (
|
||||
<Page>
|
||||
<Alert status="error">
|
||||
<AlertIcon />
|
||||
<AlertTitle>Invalid pubkey</AlertTitle>
|
||||
<AlertDescription>"{params.pubkey}" dose not look like a valid pubkey</AlertDescription>
|
||||
</Alert>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<UserView pubkey={id} />
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export type UserViewProps = {
|
||||
pubkey: string;
|
||||
};
|
||||
|
||||
export const UserView = ({ pubkey }: UserViewProps) => {
|
||||
const UserView = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const navigate = useNavigate();
|
||||
const { pubkey } = useLoaderData() as { pubkey: string };
|
||||
|
||||
const matches = useMatches();
|
||||
const lastMatch = matches[matches.length - 1];
|
||||
console.log(lastMatch);
|
||||
|
||||
const activeTab = tabs.indexOf(tabs.find((t) => lastMatch.pathname.includes(t.path)) ?? tabs[0]);
|
||||
|
||||
const metadata = useUserMetadata(pubkey, [], true);
|
||||
const label = getUserDisplayName(metadata, pubkey);
|
||||
|
||||
const header = (
|
||||
<Flex gap="4" padding="2">
|
||||
<UserAvatar pubkey={pubkey} size={isMobile ? "md" : "xl"} />
|
||||
<Flex direction="column" gap={isMobile ? 0 : 2}>
|
||||
<Heading size={isMobile ? "md" : "lg"}>{label}</Heading>
|
||||
<Heading size={isMobile ? "md" : "lg"}>{getUserDisplayName(metadata, pubkey)}</Heading>
|
||||
{!metadata ? <SkeletonText /> : <Text>{metadata?.about}</Text>}
|
||||
</Flex>
|
||||
<Box ml="auto">
|
||||
@ -78,33 +44,31 @@ export const UserView = ({ pubkey }: UserViewProps) => {
|
||||
<Flex direction="column" alignItems="stretch" gap="2" overflow={isMobile ? "auto" : "hidden"} height="100%">
|
||||
{/* {metadata?.banner && <Image src={metadata.banner} />} */}
|
||||
{header}
|
||||
<Tabs display="flex" flexDirection="column" flexGrow="1" overflow={isMobile ? undefined : "hidden"} isLazy>
|
||||
<Tabs
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
flexGrow="1"
|
||||
overflow={isMobile ? undefined : "hidden"}
|
||||
isLazy
|
||||
index={activeTab}
|
||||
onChange={(v) => navigate(tabs[v].path)}
|
||||
>
|
||||
<TabList overflow={isMobile ? "auto" : undefined}>
|
||||
<Tab>Notes</Tab>
|
||||
<Tab>Replies</Tab>
|
||||
<Tab>Followers</Tab>
|
||||
<Tab>Following</Tab>
|
||||
<Tab>Relays</Tab>
|
||||
{tabs.map(({ label }) => (
|
||||
<Tab>{label}</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
|
||||
<TabPanels overflow={isMobile ? undefined : "auto"} height="100%">
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<UserNotesTab pubkey={pubkey} />
|
||||
</TabPanel>
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<UserRepliesTab pubkey={pubkey} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<UserFollowersTab pubkey={pubkey} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<UserFollowingTab pubkey={pubkey} />
|
||||
</TabPanel>
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<UserRelaysTab pubkey={pubkey} />
|
||||
</TabPanel>
|
||||
{tabs.map(({ label }) => (
|
||||
<TabPanel key={label} pr={0} pl={0}>
|
||||
<Outlet context={{ pubkey }} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserView;
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { Button, Flex, Spinner } from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { Note } from "../../components/note";
|
||||
import { isNote } from "../../helpers/nostr-event";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
|
||||
export const UserNotesTab = ({ pubkey }: { pubkey: string }) => {
|
||||
const UserNotesTab = () => {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
|
||||
const { events, loading, loadMore } = useTimelineLoader(
|
||||
`${pubkey} notes`,
|
||||
{ authors: [pubkey], kinds: [1], since: moment().subtract(1, "day").unix() },
|
||||
@ -21,3 +24,5 @@ export const UserNotesTab = ({ pubkey }: { pubkey: string }) => {
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserNotesTab;
|
||||
|
@ -3,8 +3,10 @@ import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Button, SkeletonText }
|
||||
import settings from "../../services/settings";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
|
||||
export const UserRelaysTab = ({ pubkey }: { pubkey: string }) => {
|
||||
const UserRelaysTab = () => {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const contacts = useUserContacts(pubkey);
|
||||
|
||||
const relays = useSubject(settings.relays);
|
||||
@ -44,3 +46,5 @@ export const UserRelaysTab = ({ pubkey }: { pubkey: string }) => {
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserRelaysTab;
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Button, Flex, Spinner } from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { Note } from "../../components/note";
|
||||
import { isReply } from "../../helpers/nostr-event";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
|
||||
export const UserRepliesTab = ({ pubkey }: { pubkey: string }) => {
|
||||
const UserRepliesTab = () => {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const { events, loading, loadMore } = useTimelineLoader(
|
||||
`${pubkey} replies`,
|
||||
{ authors: [pubkey], kinds: [1], since: moment().subtract(4, "hours").unix() },
|
||||
@ -21,3 +23,5 @@ export const UserRepliesTab = ({ pubkey }: { pubkey: string }) => {
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserRepliesTab;
|
||||
|
Loading…
x
Reference in New Issue
Block a user