From 80b178306e9c16c6c82faf5002f24015b5e0f661 Mon Sep 17 00:00:00 2001 From: zmeyer44 Date: Sun, 15 Oct 2023 10:58:44 -0400 Subject: [PATCH] link cards updates, profile page with feed, auth and current user stuff --- .../(profile)/[npub]/_components/Feed.tsx | 20 ++ app/(app)/(profile)/[npub]/page.tsx | 84 ++++--- app/(app)/_layout/Header.tsx | 11 +- app/(app)/_layout/components/AuthActions.tsx | 209 ++++++++++++++++++ .../_layout/components/Notifications.tsx | 59 ----- app/(app)/_layout/components/Relays.tsx | 41 ---- app/(app)/_layout/components/UserMenu.tsx | 63 ------ app/_providers/modal/index.tsx | 4 +- app/_providers/modal/leaflet.tsx | 14 +- app/globals.css | 3 + components/KindCard/1.tsx | 23 +- components/LinkCard/index.tsx | 38 ++-- components/Modals/FormModal.tsx | 176 +++++++++++++++ components/Modals/Login.tsx | 89 ++++++++ components/Modals/Template.tsx | 41 ++++ components/StatusIndicator/index.tsx | 19 ++ components/Tabs/index.tsx | 1 + components/TextRendering/EventMention.tsx | 15 ++ components/TextRendering/ProfileMention.tsx | 35 +++ components/TextRendering/index.tsx | 78 +++++++ components/ui/button.tsx | 50 +++-- containers/Feed/index.tsx | 32 +++ lib/hooks/useCurrentUser.ts | 77 +++++++ lib/hooks/useEvents.ts | 95 ++++++++ lib/hooks/useProfile.ts | 22 ++ lib/stores/currentUser.ts | 28 +++ lib/utils/index.ts | 34 ++- tailwind.config.ts | 3 - types/global.ts | 8 + types/index.ts | 42 ++++ 30 files changed, 1155 insertions(+), 259 deletions(-) create mode 100644 app/(app)/(profile)/[npub]/_components/Feed.tsx create mode 100644 app/(app)/_layout/components/AuthActions.tsx delete mode 100644 app/(app)/_layout/components/Notifications.tsx delete mode 100644 app/(app)/_layout/components/Relays.tsx delete mode 100644 app/(app)/_layout/components/UserMenu.tsx create mode 100644 components/Modals/FormModal.tsx create mode 100644 components/Modals/Login.tsx create mode 100644 components/Modals/Template.tsx create mode 100644 components/StatusIndicator/index.tsx create mode 100644 components/TextRendering/EventMention.tsx create mode 100644 components/TextRendering/ProfileMention.tsx create mode 100644 components/TextRendering/index.tsx create mode 100644 containers/Feed/index.tsx create mode 100644 lib/hooks/useCurrentUser.ts create mode 100644 lib/hooks/useEvents.ts create mode 100644 lib/hooks/useProfile.ts create mode 100644 lib/stores/currentUser.ts create mode 100644 types/index.ts diff --git a/app/(app)/(profile)/[npub]/_components/Feed.tsx b/app/(app)/(profile)/[npub]/_components/Feed.tsx new file mode 100644 index 0000000..65e71c5 --- /dev/null +++ b/app/(app)/(profile)/[npub]/_components/Feed.tsx @@ -0,0 +1,20 @@ +import Feed from "@/containers/Feed"; +import Spinner from "@/components/spinner"; +export default function ProfileFeed({ pubkey }: { pubkey: string }) { + return ( +
+ ( +
+ +

Fetching notes...

+
+ )} + /> +
+ ); +} diff --git a/app/(app)/(profile)/[npub]/page.tsx b/app/(app)/(profile)/[npub]/page.tsx index 60aa59a..3a5b550 100644 --- a/app/(app)/(profile)/[npub]/page.tsx +++ b/app/(app)/(profile)/[npub]/page.tsx @@ -5,6 +5,10 @@ import { Button } from "@/components/ui/button"; import SubscriptionCard from "@/components/SubscriptionCard"; import { HiCheckBadge } from "react-icons/hi2"; import Tabs from "@/components/Tabs"; +import useProfile from "@/lib/hooks/useProfile"; +import { getTwoLetters, truncateText } from "@/lib/utils"; +import ProfileFeed from "./_components/Feed"; +import { nip19 } from "nostr-tools"; export default function ProfilePage({ params: { npub }, @@ -14,6 +18,9 @@ export default function ProfilePage({ }; }) { const [activeTab, setActiveTab] = useState("feed"); + const pubkey = nip19.decode(npub).data.toString(); + const { profile } = useProfile(pubkey); + const demo = [ { id: "1", @@ -30,33 +37,44 @@ export default function ProfilePage({
-
+
- banner + {!!profile.banner && ( + banner + )}
- profile picture + {profile.image ? ( + profile picture + ) : ( +
+ {getTwoLetters({ + npub, + profile, + })} +
+ )}
@@ -107,6 +134,7 @@ export default function ProfilePage({ setActiveTab={(t) => setActiveTab(t.name)} />
+ {activeTab === "feed" ? : ""}
); diff --git a/app/(app)/_layout/Header.tsx b/app/(app)/_layout/Header.tsx index 1491828..83064fd 100644 --- a/app/(app)/_layout/Header.tsx +++ b/app/(app)/_layout/Header.tsx @@ -1,10 +1,7 @@ -import { UserMenu } from "./components/UserMenu"; import { Search } from "./components/Search"; -import { Notifications } from "./components/Notifications"; -import { MobileMenu } from "./components/MobileMenu"; -import { Relays } from "./components/Relays"; - +import AuthActions from "./components/AuthActions"; import Logo from "@/assets/Logo"; + export default function Header() { return (
@@ -21,9 +18,7 @@ export default function Header() {
- - - +
diff --git a/app/(app)/_layout/components/AuthActions.tsx b/app/(app)/_layout/components/AuthActions.tsx new file mode 100644 index 0000000..5c08cad --- /dev/null +++ b/app/(app)/_layout/components/AuthActions.tsx @@ -0,0 +1,209 @@ +"use client"; +import Link from "next/link"; +import dynamic from "next/dynamic"; +import useCurrentUser from "@/lib/hooks/useCurrentUser"; +import { useModal } from "@/app/_providers/modal/provider"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { RiNotification4Line } from "react-icons/ri"; +import { SiRelay } from "react-icons/si"; +import { RELAYS } from "@/constants"; +import StatusIndicator from "@/components/StatusIndicator"; +import { type NDKUser } from "@nostr-dev-kit/ndk"; +import { truncateText, getTwoLetters } from "@/lib/utils"; +import { useNDK } from "@/app/_providers/ndk"; +const LoginModal = dynamic(() => import("@/components/Modals/Login"), { + ssr: false, +}); + +export default function AuthActions() { + const modal = useModal(); + const { currentUser, logout } = useCurrentUser(); + if (currentUser) { + return ( + <> + + + + + ); + } + return ( + <> + + + ); +} + +export function Notifications({ user }: { user: NDKUser }) { + return ( + + + + + + +
+ {user.profile?.displayName || user.profile?.name ? ( + <> +

+ {user.profile?.displayName ?? user.profile.name} +

+

+ m@example.com +

+ + ) : ( +

+ {truncateText(user.npub)} +

+ )} +
+
+ + + + Profile + ⇧⌘P + + + Billing + ⌘B + + + Settings + ⌘S + + New Team + + + + Log out + ⇧⌘Q + +
+
+ ); +} +export function Relays() { + const { ndk } = useNDK(); + return ( + + + + + + {ndk?.explicitRelayUrls?.map((r) => ( + + + + {r} + + + ))} + + + + Manage Relays + ⇧⌘M + + + + ); +} +export function UserMenu({ + user, + logout, +}: { + user: NDKUser; + logout: () => void; +}) { + return ( + + + + + + +
+ {user.profile?.displayName || user.profile?.name ? ( + <> +

+ {user.profile?.displayName ?? user.profile.name} +

+

+ {user.profile?.nip05 ?? truncateText(user.npub)} +

+ + ) : ( +

+ {truncateText(user.npub)} +

+ )} +
+
+ + + + + Profile + ⇧⌘P + + + {/* + + Billing + ⌘B + + + + Settings + ⌘S + + New Team */} + + + + Log out + ⇧⌘Q + +
+
+ ); +} diff --git a/app/(app)/_layout/components/Notifications.tsx b/app/(app)/_layout/components/Notifications.tsx deleted file mode 100644 index 881b068..0000000 --- a/app/(app)/_layout/components/Notifications.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { RiNotification4Line } from "react-icons/ri"; - -export function Notifications() { - return ( - - - - - - -
-

shadcn

-

- m@example.com -

-
-
- - - - Profile - ⇧⌘P - - - Billing - ⌘B - - - Settings - ⌘S - - New Team - - - - Log out - ⇧⌘Q - -
-
- ); -} diff --git a/app/(app)/_layout/components/Relays.tsx b/app/(app)/_layout/components/Relays.tsx deleted file mode 100644 index 300d2fd..0000000 --- a/app/(app)/_layout/components/Relays.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { SiRelay } from "react-icons/si"; -import { RELAYS } from "@/constants"; -export function Relays() { - return ( - - - - - - {RELAYS.map((r) => ( - - {r} - - ))} - - - - Manage Relays - ⇧⌘M - - - - ); -} diff --git a/app/(app)/_layout/components/UserMenu.tsx b/app/(app)/_layout/components/UserMenu.tsx deleted file mode 100644 index a026497..0000000 --- a/app/(app)/_layout/components/UserMenu.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import Link from "next/link"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; - -export function UserMenu() { - return ( - - - - - - -
-

shadcn

-

- m@example.com -

-
-
- - - - - Profile - ⇧⌘P - - - - - Billing - ⌘B - - - - Settings - ⌘S - - New Team - - - - Log out - ⇧⌘Q - -
-
- ); -} diff --git a/app/_providers/modal/index.tsx b/app/_providers/modal/index.tsx index 511015b..8c74247 100644 --- a/app/_providers/modal/index.tsx +++ b/app/_providers/modal/index.tsx @@ -29,7 +29,7 @@ export default function Modal({ setShowModal(false); } }, - [setShowModal] + [setShowModal], ); useEffect(() => { @@ -86,7 +86,7 @@ export default function Modal({
-
-
+
+
-
+
{children}
- - The start of the Nostr revolution - - - This is the summary of this artilce. Let's hope that it is a good - article and that it will end up being worth reading. I don't want to - waste my time on some random other stuff. + + + {!!r.length && ( +
+ {r.map((url) => ( + + ))} +
+ )} ); } diff --git a/components/LinkCard/index.tsx b/components/LinkCard/index.tsx index a78d2b6..b9eb18f 100644 --- a/components/LinkCard/index.tsx +++ b/components/LinkCard/index.tsx @@ -2,17 +2,15 @@ import { useState, useEffect } from "react"; import Image from "next/image"; -import { Button } from "@/components/ui/button"; import { Card, - CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { cn } from "@/lib/utils"; import { fetchMetadata } from "@/lib/fetchers/metadata"; -import { HiOutlineCheckBadge } from "react-icons/hi2"; +import { AspectRatio } from "@/components/ui/aspect-ratio"; type LinkCardProps = { url: string; @@ -46,25 +44,29 @@ export default function LinkCard({ if (metadata) { return ( - + {metadata.image && ( -
- {metadata.title} +
+ + {metadata.title} +
)}
- - {metadata.title} - + + + {metadata.title} + + {metadata.description} diff --git a/components/Modals/FormModal.tsx b/components/Modals/FormModal.tsx new file mode 100644 index 0000000..4d4ad8c --- /dev/null +++ b/components/Modals/FormModal.tsx @@ -0,0 +1,176 @@ +"use client"; + +import { type ReactNode } from "react"; +import * as z from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + FieldErrors, + useForm, + FieldValues, + DefaultValues, + Path, +} from "react-hook-form"; +import { cn } from "@/lib/utils"; +import Template from "./Template"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; + +type FieldOptions = + | "toggle" + | "horizontal-tabs" + | "input" + | "text-area" + | "custom"; + +type DefaultFieldType = { + label: string; + slug: keyof z.infer> & string; + placeholder?: string; + description?: string; + lines?: number; + styles?: string; + value?: string | number | boolean; + custom?: ReactNode; + options?: { label: string; value: string; icon?: ReactNode }[]; +}; +type FieldType = DefaultFieldType & + ( + | { + type: "select"; + options: { label: string; value: string; icon?: ReactNode }[]; + } + | { + type: FieldOptions; + } + ); + +type FormModalProps = { + title: string; + fields: FieldType[]; + errors?: FieldErrors; + isSubmitting?: boolean; + cta: { + text: string; + }; + defaultValues?: Partial>>; + onSubmit: (props: z.infer>) => any; + formSchema: z.Schema; +}; + +export default function FormModal({ + title, + fields, + cta, + errors, + isSubmitting, + formSchema, + defaultValues, + onSubmit, +}: FormModalProps) { + type FormDataType = z.infer; + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: defaultValues as DefaultValues, + mode: "onChange", + }); + return ( +