mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-30 19:06:47 -05:00
feat: remember me and many more imporves
This commit is contained in:
Binary file not shown.
@@ -15,15 +15,20 @@ import { authClient } from "@/lib/auth-client";
|
||||
import { useState } from "react";
|
||||
import { Key } from "lucide-react";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { toast } from "sonner";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export default function Page() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [rememberMe, setRememberMe] = useState(false);
|
||||
const router = useRouter()
|
||||
return (
|
||||
<div className="h-[50rem] w-full dark:bg-black bg-white dark:bg-grid-white/[0.2] bg-grid-black/[0.2] relative flex items-center justify-center">
|
||||
{/* Radial gradient for the container to give a faded look */}
|
||||
<div className="absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-black bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)]"></div>
|
||||
<Card className="mx-auto max-w-sm">
|
||||
<Card className="mx-auto max-w-sm z-50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Login</CardTitle>
|
||||
<CardDescription>
|
||||
@@ -63,12 +68,22 @@ export default function Page() {
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox onClick={() => {
|
||||
setRememberMe(!rememberMe)
|
||||
}} />
|
||||
<Label>Remember me</Label>
|
||||
</div>
|
||||
<Button type="submit" className="w-full" onClick={async () => {
|
||||
await authClient.signIn.credential({
|
||||
const res = await authClient.signIn.credential({
|
||||
email,
|
||||
password,
|
||||
callbackURL: "/"
|
||||
callbackURL: "/",
|
||||
dontRememberMe: !rememberMe
|
||||
})
|
||||
if (res.error) {
|
||||
toast.error(res.error.message)
|
||||
}
|
||||
}}>
|
||||
Login
|
||||
</Button>
|
||||
@@ -85,9 +100,14 @@ export default function Page() {
|
||||
Login with Github
|
||||
</Button>
|
||||
<Button variant="secondary" className="gap-2" onClick={async () => {
|
||||
await authClient.passkey.signIn({
|
||||
const res = await authClient.passkey.signIn({
|
||||
callbackURL: "/"
|
||||
})
|
||||
if (res?.error) {
|
||||
toast.error(res.error.message)
|
||||
} else {
|
||||
router.push("/")
|
||||
}
|
||||
}}>
|
||||
<Key size={16} />
|
||||
Login with Passkey
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { ThemeWrapper } from "@/components/theme-provider";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
@@ -16,10 +17,13 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<ThemeWrapper forcedTheme="dark" attribute="class">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</ThemeWrapper>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
<ThemeWrapper forcedTheme="dark" attribute="class">
|
||||
{children}
|
||||
</ThemeWrapper>
|
||||
<Toaster />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default async function TypewriterEffectSmoothDemo() {
|
||||
{/* Radial gradient for the container to give a faded look */}
|
||||
<div className="absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-black bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)]"></div>
|
||||
{
|
||||
session ? <UserCard user={session.user} /> : null
|
||||
session ? <UserCard session={session} /> : null
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@ export const SignOut = () => {
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await authClient.signOut({
|
||||
body: {
|
||||
callbackURL: "/"
|
||||
}
|
||||
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -12,6 +12,8 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
richColors
|
||||
closeButton
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "./ui/button";
|
||||
import { LogOut } from "lucide-react";
|
||||
import { Check, LogOut } from "lucide-react";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { useRouter } from "next/navigation";
|
||||
import AddPasskey from "./add-passkey";
|
||||
import { Session, User } from "@/lib/types";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function UserCard({
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string;
|
||||
email: string;
|
||||
image?: string;
|
||||
};
|
||||
export default function UserCard(props: {
|
||||
session: {
|
||||
user: User;
|
||||
session: Session
|
||||
} | null
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const session = authClient.useSession(props.session)
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -26,23 +26,42 @@ export default function UserCard({
|
||||
<CardContent className="grid gap-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<Avatar className="hidden h-9 w-9 sm:flex">
|
||||
<AvatarImage src={user.image || "#"} alt="Avatar" />
|
||||
<AvatarFallback>{user.name.charAt(0)}</AvatarFallback>
|
||||
<AvatarImage src={session?.user.image || "#"} alt="Avatar" />
|
||||
<AvatarFallback>{session?.user.name.charAt(0)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid gap-1">
|
||||
<p className="text-sm font-medium leading-none">{user.name}</p>
|
||||
<p className="text-sm text-muted-foreground">{user.email}</p>
|
||||
<p className="text-sm font-medium leading-none">{session?.user.name}</p>
|
||||
<p className="text-sm text-muted-foreground">{session?.user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-y py-4 flex items-center justify-between">
|
||||
<div className="border-y py-4 flex items-center justify-between gap-2">
|
||||
<AddPasskey />
|
||||
{
|
||||
session?.user.twoFactorEnabled ? <Button variant="secondary" className="gap-2" onClick={async () => {
|
||||
const res = await authClient.twoFactor.disable()
|
||||
if (res.error) {
|
||||
toast.error(res.error.message)
|
||||
}
|
||||
}}>
|
||||
Disable 2FA
|
||||
</Button> : <Button variant="outline" className="gap-2" onClick={async () => {
|
||||
const res = await authClient.twoFactor.enable()
|
||||
if (res.error) {
|
||||
toast.error(res.error.message)
|
||||
}
|
||||
}}>
|
||||
<p>
|
||||
Enable 2FA
|
||||
</p>
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button className="gap-2 z-10" variant="secondary">
|
||||
<LogOut size={16} />
|
||||
<span className="text-sm" onClick={async () => {
|
||||
const res = await authClient.signOut()
|
||||
await authClient.signOut()
|
||||
router.refresh()
|
||||
}}>
|
||||
Sign Out
|
||||
|
||||
5
dev/next-app/src/lib/types.ts
Normal file
5
dev/next-app/src/lib/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { InferSession, InferUser } from "better-auth/types";
|
||||
import type { auth } from "./auth";
|
||||
|
||||
export type User = InferUser<typeof auth>;
|
||||
export type Session = InferSession<typeof auth>;
|
||||
@@ -15,7 +15,11 @@ export async function middleware(request: NextRequest) {
|
||||
permission: {
|
||||
invitation: ["create"],
|
||||
},
|
||||
options: {
|
||||
headers: request.headers,
|
||||
},
|
||||
});
|
||||
console.log({ canInvite });
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user