better auth set up

This commit is contained in:
zmeyer44 2023-10-15 11:44:15 -04:00
parent 80b178306e
commit 36b76f943a
17 changed files with 272 additions and 91 deletions

View File

@ -111,7 +111,7 @@ export default function ProfilePage({
<div className="mx-auto max-w-[800px] space-y-6">
<div className="flex max-w-2xl flex-col gap-5">
{demo.map((e) => (
<SubscriptionCard {...e} />
<SubscriptionCard key={e.id} {...e} />
))}
</div>
<div className="">

View File

@ -4,7 +4,7 @@ export default function Keystone() {
return (
<div className="center hidden sm:flex">
<Link
href="/"
href="/app"
className="center fixed h-[var(--header-height)] w-[var(--sidebar-closed-width)] gap-x-3 border-r text-primary hover:text-primary/80 xl:w-[var(--sidebar-open-width)] xl:justify-start xl:pl-5"
>
<Logo className="h-[30px] w-[30px]" />

View File

@ -1,4 +1,5 @@
"use client";
import { useEffect } from "react";
import Link from "next/link";
import dynamic from "next/dynamic";
import useCurrentUser from "@/lib/hooks/useCurrentUser";
@ -17,18 +18,26 @@ import {
} 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";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
const LoginModal = dynamic(() => import("@/components/Modals/Login"), {
ssr: false,
});
export default function AuthActions() {
const modal = useModal();
const { currentUser, logout } = useCurrentUser();
const { currentUser, logout, attemptLogin } = useCurrentUser();
useEffect(() => {
attemptLogin();
}, []);
if (currentUser) {
return (
<>
@ -53,59 +62,77 @@ export default function AuthActions() {
export function Notifications({ user }: { user: NDKUser }) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="center relative h-8 w-8 rounded-full bg-muted text-foreground"
>
<RiNotification4Line className="h-[18px] w-[18px] text-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="z-header+ w-56" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
{user.profile?.displayName || user.profile?.name ? (
<>
<p className="text-sm font-medium leading-none">
{user.profile?.displayName ?? user.profile.name}
</p>
<p className="text-xs leading-none text-muted-foreground">
m@example.com
</p>
</>
) : (
<p className="text-sm font-medium leading-none">
{truncateText(user.npub)}
</p>
)}
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
Profile
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Billing
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Settings
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>New Team</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
Log out
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<TooltipProvider>
<Tooltip delayDuration={100}>
<TooltipTrigger>
<Button
variant="ghost"
size="icon"
className="center relative h-8 w-8 rounded-full bg-muted text-foreground"
>
<RiNotification4Line className="h-[18px] w-[18px] text-foreground" />
</Button>
</TooltipTrigger>
<TooltipContent align="center">
<p>Coming Soon</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
// return (
// <DropdownMenu>
// <DropdownMenuTrigger asChild>
// <Button
// variant="ghost"
// size="icon"
// className="center relative h-8 w-8 rounded-full bg-muted text-foreground"
// >
// <RiNotification4Line className="h-[18px] w-[18px] text-foreground" />
// </Button>
// </DropdownMenuTrigger>
// <DropdownMenuContent className="z-header+ w-56" align="end" forceMount>
// <DropdownMenuLabel className="font-normal">
// <div className="flex flex-col space-y-1">
// {user.profile?.displayName || user.profile?.name ? (
// <>
// <p className="text-sm font-medium leading-none">
// {user.profile?.displayName ?? user.profile.name}
// </p>
// <p className="text-xs leading-none text-muted-foreground">
// m@example.com
// </p>
// </>
// ) : (
// <p className="text-sm font-medium leading-none">
// {truncateText(user.npub)}
// </p>
// )}
// </div>
// </DropdownMenuLabel>
// <DropdownMenuSeparator />
// <DropdownMenuGroup>
// <DropdownMenuItem>
// Profile
// <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
// </DropdownMenuItem>
// <DropdownMenuItem>
// Billing
// <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
// </DropdownMenuItem>
// <DropdownMenuItem>
// Settings
// <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
// </DropdownMenuItem>
// <DropdownMenuItem>New Team</DropdownMenuItem>
// </DropdownMenuGroup>
// <DropdownMenuSeparator />
// <DropdownMenuItem>
// Log out
// <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// );
}
export function Relays() {
const { ndk } = useNDK();

View File

@ -48,7 +48,10 @@ export default function HorizontalCarousel() {
return (
<div className="scrollbar-thumb-rounded-full mr-auto flex min-w-0 max-w-full snap-x snap-mandatory overflow-x-auto pl-5 pr-[50vw] scrollbar-thin sm:pr-[200px]">
{cards.map((creator, index) => (
<div className={cn("snap-start pl-2 sm:pl-5", index === 0 && "pl-5")}>
<div
key={index}
className={cn("snap-start pl-2 sm:pl-5", index === 0 && "pl-5")}
>
<CreatorCard key={creator.displayName} {...creator} />
</div>
))}

View File

@ -86,7 +86,7 @@ export default function Modal({
</FocusTrap>
<motion.div
key="desktop-backdrop"
className="z-overlay fixed inset-0 bg-background bg-opacity-10 backdrop-blur"
className="z-overlay fixed inset-0 bg-background/40 backdrop-blur"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}

View File

@ -3,7 +3,6 @@ import { parse } from "node-html-parser";
import { z } from "zod";
import { validateUrl } from "@/lib/utils";
type MetaData = {
title: string;
description: string;
@ -81,6 +80,10 @@ async function handler(req: NextRequest) {
});
} catch (err) {
console.log("Error", err);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
}
}

View File

@ -0,0 +1,60 @@
import Link from "next/link";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ReactNode } from "react";
type OptionLink = {
href: string;
type: "link";
};
type OptionButton = {
onClick: () => void;
type: "button";
};
type Option = {
label: string;
} & (OptionLink | OptionButton);
type DropDownMenuProps = {
options: Option[];
children: ReactNode;
};
export default function DropDownMenu({ children, options }: DropDownMenuProps) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent className="z-header+ w-56" align="end" forceMount>
<DropdownMenuGroup>
{options.map((o) => {
if (o.type === "button") {
return (
<DropdownMenuItem key={o.label}>
<button
onClick={o.onClick}
className="flex w-full justify-between"
>
{o.label}
</button>
</DropdownMenuItem>
);
} else {
return (
<DropdownMenuItem key={o.label}>
<Link href={o.href} className="flex w-full justify-between">
{o.label}
</Link>
</DropdownMenuItem>
);
}
})}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -4,10 +4,34 @@ import { type Event } from "nostr-tools";
import { RenderText } from "../TextRendering";
import { getTagsValues } from "@/lib/nostr/utils";
import LinkCard from "@/components/LinkCard";
export default function Kind1({ content, tags }: Event) {
import { copyText } from "@/lib/utils";
import { nip19 } from "nostr-tools";
import { toast } from "sonner";
export default function Kind1(props: Event) {
const { content, pubkey, tags } = props;
const r = getTagsValues("r", tags);
const npub = nip19.npubEncode(pubkey);
return (
<Container>
<Container
pubkey={pubkey}
actionOptions={[
{
label: "View profile",
href: `/${npub}`,
type: "link",
},
{
label: "Copy raw data",
type: "button",
onClick: () => {
void copyText(JSON.stringify(props));
toast.success("Copied Text!");
},
},
]}
>
<CardDescription className="text-base text-foreground">
<RenderText text={content} />
</CardDescription>

View File

@ -5,13 +5,13 @@ import { getTagValues, getTagsValues } from "@/lib/nostr/utils";
import { type Event } from "nostr-tools";
import { removeDuplicates } from "@/lib/utils";
export default function Kind30023({ content, tags }: Event) {
export default function Kind30023({ content, pubkey, tags }: Event) {
const title = getTagValues("title", tags);
const summary = getTagValues("summary", tags);
const contentTags = removeDuplicates(getTagsValues("t", tags));
return (
<Container contentTags={contentTags}>
<Container pubkey={pubkey} contentTags={contentTags}>
<CardTitle className="mb-1.5 line-clamp-2 text-lg font-semibold">
{title}
</CardTitle>

View File

@ -2,9 +2,9 @@ import Container from "./components/Container";
import { CardTitle, CardDescription } from "@/components/ui/card";
import { type Event } from "nostr-tools";
export default function Kind3745({}: Event) {
export default function Kind3745({ pubkey }: Event) {
return (
<Container>
<Container pubkey={pubkey}>
<CardTitle className="mb-1.5 line-clamp-2 text-lg font-semibold">
The start of the Nostr revolution
</CardTitle>

View File

@ -9,23 +9,48 @@ import { ReactNode } from "react";
import ProfileHeader from "./ProfileHeader";
import Actions from "./Actions";
import Tags from "./Tags";
import { type Event } from "nostr-tools";
import DropDownMenu from "@/components/DropDownMenu";
type OptionLink = {
href: string;
type: "link";
};
type OptionButton = {
onClick: () => void;
type: "button";
};
type Option = {
label: string;
} & (OptionLink | OptionButton);
type CreatorCardProps = {
pubkey: string;
contentTags?: string[];
children: ReactNode;
actionOptions?: Option[];
};
export default function Container({ children, contentTags }: CreatorCardProps) {
export default function Container({
children,
contentTags,
pubkey,
actionOptions = [],
}: CreatorCardProps) {
return (
<Card className="relative overflow-hidden">
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-4">
<ProfileHeader />
<ProfileHeader pubkey={pubkey} />
<div className="-mr-1 flex items-center gap-x-1.5 text-xs text-muted-foreground">
{formatDate(new Date("10-5-23"), "MMM Do")}
<Button size={"sm"} variant={"ghost"} className="center h-6 w-6 p-0">
<RiMoreFill className="h-4 w-4" />
</Button>
<DropDownMenu options={actionOptions}>
<Button
size={"sm"}
variant={"ghost"}
className="center h-6 w-6 p-0"
>
<RiMoreFill className="h-4 w-4" />
</Button>
</DropDownMenu>
</div>
</CardHeader>
<CardContent className="px-4 pb-3">

View File

@ -1,25 +1,30 @@
import { Avatar, AvatarImage, AvatarFallback } from "@radix-ui/react-avatar";
import { HiCheckBadge } from "react-icons/hi2";
import Link from "next/link";
import useProfile from "@/lib/hooks/useProfile";
import { nip19 } from "nostr-tools";
import { getTwoLetters, getNameToShow } from "@/lib/utils";
type ProfileHeaderProps = {};
export default function ProfileHeader({}: ProfileHeaderProps) {
type ProfileHeaderProps = {
pubkey: string;
};
export default function ProfileHeader({ pubkey }: ProfileHeaderProps) {
const { profile } = useProfile(pubkey);
const npub = nip19.npubEncode(pubkey);
return (
<div className="center gap-x-3">
<Link href={`/${npub}`} className="center group gap-x-3">
<Avatar className="center h-8 w-8 overflow-hidden rounded-sm bg-muted">
<AvatarImage
src={
"https://images.unsplash.com/photo-1566492031773-4f4e44671857?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60"
}
alt="user"
/>
<AvatarFallback className="text-xs">SC</AvatarFallback>
<AvatarImage src={profile.image} alt={profile.displayName} />
<AvatarFallback className="text-xs">
{getTwoLetters({ npub, profile })}
</AvatarFallback>
</Avatar>
<div className="center 5 gap-1">
<span className="text-xs uppercase text-muted-foreground">
Derek Seivers
<span className="text-xs uppercase text-muted-foreground group-hover:underline">
{getNameToShow({ npub, profile })}
</span>
<HiCheckBadge className="h-4 w-4 text-primary" />
{!!profile.nip05 && <HiCheckBadge className="h-4 w-4 text-primary" />}
</div>
</div>
</Link>
);
}

View File

@ -2,9 +2,9 @@ import Container from "./components/Container";
import { CardTitle, CardDescription } from "@/components/ui/card";
import { type Event } from "nostr-tools";
export default function KindDefault({}: Event) {
export default function KindDefault({ pubkey }: Event) {
return (
<Container>
<Container pubkey={pubkey}>
<CardTitle className="mb-1.5 line-clamp-2 text-lg font-semibold">
The start of the Nostr revolution
</CardTitle>

View File

@ -10,7 +10,7 @@ import useCurrentUser from "@/lib/hooks/useCurrentUser";
export default function LoginModal() {
const { loginWithNip07 } = useNDK();
const { loginWithPubkey } = useCurrentUser();
const { loginWithPubkey, currentUser } = useCurrentUser();
const [isLoading, setIsLoading] = useState(false);
const modal = useModal();
@ -29,9 +29,7 @@ export default function LoginModal() {
if (!user) {
throw new Error("NO auth");
}
console.log("LOGIN", user);
await loginWithPubkey(nip19.decode(user.npub).data.toString());
// keys?.setKeys({
// privkey: "",
// pubkey: ,
@ -56,6 +54,11 @@ export default function LoginModal() {
getConnected(shouldReconnect);
}
}, []);
useEffect(() => {
if (currentUser) {
modal?.hide();
}
}, [currentUser]);
async function handleLogin() {
setIsLoading(true);

View File

@ -17,7 +17,10 @@ export default function Template({ title, children, className }: ModalProps) {
return (
<div
className={cn("relative w-full grow p-4 md:rounded-lg md:p-6", className)}
className={cn(
"relative w-full grow border bg-background p-4 shadow md:rounded-lg md:p-6",
className,
)}
>
<div className="mb-4 flex items-center justify-between">
<h1

View File

@ -4,6 +4,7 @@ import currentUserStore from "@/lib/stores/currentUser";
// import useEvents from "@/lib/hooks/useEvents";
import { UserSchema } from "@/types";
import { useNDK } from "@/app/_providers/ndk";
import { nip19 } from "nostr-tools";
export default function useCurrentUser() {
const {
@ -38,6 +39,21 @@ export default function useCurrentUser() {
// }
// });
async function attemptLogin() {
const shouldReconnect = localStorage.getItem("shouldReconnect");
if (!shouldReconnect || typeof window.nostr === "undefined") return;
const user = await loginWithNip07();
if (!user) {
throw new Error("NO auth");
}
console.log("LOGIN", user);
await loginWithPubkey(nip19.decode(user.npub).data.toString());
if (typeof window.webln !== "undefined") {
await window.webln.enable();
}
console.log("connected ");
}
function logout() {
localStorage.removeItem("shouldReconnect");
setCurrentUser(null);
@ -73,5 +89,6 @@ export default function useCurrentUser() {
logout,
updateUser: handleUpdateUser,
loginWithPubkey,
attemptLogin,
};
}

View File

@ -26,6 +26,17 @@ export function truncateText(text: string, size?: number) {
let length = size ?? 5;
return text.slice(0, length) + "..." + text.slice(-length);
}
export function getNameToShow(user: {
npub: string;
profile?: {
displayName?: string;
name?: string;
};
}) {
return (
user.profile?.displayName ?? user.profile?.name ?? truncateText(user.npub)
);
}
export function getTwoLetters(user: {
npub: string;
profile?: {