diff --git a/demo/nextjs/app/dashboard/user-card.tsx b/demo/nextjs/app/dashboard/user-card.tsx index 4e576887..a208bdcf 100644 --- a/demo/nextjs/app/dashboard/user-card.tsx +++ b/demo/nextjs/app/dashboard/user-card.tsx @@ -3,668 +3,746 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { PasswordInput } from "@/components/ui/password-input"; import { client, signOut, user, useSession } from "@/lib/auth-client"; import { Session } from "@/lib/auth-types"; import { MobileIcon } from "@radix-ui/react-icons"; -import { Edit, Fingerprint, Laptop, Loader2, LogOut, Plus, QrCode, ShieldCheck, ShieldOff, Trash, X } from "lucide-react"; +import { + Edit, + Fingerprint, + Laptop, + Loader2, + LogOut, + Plus, + QrCode, + ShieldCheck, + ShieldOff, + Trash, + X, +} from "lucide-react"; import Image from "next/image"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { UAParser } from "ua-parser-js"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; import QRCode from "react-qr-code"; import CopyButton from "@/components/ui/copy-button"; - export default function UserCard(props: { - session: Session | null; - activeSessions: Session["session"][] + session: Session | null; + activeSessions: Session["session"][]; }) { - const router = useRouter(); - const { data, isPending, error } = useSession(props.session); - const [ua, setUa] = useState() + const router = useRouter(); + const { data, isPending, error } = useSession(props.session); + const [ua, setUa] = useState(); - const session = data || props.session + const session = data || props.session; - useEffect(() => { - setUa(new UAParser(session?.session.userAgent)) - }, [session?.session.userAgent]) + useEffect(() => { + setUa(new UAParser(session?.session.userAgent)); + }, [session?.session.userAgent]); - const [isTerminating, setIsTerminating] = useState(); + const [isTerminating, setIsTerminating] = useState(); - const { data: qr } = useQuery({ - queryKey: ["two-factor-qr"], - queryFn: async () => { - const res = await client.twoFactor.getTotpUri() - if (res.error) { - throw res.error - } - return res.data - }, - enabled: !!session?.user.twoFactorSecret - }) + const { data: qr } = useQuery({ + queryKey: ["two-factor-qr"], + queryFn: async () => { + const res = await client.twoFactor.getTotpUri(); + if (res.error) { + throw res.error; + } + return res.data; + }, + enabled: !!session?.user.twoFactorSecret, + }); - const [isPendingTwoFa, setIsPendingTwoFa] = useState(false); - const [twoFaPassword, setTwoFaPassword] = useState(""); - const [twoFactorDialog, setTwoFactorDialog] = useState(false); - const [isSignOut, setIsSignOut] = useState(false); + const [isPendingTwoFa, setIsPendingTwoFa] = useState(false); + const [twoFaPassword, setTwoFaPassword] = useState(""); + const [twoFactorDialog, setTwoFactorDialog] = useState(false); + const [isSignOut, setIsSignOut] = useState(false); - return ( - - - User - - -
-
- - - {session?.user.name.charAt(0)} - -
-

- {session?.user.name} -

-

- {session?.user.email} -

-
-
- -
-
-

- Active Sessions -

- { - props.activeSessions.filter((session) => session.userAgent).map((session) => { - return ( -
-
- { - new UAParser(session.userAgent).getDevice().type === "mobile" ? : - } - {new UAParser(session.userAgent).getOS().name}, {new UAParser(session.userAgent).getBrowser().name} - -
-
- ) - }) - } -
-
-
-

- Passkeys -

-
- - -
-
-
-

- Two Factor -

-
- { - session?.user.twoFactorEnabled && ( - - - - - - - - Scan QR Code - - - Scan the QR code with your TOTP app - - -
- -
-
-

- Copy URI to clipboard -

- -
-
-
- ) - } - - - - - - - - { - session?.user.twoFactorEnabled ? "Disable 2FA" : "Enable 2FA" - } - - - { - session?.user.twoFactorEnabled ? "Disable the second factor authentication from your account" : "Enable 2FA to secure your account" - } - - -
- - setTwoFaPassword(e.target.value)} /> -
- + return ( + + + User + + +
+
+ + + {session?.user.name.charAt(0)} + +
+

+ {session?.user.name} +

+

{session?.user.email}

+
+
+ +
+
+

Active Sessions

+ {props.activeSessions + .filter((session) => session.userAgent) + .map((session) => { + return ( +
+
+ {new UAParser(session.userAgent).getDevice().type === + "mobile" ? ( + + ) : ( + + )} + {new UAParser(session.userAgent).getOS().name},{" "} + {new UAParser(session.userAgent).getBrowser().name} + - - - -
- -
-
-
-
- - - + + + ); + })} + +
+
+

Passkeys

+
+ + +
+
+
+

Two Factor

+
+ {session?.user.twoFactorEnabled && ( + + + + + + + Scan QR Code + + Scan the QR code with your TOTP app + + +
+ +
+
+

+ Copy URI to clipboard +

+ +
+
+
+ )} + + + - - - ); + + + + + + {session?.user.twoFactorEnabled + ? "Disable 2FA" + : "Enable 2FA"} + + + {session?.user.twoFactorEnabled + ? "Disable the second factor authentication from your account" + : "Enable 2FA to secure your account"} + + +
+ + setTwoFaPassword(e.target.value)} + /> +
+ + + +
+
+
+
+
+ + + + + +
+ ); } - - async function convertImageToBase64(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result as string); - reader.onerror = reject; - reader.readAsDataURL(file); - }); + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result as string); + reader.onerror = reject; + reader.readAsDataURL(file); + }); } - function ChangePassword() { - const [currentPassword, setCurrentPassword] = useState(""); - const [newPassword, setNewPassword] = useState(""); - const [confirmPassword, setConfirmPassword] = useState(""); - const [loading, setLoading] = useState(false); - const [open, setOpen] = useState(false); - const [signOutDevices, setSignOutDevices] = useState(false); - return ( - - - - - - - - Change Password - - - Change your password - - -
- - setCurrentPassword(e.target.value)} - autoComplete="new-password" - placeholder="Password" /> - - setNewPassword(e.target.value)} - autoComplete="new-password" - placeholder="New Password" /> - - setConfirmPassword(e.target.value)} - autoComplete="new-password" - placeholder="Confirm Password" /> -
- checked ? setSignOutDevices(true) : setSignOutDevices(false)} /> -

- Sign out from other devices -

-
-
- - - -
-
- ) + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [signOutDevices, setSignOutDevices] = useState(false); + return ( + + + + + + + Change Password + Change your password + +
+ + setCurrentPassword(e.target.value)} + autoComplete="new-password" + placeholder="Password" + /> + + setNewPassword(e.target.value)} + autoComplete="new-password" + placeholder="New Password" + /> + + setConfirmPassword(e.target.value)} + autoComplete="new-password" + placeholder="Confirm Password" + /> +
+ + checked ? setSignOutDevices(true) : setSignOutDevices(false) + } + /> +

Sign out from other devices

+
+
+ + + +
+
+ ); } - function EditUserDialog(props: { session: Session | null }) { - const [name, setName] = useState(); - const router = useRouter() - const [image, setImage] = useState(null); - const [imagePreview, setImagePreview] = useState(null); - const handleImageChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (file) { - setImage(file); - const reader = new FileReader(); - reader.onloadend = () => { - setImagePreview(reader.result as string); - }; - reader.readAsDataURL(file); - } - }; - const [open, setOpen] = useState(false); - const [isLoading, setIsLoading] = useState(false); - return ( - - - - - - - - Edit User - - - Edit user information - - -
- - { - setName(e.target.value) - }} - /> -
- -
- {imagePreview && ( -
- Profile preview -
- )} -
- - {imagePreview && { - setImage(null); - setImagePreview(null); - }} />} -
-
-
+ const [name, setName] = useState(); + const router = useRouter(); + const [image, setImage] = useState(null); + const [imagePreview, setImagePreview] = useState(null); + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + setImage(file); + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result as string); + }; + reader.readAsDataURL(file); + } + }; + const [open, setOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + return ( + + + + + + + Edit User + Edit user information + +
+ + { + setName(e.target.value); + }} + /> +
+ +
+ {imagePreview && ( +
+ Profile preview
- - - - -
- ) + )} +
+ + {imagePreview && ( + { + setImage(null); + setImagePreview(null); + }} + /> + )} +
+
+ + + + + +
+
+ ); } function AddPasskey() { - const [isOpen, setIsOpen] = useState(false); - const [passkeyName, setPasskeyName] = useState(""); - const queryClient = useQueryClient() - const [isLoading, setIsLoading] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [passkeyName, setPasskeyName] = useState(""); + const queryClient = useQueryClient(); + const [isLoading, setIsLoading] = useState(false); - const handleAddPasskey = async () => { - if (!passkeyName) { - toast.error("Passkey name is required"); - return; - } - setIsLoading(true); - const res = await client.passkey.addPasskey({ - name: passkeyName, - }); - if (res?.error) { - toast.error(res?.error.message); - } else { - setIsOpen(false); - toast.success("Passkey added successfully. You can now use it to login."); - } - setIsLoading(false); - }; - return ( - - - - - - - Add New Passkey - - Create a new passkey to securely access your account without a - password. - - -
- - setPasskeyName(e.target.value)} /> -
- - - -
-
- ); + const handleAddPasskey = async () => { + if (!passkeyName) { + toast.error("Passkey name is required"); + return; + } + setIsLoading(true); + const res = await client.passkey.addPasskey({ + name: passkeyName, + }); + if (res?.error) { + toast.error(res?.error.message); + } else { + setIsOpen(false); + toast.success("Passkey added successfully. You can now use it to login."); + } + setIsLoading(false); + }; + return ( + + + + + + + Add New Passkey + + Create a new passkey to securely access your account without a + password. + + +
+ + setPasskeyName(e.target.value)} + /> +
+ + + +
+
+ ); } - function ListPasskeys() { - const { data, error } = client.useListPasskeys() - const [isOpen, setIsOpen] = useState(false); - const [passkeyName, setPasskeyName] = useState(""); + const { data, error } = client.useListPasskeys(); + const [isOpen, setIsOpen] = useState(false); + const [passkeyName, setPasskeyName] = useState(""); - const handleAddPasskey = async () => { - if (!passkeyName) { - toast.error("Passkey name is required"); - return; - } - setIsLoading(true); - const res = await client.passkey.addPasskey({ - name: passkeyName, - }); - setIsLoading(false); - if (res?.error) { - toast.error(res?.error.message); - } else { - toast.success("Passkey added successfully. You can now use it to login."); - } - }; - const [isLoading, setIsLoading] = useState(false); - const [isDeletePasskey, setIsDeletePasskey] = useState(false); - return ( - - - - - - - Passkeys - - List of passkeys - - - {data?.length ? ( - - - - Name - - - - {data.map((passkey) => ( - - {passkey.name || "My Passkey"} - - - - - ))} - -
- ) : ( -

- No passkeys found -

- )} - { - !data?.length && ( -
- -
- - setPasskeyName(e.target.value)} placeholder="My Passkey" /> -
- -
- ) - } - - - -
-
- ) -} \ No newline at end of file + const handleAddPasskey = async () => { + if (!passkeyName) { + toast.error("Passkey name is required"); + return; + } + setIsLoading(true); + const res = await client.passkey.addPasskey({ + name: passkeyName, + }); + setIsLoading(false); + if (res?.error) { + toast.error(res?.error.message); + } else { + toast.success("Passkey added successfully. You can now use it to login."); + } + }; + const [isLoading, setIsLoading] = useState(false); + const [isDeletePasskey, setIsDeletePasskey] = useState(false); + return ( + + + + + + + Passkeys + List of passkeys + + {data?.length ? ( + + + + Name + + + + {data.map((passkey) => ( + + {passkey.name || "My Passkey"} + + + + + ))} + +
+ ) : ( +

No passkeys found

+ )} + {!data?.length && ( +
+
+ + setPasskeyName(e.target.value)} + placeholder="My Passkey" + /> +
+ +
+ )} + + + +
+
+ ); +} diff --git a/docs/components/sidebar-content.tsx b/docs/components/sidebar-content.tsx index 84632276..5156fe53 100644 --- a/docs/components/sidebar-content.tsx +++ b/docs/components/sidebar-content.tsx @@ -196,6 +196,40 @@ export const contents: Content[] = [ ), }, + { + title: "Session Management", + href: "/docs/concepts/session-management", + icon: () => ( + + + + ), + }, + { + title: "User Management", + href: "/docs/concepts/user-management", + icon: () => ( + + + + ), + }, ], Icon: () => (