mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-27 10:11:49 +01:00
cleanup page layout
This commit is contained in:
parent
e04aa5c02c
commit
cff46755e4
src
components
views
badges
emoji-packs
goals
hashtag
home
lists
messages
note
notifications
profile
relays
search
settings
streams
tools
user
@ -21,7 +21,7 @@ export default function DesktopSideNav(props: Omit<FlexProps, "children">) {
|
||||
gap="2"
|
||||
direction="column"
|
||||
width="15rem"
|
||||
pt="2"
|
||||
p="2"
|
||||
alignItems="stretch"
|
||||
flexShrink={0}
|
||||
h="100vh"
|
||||
|
@ -12,7 +12,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<ReloadPrompt mb="2" />
|
||||
<Container size="lg" display="flex" padding="0" gap="4" alignItems="flex-start">
|
||||
<Container size="lg" display="flex" padding="0" alignItems="flex-start">
|
||||
{!isMobile && <DesktopSideNav position="sticky" top="0" />}
|
||||
<Flex flexGrow={1} direction="column" w="full" overflow="hidden" pb={isMobile ? "14" : 0} minH="50vh">
|
||||
<ErrorBoundary>{children}</ErrorBoundary>
|
||||
|
@ -30,7 +30,11 @@ import { UserAvatarStack } from "../compact-user-stack";
|
||||
import MagicTextArea from "../magic-textarea";
|
||||
import { useContextEmojis } from "../../providers/emoji-provider";
|
||||
|
||||
export default function PostModal({ isOpen, onClose, initContent = '' }: Omit<ModalProps, "children"> & {initContent?: string}) {
|
||||
export default function PostModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
initContent = "",
|
||||
}: Omit<ModalProps, "children"> & { initContent?: string }) {
|
||||
const toast = useToast();
|
||||
const { requestSignature } = useSigningContext();
|
||||
const writeRelays = useWriteRelayUrls();
|
||||
|
9
src/components/vertical-page-layout.tsx
Normal file
9
src/components/vertical-page-layout.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Flex, FlexProps } from "@chakra-ui/react";
|
||||
|
||||
export default function VerticalPageLayout({ children, ...props }: FlexProps) {
|
||||
return (
|
||||
<Flex direction="column" pt="2" pb="12" gap="2" px="2" {...props}>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
@ -21,6 +21,7 @@ import { UserAvatarLink } from "../../components/user-avatar-link";
|
||||
import { UserLink } from "../../components/user-link";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import Timestamp from "../../components/timestamp";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function BadgeDetailsPage({ badge }: { badge: NostrEvent }) {
|
||||
const navigate = useNavigate();
|
||||
@ -45,8 +46,8 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) {
|
||||
const isAuthor = account?.pubkey === badge.pubkey;
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex direction="column" px="2" pt="2" pb="8" overflow="hidden" h="full" gap="2">
|
||||
<Flex gap="2" alignItems="center">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<Button onClick={() => navigate(-1)} leftIcon={<ArrowLeftSIcon />}>
|
||||
Back
|
||||
</Button>
|
||||
@ -96,7 +97,7 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) {
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import BadgeCard from "./components/badge-card";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function BadgesBrowsePage() {
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
@ -27,7 +28,7 @@ function BadgesBrowsePage() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex direction="column" gap="2" p="2">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
</Flex>
|
||||
@ -37,7 +38,7 @@ function BadgesBrowsePage() {
|
||||
<BadgeCard key={getEventUID(badge)} badge={badge} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -6,13 +6,14 @@ import { ExternalLinkIcon } from "../../components/icons";
|
||||
import RequireCurrentAccount from "../../providers/require-current-account";
|
||||
import BadgeCard from "./components/badge-card";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function BadgesPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
|
||||
return (
|
||||
<Flex direction="column" pt="2" pb="10" gap="2" px={["2", "2", 0]}>
|
||||
<Flex gap="2">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
<Button as={RouterLink} to="/badges/browse">
|
||||
Browse Badges
|
||||
</Button>
|
||||
@ -27,19 +28,7 @@ function BadgesPage() {
|
||||
Badges
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{/* {peopleLists.length > 0 && (
|
||||
<>
|
||||
<Heading size="md">People lists</Heading>
|
||||
<Divider />
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{peopleLists.map((event) => (
|
||||
<BadgeCard key={getEventUID(event)} badge={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)} */}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import useSubject from "../../hooks/use-subject";
|
||||
import EmojiPackCard from "./components/emoji-pack-card";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import { EMOJI_PACK_KIND, getEmojisFromPack } from "../../helpers/nostr/emoji-packs";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function EmojiPacksBrowsePage() {
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
@ -37,7 +38,7 @@ function EmojiPacksBrowsePage() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex direction="column" gap="2" p="2" pb="10">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
<Switch checked={showEmpty.isOpen} onChange={showEmpty.onToggle} whiteSpace="pre">
|
||||
@ -50,7 +51,7 @@ function EmojiPacksBrowsePage() {
|
||||
<EmojiPackCard key={getEventUID(event)} pack={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -17,13 +17,13 @@ import { useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { EMOJI_PACK_KIND } from "../../helpers/nostr/emoji-packs";
|
||||
import { DraftNostrEvent } from "../../types/nostr-event";
|
||||
import { useSigningContext } from "../../providers/signing-provider";
|
||||
import NostrPublishAction from "../../classes/nostr-publish-action";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import replaceableEventLoaderService from "../../services/replaceable-event-requester";
|
||||
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||
import { EMOJI_PACK_KIND } from "../../../helpers/nostr/emoji-packs";
|
||||
import { DraftNostrEvent } from "../../../types/nostr-event";
|
||||
import { useSigningContext } from "../../../providers/signing-provider";
|
||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||
import clientRelaysService from "../../../services/client-relays";
|
||||
import replaceableEventLoaderService from "../../../services/replaceable-event-requester";
|
||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||
|
||||
export default function EmojiPackCreateModal({ onClose, ...props }: Omit<ModalProps, "children">) {
|
||||
const toast = useToast();
|
@ -33,6 +33,7 @@ import NostrPublishAction from "../../classes/nostr-publish-action";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import replaceableEventLoaderService from "../../services/replaceable-event-requester";
|
||||
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function AddEmojiForm({ onAdd }: { onAdd: (values: { name: string; url: string }) => void }) {
|
||||
const { register, handleSubmit, watch, getValues, reset } = useForm({
|
||||
@ -114,7 +115,7 @@ function EmojiPackPage({ pack }: { pack: NostrEvent }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" px="2" pt="2" pb="8" overflowY="auto" overflowX="hidden" h="full" gap="2">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center">
|
||||
<Button onClick={() => navigate(-1)} leftIcon={<ArrowLeftSIcon />}>
|
||||
Back
|
||||
@ -188,7 +189,7 @@ function EmojiPackPage({ pack }: { pack: NostrEvent }) {
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button, Divider, Flex, Heading, Link, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
|
||||
import { Button, Divider, Flex, Heading, Link, SimpleGrid, useDisclosure } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
@ -11,7 +11,8 @@ import useSubject from "../../hooks/use-subject";
|
||||
import EmojiPackCard from "./components/emoji-pack-card";
|
||||
import useFavoriteEmojiPacks from "../../hooks/use-favorite-emoji-packs";
|
||||
import useReplaceableEvents from "../../hooks/use-replaceable-events";
|
||||
import EmojiPackCreateModal from "./create-modal";
|
||||
import EmojiPackCreateModal from "./components/create-modal";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function UserEmojiPackMangerPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
@ -71,13 +72,12 @@ export default function EmojiPacksView() {
|
||||
const createModal = useDisclosure();
|
||||
|
||||
return (
|
||||
<Flex direction="column" pt="2" pb="10" gap="2" px={["2", "2", 0]}>
|
||||
<Flex gap="2">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
<Button as={RouterLink} to="/emojis/browse">
|
||||
Find packs
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button as={Link} href="https://emojis-iota.vercel.app/" isExternal rightIcon={<ExternalLinkIcon />}>
|
||||
<Button as={Link} href="https://emojis-iota.vercel.app/" isExternal rightIcon={<ExternalLinkIcon />} ml="auto">
|
||||
Emoji pack manager
|
||||
</Button>
|
||||
{account && (
|
||||
@ -89,6 +89,6 @@ export default function EmojiPacksView() {
|
||||
|
||||
{account && <UserEmojiPackMangerPage />}
|
||||
{createModal.isOpen && <EmojiPackCreateModal isOpen={createModal.isOpen} onClose={createModal.onClose} />}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import { SwipeState } from "yet-another-react-lightbox";
|
||||
import { useCallback } from "react";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import dayjs from "dayjs";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function GoalsBrowsePage() {
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
@ -40,7 +41,7 @@ function GoalsBrowsePage() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex direction="column" gap="2" p="2" pb="10">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
<Switch isChecked={showClosed.isOpen} onChange={showClosed.onToggle}>
|
||||
@ -53,7 +54,7 @@ function GoalsBrowsePage() {
|
||||
<GoalCard key={getEventUID(event)} goal={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import { Button, ButtonGroup, Divider, Flex, Heading, Spacer, Spinner } from "@chakra-ui/react";
|
||||
|
||||
import { ArrowLeftSIcon } from "../../components/icons";
|
||||
import GoalMenu from "./components/goal-menu";
|
||||
import { getGoalAmount, getGoalName } from "../../helpers/nostr/goal";
|
||||
@ -15,6 +15,7 @@ import GoalContents from "./components/goal-contents";
|
||||
import GoalZapList from "./components/goal-zap-list";
|
||||
import { readablizeSats } from "../../helpers/bolt11";
|
||||
import GoalZapButton from "./components/goal-zap-button";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function useGoalPointerFromParams(): EventPointer {
|
||||
const { id } = useParams() as { id: string };
|
||||
@ -33,7 +34,7 @@ export default function GoalDetailsView() {
|
||||
if (!goal) return <Spinner />;
|
||||
|
||||
return (
|
||||
<Flex direction="column" px="2" pt="2" pb="8" overflow="hidden" h="full" gap="2">
|
||||
<VerticalPageLayout overflow="hidden" h="full">
|
||||
<Flex gap="2" alignItems="center">
|
||||
<Button onClick={() => navigate(-1)} leftIcon={<ArrowLeftSIcon />}>
|
||||
Back
|
||||
@ -65,6 +66,6 @@ export default function GoalDetailsView() {
|
||||
</Heading>
|
||||
<Divider />
|
||||
<GoalZapList goal={goal} />
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import GoalCard from "./components/goal-card";
|
||||
import { GOAL_KIND } from "../../helpers/nostr/goal";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function UserGoalsManagerPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
@ -64,7 +65,7 @@ export default function GoalsView() {
|
||||
const account = useCurrentAccount();
|
||||
|
||||
return (
|
||||
<Flex direction="column" pt="2" pb="10" gap="2" px={["2", "2", 0]}>
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2">
|
||||
<Button as={RouterLink} to="/goals/browse">
|
||||
Explore goals
|
||||
@ -76,6 +77,6 @@ export default function GoalsView() {
|
||||
</Flex>
|
||||
|
||||
{account ? <UserGoalsManagerPage /> : <Navigate to="/goals/browse" />}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ function HashTagPage() {
|
||||
useRelaysChanged(readRelays, () => timeline.reset());
|
||||
|
||||
const header = (
|
||||
<Flex gap="4" alignItems="center" wrap="wrap" pr="2">
|
||||
<Flex gap="4" alignItems="center" wrap="wrap">
|
||||
<Editable
|
||||
value={editableHashtag}
|
||||
onChange={(v) => setEditableHashtag(v)}
|
||||
@ -100,7 +100,7 @@ function HashTagPage() {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return <TimelinePage timeline={timeline} header={header} pt="4" pb="8" />;
|
||||
return <TimelinePage timeline={timeline} header={header} pt="2" pb="12" px="2" />;
|
||||
}
|
||||
|
||||
export default function HashTagView() {
|
||||
|
@ -52,7 +52,7 @@ function HomePage() {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return <TimelinePage timeline={timeline} header={header} pt="2" pb="8" />;
|
||||
return <TimelinePage timeline={timeline} header={header} pt="2" pb="12" px="2" />;
|
||||
}
|
||||
|
||||
export default function HomeView() {
|
||||
|
@ -18,6 +18,7 @@ import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import ListCard from "./components/list-card";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function BrowseListPage() {
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
@ -53,7 +54,7 @@ function BrowseListPage() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex direction="column" gap="2" p="2">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
<Select w="sm" value={listKind} onChange={(e) => setListKind(parseInt(e.target.value))}>
|
||||
@ -73,7 +74,7 @@ function BrowseListPage() {
|
||||
<ListCard key={getEventUID(event)} event={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ function ListCardRender({ event, ...props }: Omit<CardProps, "children"> & { eve
|
||||
return (
|
||||
<Card ref={ref} variant="outline" {...props}>
|
||||
<CardHeader display="flex" alignItems="center" p="2" pb="0">
|
||||
<Heading size="md">
|
||||
<Heading size="md" isTruncated>
|
||||
<Link as={RouterLink} to={`/lists/${link}`}>
|
||||
{getListName(event)}
|
||||
</Link>
|
||||
|
@ -12,6 +12,7 @@ import NewListModal from "./components/new-list-modal";
|
||||
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||
import { MUTE_LIST_KIND, NOTE_LIST_KIND, PEOPLE_LIST_KIND, PIN_LIST_KIND } from "../../helpers/nostr/lists";
|
||||
import useFavoriteLists from "../../hooks/use-favorite-lists";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function ListsPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
@ -24,7 +25,7 @@ function ListsPage() {
|
||||
const noteLists = lists.filter((event) => event.kind === NOTE_LIST_KIND);
|
||||
|
||||
return (
|
||||
<Flex direction="column" pt="2" pb="10" gap="2" px={["2", "2", 0]}>
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2">
|
||||
<Button as={RouterLink} to="/lists/browse">
|
||||
Browse Lists
|
||||
@ -92,7 +93,7 @@ function ListsPage() {
|
||||
onCreated={(list) => navigate(`/lists/${getSharableEventAddress(list)}`)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,13 @@ import { useDeleteEventContext } from "../../providers/delete-event-provider";
|
||||
import { parseCoordinate } from "../../helpers/nostr/events";
|
||||
import { getEventsFromList, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { EventRelays } from "../../components/note/note-relays";
|
||||
import UserCard from "./components/user-card";
|
||||
import NoteCard from "./components/note-card";
|
||||
import { TrustProvider } from "../../providers/trust";
|
||||
import ListMenu from "./components/list-menu";
|
||||
import ListFavoriteButton from "./components/list-favorite-button";
|
||||
import ListFeedButton from "./components/list-feed-button";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function useListCoordinate() {
|
||||
const { addr } = useParams() as { addr: string };
|
||||
@ -51,7 +51,7 @@ export default function ListDetailsView() {
|
||||
const notes = getEventsFromList(event);
|
||||
|
||||
return (
|
||||
<Flex direction="column" px="2" pt="2" pb="8" overflow="hidden" h="full" gap="2">
|
||||
<VerticalPageLayout overflow="hidden" h="full">
|
||||
<Flex gap="2" alignItems="center">
|
||||
<Button onClick={() => navigate(-1)} leftIcon={<ArrowLeftSIcon />}>
|
||||
Back
|
||||
@ -98,6 +98,6 @@ export default function ListDetailsView() {
|
||||
</TrustProvider>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import { ExternalLinkIcon } from "../../components/icons";
|
||||
import RequireCurrentAccount from "../../providers/require-current-account";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import Timestamp from "../../components/timestamp";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function ContactCard({ pubkey }: { pubkey: string }) {
|
||||
const subject = useMemo(() => directMessagesService.getUserMessages(pubkey), [pubkey]);
|
||||
@ -92,7 +93,7 @@ function DirectMessagesPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" overflowX="hidden" overflowY="auto" height="100%" pt="2" pb="8">
|
||||
<VerticalPageLayout>
|
||||
<Alert status="info" flexShrink={0}>
|
||||
<AlertIcon />
|
||||
<Flex direction={{ base: "column", lg: "row" }}>
|
||||
@ -113,7 +114,7 @@ function DirectMessagesPage() {
|
||||
<Button onClick={loadMore} isLoading={loading} flexShrink={0}>
|
||||
Load More
|
||||
</Button>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import Note from "../../components/note";
|
||||
import { isHexKey } from "../../helpers/nip19";
|
||||
import { useThreadLoader } from "../../hooks/use-thread-loader";
|
||||
import { ThreadPost } from "./components/thread-post";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function useNotePointer() {
|
||||
const { id } = useParams() as { id: string };
|
||||
@ -61,9 +62,5 @@ export default function NoteView() {
|
||||
pageContent = <Note event={events[focusId]} variant="filled" hideDrawerButton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="4" flex={1} pb="12" pt="4" pl="1" pr="1">
|
||||
{pageContent}
|
||||
</Flex>
|
||||
);
|
||||
return <VerticalPageLayout>{pageContent}</VerticalPageLayout>;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import EmbeddedUnknown from "../../components/embed-event/event-types/embedded-u
|
||||
import { NoteContents } from "../../components/note/note-contents";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
const Kind1Notification = forwardRef<HTMLDivElement, { event: NostrEvent }>(({ event }, ref) => {
|
||||
const refs = getReferences(event);
|
||||
@ -189,31 +190,31 @@ function NotificationsPage() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex gap="2" alignItems="center" py="2" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
<Switch isChecked={!hideReplies.isOpen} onChange={hideReplies.onToggle}>
|
||||
Replies
|
||||
</Switch>
|
||||
<Switch isChecked={!hideMentions.isOpen} onChange={hideMentions.onToggle}>
|
||||
Mentions
|
||||
</Switch>
|
||||
<Switch isChecked={!hideReactions.isOpen} onChange={hideReactions.onToggle}>
|
||||
Reactions
|
||||
</Switch>
|
||||
<Switch isChecked={!hideShares.isOpen} onChange={hideShares.onToggle}>
|
||||
Shares
|
||||
</Switch>
|
||||
<Switch isChecked={!hideZaps.isOpen} onChange={hideZaps.onToggle}>
|
||||
Zaps
|
||||
</Switch>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4" pt="2" pb="12">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
<Switch isChecked={!hideReplies.isOpen} onChange={hideReplies.onToggle}>
|
||||
Replies
|
||||
</Switch>
|
||||
<Switch isChecked={!hideMentions.isOpen} onChange={hideMentions.onToggle}>
|
||||
Mentions
|
||||
</Switch>
|
||||
<Switch isChecked={!hideReactions.isOpen} onChange={hideReactions.onToggle}>
|
||||
Reactions
|
||||
</Switch>
|
||||
<Switch isChecked={!hideShares.isOpen} onChange={hideShares.onToggle}>
|
||||
Shares
|
||||
</Switch>
|
||||
<Switch isChecked={!hideZaps.isOpen} onChange={hideZaps.onToggle}>
|
||||
Zaps
|
||||
</Switch>
|
||||
</Flex>
|
||||
{events.map((event) => (
|
||||
<NotificationItem key={event.id} event={event} />
|
||||
))}
|
||||
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import signingService from "../../services/signing";
|
||||
import userMetadataService from "../../services/user-metadata";
|
||||
import { DraftNostrEvent } from "../../types/nostr-event";
|
||||
import lnurlMetadataService from "../../services/lnurl-metadata";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
const isEmail =
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
@ -67,126 +68,122 @@ const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => {
|
||||
}, [defaultValues]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" pb="4" px={["2", 0]}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Flex direction="column" gap="2" pt="4">
|
||||
<Flex gap="2">
|
||||
<FormControl isInvalid={!!errors.displayName}>
|
||||
<FormLabel>Display Name</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("displayName", {
|
||||
minLength: 2,
|
||||
maxLength: 64,
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.displayName?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={!!errors.username} isRequired>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("username", {
|
||||
minLength: 2,
|
||||
maxLength: 64,
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z0-9_-]{4,64}$/,
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.username?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
<Flex gap="2" alignItems="center">
|
||||
<FormControl isInvalid={!!errors.picture}>
|
||||
<FormLabel>Picture</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
placeholder="https://domain.com/path/picture.png"
|
||||
{...register("picture", { maxLength: 150 })}
|
||||
/>
|
||||
</FormControl>
|
||||
<Avatar src={watch("picture")} size="lg" ignoreFallback />
|
||||
</Flex>
|
||||
<FormControl isInvalid={!!errors.nip05}>
|
||||
<FormLabel>NIP-05 ID</FormLabel>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="user@domain.com"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("nip05", {
|
||||
minLength: 5,
|
||||
validate: async (address) => {
|
||||
if (!address) return true;
|
||||
if (!address.includes("@")) return "Invalid address";
|
||||
try {
|
||||
const id = await dnsIdentityService.fetchIdentity(address);
|
||||
if (!id) return "Cant find NIP-05 ID";
|
||||
if (id.pubkey !== account.pubkey) return "Pubkey dose not match";
|
||||
} catch (e) {
|
||||
return "Failed to fetch ID";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.nip05?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={!!errors.website}>
|
||||
<FormLabel>Website</FormLabel>
|
||||
<Input
|
||||
type="url"
|
||||
autoComplete="off"
|
||||
placeholder="https://example.com"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("website", { maxLength: 300 })}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={!!errors.about}>
|
||||
<FormLabel>About</FormLabel>
|
||||
<Textarea
|
||||
placeholder="A short description"
|
||||
resize="vertical"
|
||||
rows={6}
|
||||
isDisabled={isSubmitting}
|
||||
{...register("about")}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={!!errors.lightningAddress}>
|
||||
<FormLabel>Lightning Address (or LNURL)</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("lightningAddress", {
|
||||
validate: async (v) => {
|
||||
if (!v) return true;
|
||||
if (!isLNURL(v) && !isLightningAddress(v)) {
|
||||
return "Must be lightning address or LNURL";
|
||||
}
|
||||
const metadata = await lnurlMetadataService.requestMetadata(v);
|
||||
if (!metadata) {
|
||||
return "Incorrect or broken LNURL address";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.lightningAddress?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Flex alignSelf="flex-end" gap="2">
|
||||
<Button as={Link} isExternal href="https://metadata.nostr.com/" rightIcon={<ExternalLinkIcon />}>
|
||||
Download Backup
|
||||
</Button>
|
||||
<Button onClick={() => reset()}>Reset</Button>
|
||||
<Button colorScheme="brand" isLoading={isSubmitting} type="submit">
|
||||
Update
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</form>
|
||||
</Flex>
|
||||
<VerticalPageLayout as="form" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Flex gap="2">
|
||||
<FormControl isInvalid={!!errors.displayName}>
|
||||
<FormLabel>Display Name</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("displayName", {
|
||||
minLength: 2,
|
||||
maxLength: 64,
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.displayName?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={!!errors.username} isRequired>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("username", {
|
||||
minLength: 2,
|
||||
maxLength: 64,
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z0-9_-]{4,64}$/,
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.username?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
<Flex gap="2" alignItems="center">
|
||||
<FormControl isInvalid={!!errors.picture}>
|
||||
<FormLabel>Picture</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
placeholder="https://domain.com/path/picture.png"
|
||||
{...register("picture", { maxLength: 150 })}
|
||||
/>
|
||||
</FormControl>
|
||||
<Avatar src={watch("picture")} size="lg" ignoreFallback />
|
||||
</Flex>
|
||||
<FormControl isInvalid={!!errors.nip05}>
|
||||
<FormLabel>NIP-05 ID</FormLabel>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="user@domain.com"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("nip05", {
|
||||
minLength: 5,
|
||||
validate: async (address) => {
|
||||
if (!address) return true;
|
||||
if (!address.includes("@")) return "Invalid address";
|
||||
try {
|
||||
const id = await dnsIdentityService.fetchIdentity(address);
|
||||
if (!id) return "Cant find NIP-05 ID";
|
||||
if (id.pubkey !== account.pubkey) return "Pubkey dose not match";
|
||||
} catch (e) {
|
||||
return "Failed to fetch ID";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.nip05?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={!!errors.website}>
|
||||
<FormLabel>Website</FormLabel>
|
||||
<Input
|
||||
type="url"
|
||||
autoComplete="off"
|
||||
placeholder="https://example.com"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("website", { maxLength: 300 })}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={!!errors.about}>
|
||||
<FormLabel>About</FormLabel>
|
||||
<Textarea
|
||||
placeholder="A short description"
|
||||
resize="vertical"
|
||||
rows={6}
|
||||
isDisabled={isSubmitting}
|
||||
{...register("about")}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={!!errors.lightningAddress}>
|
||||
<FormLabel>Lightning Address (or LNURL)</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("lightningAddress", {
|
||||
validate: async (v) => {
|
||||
if (!v) return true;
|
||||
if (!isLNURL(v) && !isLightningAddress(v)) {
|
||||
return "Must be lightning address or LNURL";
|
||||
}
|
||||
const metadata = await lnurlMetadataService.requestMetadata(v);
|
||||
if (!metadata) {
|
||||
return "Incorrect or broken LNURL address";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.lightningAddress?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Flex alignSelf="flex-end" gap="2">
|
||||
<Button as={Link} isExternal href="https://metadata.nostr.com/" rightIcon={<ExternalLinkIcon />}>
|
||||
Download Backup
|
||||
</Button>
|
||||
<Button onClick={() => reset()}>Reset</Button>
|
||||
<Button colorScheme="brand" isLoading={isSubmitting} type="submit">
|
||||
Update
|
||||
</Button>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -11,6 +11,7 @@ import RelayCard from "./components/relay-card";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import { RelayMode } from "../../classes/relay";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
export default function RelaysView() {
|
||||
const [search, setSearch] = useState("");
|
||||
@ -37,7 +38,7 @@ export default function RelaysView() {
|
||||
}, [isSearching, deboundedSearch, onlineRelays, clientRelays]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" p="2">
|
||||
<VerticalPageLayout>
|
||||
<Flex alignItems="center" gap="2" wrap="wrap">
|
||||
<Input type="search" placeholder="search" value={search} onChange={(e) => setSearch(e.target.value)} w="auto" />
|
||||
<Spacer />
|
||||
@ -81,6 +82,6 @@ export default function RelaysView() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -24,13 +24,14 @@ import RelayNotes from "./relay-notes";
|
||||
import PeopleListProvider from "../../../providers/people-list-provider";
|
||||
import PeopleListSelection from "../../../components/people-list-selection/people-list-selection";
|
||||
import { RelayFavicon } from "../../../components/relay-favicon";
|
||||
import VerticalPageLayout from "../../../components/vertical-page-layout";
|
||||
|
||||
function RelayPage({ relay }: { relay: string }) {
|
||||
const { info } = useRelayInfo(relay);
|
||||
const showReviewForm = useDisclosure();
|
||||
|
||||
return (
|
||||
<Flex direction="column" alignItems="stretch" gap="2" p="2">
|
||||
<VerticalPageLayout alignItems="stretch">
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<RelayFavicon relay={relay} />
|
||||
<Heading isTruncated size={{ base: "md", sm: "lg" }} mr="auto">
|
||||
@ -88,7 +89,7 @@ function RelayPage({ relay }: { relay: string }) {
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-
|
||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { ArrowLeftSIcon } from "../../components/icons";
|
||||
|
||||
function RelayReviewsPage() {
|
||||
const navigate = useNavigate();
|
||||
@ -32,15 +34,15 @@ function RelayReviewsPage() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider<string> callback={callback}>
|
||||
<Flex direction="column" gap="2" py="2">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2">
|
||||
<Button onClick={() => navigate(-1)}>Back</Button>
|
||||
<Button onClick={() => navigate(-1)} leftIcon={<ArrowLeftSIcon/>}>Back</Button>
|
||||
<PeopleListSelection />
|
||||
</Flex>
|
||||
{reviews.map((event) => (
|
||||
<RelayReviewNote key={event.id} event={event} />
|
||||
))}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import { embedNostrLinks, renderGenericUrl } from "../../components/embed-types"
|
||||
import { UserLink } from "../../components/user-link";
|
||||
import trustedUserStatsService, { NostrBandUserStats } from "../../services/trusted-user-stats";
|
||||
import { readablizeSats } from "../../helpers/bolt11";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function ProfileResult({ profile }: { profile: NostrEvent }) {
|
||||
const metadata = parseKind0Event(profile);
|
||||
@ -137,7 +138,7 @@ export function SearchPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" py="2" px={["2", "2", 0]} gap="2">
|
||||
<VerticalPageLayout>
|
||||
<QrScannerModal isOpen={qrScannerModal.isOpen} onClose={qrScannerModal.onClose} onData={handleSearchText} />
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
@ -163,7 +164,7 @@ export function SearchPage() {
|
||||
</Link>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import PerformanceSettings from "./performance-settings";
|
||||
import PrivacySettings from "./privacy-settings";
|
||||
import useAppSettings from "../../hooks/use-app-settings";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
export default function SettingsView() {
|
||||
const toast = useToast();
|
||||
@ -31,8 +32,7 @@ export default function SettingsView() {
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex direction="column" pt="2" pb="2">
|
||||
<form onSubmit={saveSettings}>
|
||||
<VerticalPageLayout as="form" onSubmit={saveSettings}>
|
||||
<FormProvider {...form}>
|
||||
<Accordion defaultIndex={[0]} allowMultiple>
|
||||
<DisplaySettings />
|
||||
@ -56,7 +56,6 @@ export default function SettingsView() {
|
||||
Save Settings
|
||||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import { NostrRequestFilter } from "../../types/nostr-query";
|
||||
import { useAppTitle } from "../../hooks/use-app-title";
|
||||
import useUserMuteFilter from "../../hooks/use-user-mute-filter";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function StreamsPage() {
|
||||
useAppTitle("Streams");
|
||||
@ -53,7 +54,7 @@ function StreamsPage() {
|
||||
const endedStreams = streams.filter((stream) => stream.status === "ended");
|
||||
|
||||
return (
|
||||
<Flex p="2" gap="2" overflow="hidden" direction="column">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
<RelaySelectionButton ml="auto" />
|
||||
@ -73,7 +74,7 @@ function StreamsPage() {
|
||||
</SimpleGrid>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
</IntersectionObserverProvider>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
export default function StreamsView() {
|
||||
|
@ -53,6 +53,7 @@ import StreamHashtags from "../components/stream-hashtags";
|
||||
import StreamZapButton from "../components/stream-zap-button";
|
||||
import StreamGoal from "../components/stream-goal";
|
||||
import StreamShareButton from "../components/stream-share-button";
|
||||
import VerticalPageLayout from "../../../components/vertical-page-layout";
|
||||
|
||||
function DesktopStreamPage({ stream }: { stream: ParsedStream }) {
|
||||
useAppTitle(stream.title);
|
||||
@ -87,7 +88,7 @@ function DesktopStreamPage({ stream }: { stream: ParsedStream }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" p="2" pb="10">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center">
|
||||
<Button onClick={() => navigate(-1)} leftIcon={<ArrowLeftSIcon />}>
|
||||
Back
|
||||
@ -137,7 +138,7 @@ function DesktopStreamPage({ stream }: { stream: ParsedStream }) {
|
||||
<Flex gap="2" wrap="wrap">
|
||||
<StreamerCards pubkey={stream.host} maxW="lg" minW="md" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@ -147,7 +148,7 @@ function MobileStreamPage({ stream }: { stream: ParsedStream }) {
|
||||
const showChat = useDisclosure();
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" overflow="hidden" py="2">
|
||||
<VerticalPageLayout px={0}>
|
||||
<Flex gap="2" alignItems="center" px="2" flexShrink={0}>
|
||||
<Button onClick={() => navigate(-1)} leftIcon={<ArrowLeftSIcon />} size="sm">
|
||||
Back
|
||||
@ -197,7 +198,7 @@ function MobileStreamPage({ stream }: { stream: ParsedStream }) {
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Button, Flex, Heading, Image, Link } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { ExternalLinkIcon, MapIcon, ToolsIcon } from "../../components/icons";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
export default function ToolsHomeView() {
|
||||
return (
|
||||
<Flex direction="column" gap="4" p="4">
|
||||
<VerticalPageLayout>
|
||||
<Heading>
|
||||
<ToolsIcon /> Tools
|
||||
</Heading>
|
||||
@ -75,6 +76,6 @@ export default function ToolsHomeView() {
|
||||
Nostr Apps
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import { UserAvatarLink } from "../../components/user-avatar-link";
|
||||
import { UserLink } from "../../components/user-link";
|
||||
import { ArrowLeftSIcon } from "../../components/icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
const User = memo(({ pubkey, count }: { pubkey: string; count: number }) => (
|
||||
<Flex gap="2" overflow="hidden">
|
||||
@ -33,7 +34,7 @@ function NetworkPage() {
|
||||
}, [range, network]);
|
||||
|
||||
return (
|
||||
<Flex gap="2" direction="column" p="2">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2">
|
||||
<Button leftIcon={<ArrowLeftSIcon />} onClick={() => navigate(-1)}>
|
||||
Back
|
||||
@ -52,7 +53,7 @@ function NetworkPage() {
|
||||
<User key={pubkey} pubkey={pubkey} count={count} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import IntersectionObserverProvider from "../../providers/intersection-observer"
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||
import EmbeddedArticle from "../../components/embed-event/event-types/embedded-article";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
export default function UserArticlesTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
@ -24,12 +25,12 @@ export default function UserArticlesTab() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex gap="2" pt="2" pb="10" px={["2", "2", 0]} direction="column">
|
||||
<VerticalPageLayout>
|
||||
{articles.map((article) => (
|
||||
<EmbeddedArticle article={article} />
|
||||
))}
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import EmojiPackCard from "../emoji-packs/components/emoji-pack-card";
|
||||
import { EMOJI_PACK_KIND, getPackCordsFromFavorites } from "../../helpers/nostr/emoji-packs";
|
||||
import useFavoriteEmojiPacks from "../../hooks/use-favorite-emoji-packs";
|
||||
import useReplaceableEvents from "../../hooks/use-replaceable-events";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
export default function UserEmojiPacksTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
@ -29,7 +30,7 @@ export default function UserEmojiPacksTab() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex gap="2" pt="2" pb="10" px={["2", "2", 0]} direction="column">
|
||||
<VerticalPageLayout>
|
||||
{packs.length > 0 && (
|
||||
<>
|
||||
<Heading size="md" mt="2">
|
||||
@ -56,7 +57,7 @@ export default function UserEmojiPacksTab() {
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export default function UserFollowersTab() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 3, xl: 4 }} spacing="2" py="2">
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 3, xl: 4 }} spacing="2" p="2">
|
||||
{followers.map((event) => (
|
||||
<FollowerItem key={event.pubkey} event={event} />
|
||||
))}
|
||||
|
@ -16,7 +16,7 @@ export default function UserFollowingTab() {
|
||||
if (!contactsList) return <Spinner />;
|
||||
|
||||
return (
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2" py="2">
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2" p="2">
|
||||
{people.map(({ pubkey, relay }) => (
|
||||
<UserCard key={pubkey} pubkey={pubkey} relay={relay} />
|
||||
))}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { Divider, Flex, Heading, SimpleGrid } from "@chakra-ui/react";
|
||||
import { Flex, SimpleGrid } from "@chakra-ui/react";
|
||||
|
||||
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
@ -9,6 +9,7 @@ import IntersectionObserverProvider from "../../providers/intersection-observer"
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import { GOAL_KIND } from "../../helpers/nostr/goal";
|
||||
import GoalCard from "../goals/components/goal-card";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
export default function UserGoalsTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
@ -24,13 +25,13 @@ export default function UserGoalsTab() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex gap="2" pt="2" pb="10" px={["2", "2", 0]} direction="column">
|
||||
<VerticalPageLayout>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
{goals.map((goal) => (
|
||||
<GoalCard key={getEventUID(goal)} goal={goal} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import ListCard from "../lists/components/list-card";
|
||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import { Kind } from "nostr-tools";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
export default function UserListsTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
@ -28,7 +29,7 @@ export default function UserListsTab() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex gap="2" pt="2" pb="10" px={["2", "2", 0]} direction="column">
|
||||
<VerticalPageLayout>
|
||||
<Heading size="md" mt="2">
|
||||
Special lists
|
||||
</Heading>
|
||||
@ -66,7 +67,7 @@ export default function UserListsTab() {
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { memo, useMemo, useRef } from "react";
|
||||
import { Flex, Heading, SimpleGrid } from "@chakra-ui/react";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
|
||||
import { UserAvatarLink } from "../../components/user-avatar-link";
|
||||
import { UserLink } from "../../components/user-link";
|
||||
@ -10,7 +11,7 @@ import useSubject from "../../hooks/use-subject";
|
||||
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import { useNavigate, useOutletContext } from "react-router-dom";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
const User = memo(({ pubkey, listId }: { pubkey: string; listId: string }) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
@ -25,7 +26,6 @@ const User = memo(({ pubkey, listId }: { pubkey: string; listId: string }) => {
|
||||
});
|
||||
|
||||
export default function UserMutedByTab() {
|
||||
const navigate = useNavigate();
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
|
||||
const readRelays = useReadRelayUrls();
|
||||
@ -48,7 +48,7 @@ export default function UserMutedByTab() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex gap="2" direction="column" p="2">
|
||||
<VerticalPageLayout>
|
||||
<SimpleGrid spacing="2" columns={{ base: 1, md: 2, lg: 3, xl: 4 }}>
|
||||
{pubkeys.map(({ pubkey, listId }) => (
|
||||
<User key={pubkey} pubkey={pubkey} listId={listId} />
|
||||
@ -59,7 +59,7 @@ export default function UserMutedByTab() {
|
||||
Looks like no one has muted this user yet
|
||||
</Heading>
|
||||
)}
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ export default function UserNotesTab() {
|
||||
);
|
||||
|
||||
const header = (
|
||||
<Flex gap="2" px="2" alignItems="center">
|
||||
<Flex gap="2" alignItems="center">
|
||||
<Switch id="replies" mr="2" isChecked={showReplies} onChange={toggleReplies} size="sm">
|
||||
Replies
|
||||
</Switch>
|
||||
@ -51,5 +51,5 @@ export default function UserNotesTab() {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return <TimelinePage header={header} timeline={timeline} pt="2" pb="8" />;
|
||||
return <TimelinePage header={header} timeline={timeline} pt="2" pb="12" px="2" />;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import { UserLink } from "../../components/user-link";
|
||||
import { NoteMenu } from "../../components/note/note-menu";
|
||||
import { EmbedEventPointer } from "../../components/embed-event";
|
||||
import { embedEmoji } from "../../components/embed-types";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
const Reaction = ({ reaction: reaction }: { reaction: NostrEvent }) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
@ -57,13 +58,13 @@ export default function UserReactionsTab() {
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<TrustProvider trust>
|
||||
<Flex direction="column" gap="2" p="2" pb="8">
|
||||
<VerticalPageLayout>
|
||||
{likes.map((event) => (
|
||||
<Reaction reaction={event} />
|
||||
))}
|
||||
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</TrustProvider>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
|
@ -40,11 +40,14 @@ function Relay({ url, reviews }: { url: string; reviews: NostrEvent[] }) {
|
||||
<RelayJoinAction url={url} size="sm" />
|
||||
</Flex>
|
||||
<RelayMetadata url={url} />
|
||||
<Flex py="0" direction="column" gap="2">
|
||||
{reviews.length>0&&(
|
||||
|
||||
<Flex py="0" direction="column" gap="2">
|
||||
{reviews.map((event) => (
|
||||
<RelayReviewNote key={event.id} event={event} />
|
||||
))}
|
||||
))}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import TimelineActionAndStatus from "../../components/timeline-page/timeline-act
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function ReportEvent({ report }: { report: NostrEvent }) {
|
||||
const reportedEvent = report.tags.filter(isETag)[0]?.[1];
|
||||
@ -56,13 +57,13 @@ export default function UserReportsTab() {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex direction="column" gap="2" pr="2" pl="2">
|
||||
<VerticalPageLayout>
|
||||
{events.map((report) => (
|
||||
<ReportEvent key={report.id} report={report} />
|
||||
))}
|
||||
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import { STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import useParsedStreams from "../../hooks/use-parsed-streams";
|
||||
import StreamCard from "../streams/components/stream-card";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
export default function UserStreamsTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
@ -29,15 +30,15 @@ export default function UserStreamsTab() {
|
||||
const streams = useParsedStreams(events);
|
||||
|
||||
return (
|
||||
<Flex p="2" gap="2" overflow="hidden" direction="column">
|
||||
<IntersectionObserverProvider<string> callback={callback}>
|
||||
<VerticalPageLayout>
|
||||
<SimpleGrid columns={{ base: 1, md: 2, lg: 3, xl: 4 }} spacing="2">
|
||||
{streams.map((stream) => (
|
||||
<StreamCard key={stream.event.id} stream={stream} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import { embedNostrLinks, renderGenericUrl } from "../../components/embed-types"
|
||||
import Timestamp from "../../components/timestamp";
|
||||
import { EmbedEventNostrLink, EmbedEventPointer } from "../../components/embed-event";
|
||||
import { parseCoordinate } from "../../helpers/nostr/events";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
const Zap = ({ zapEvent }: { zapEvent: NostrEvent }) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
@ -112,7 +113,7 @@ const UserZapsTab = () => {
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex direction="column" gap="2" p="2" pb="8">
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<Select value={filter} onChange={(e) => setFilter(e.target.value)} maxW="md">
|
||||
<option value="both">Note & Profile Zaps</option>
|
||||
@ -136,7 +137,7 @@ const UserZapsTab = () => {
|
||||
))}
|
||||
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
</Flex>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user