chore:lint

This commit is contained in:
Bereket Engida
2024-10-30 09:20:44 +03:00
parent 5d1a6772e8
commit 5e0edd35c2
21 changed files with 872 additions and 772 deletions

View File

@@ -267,32 +267,129 @@ export const Icons = {
height="1.2em" height="1.2em"
viewBox="0 0 100 100" viewBox="0 0 100 100"
> >
<mask id="a" style={{maskType: "alpha"}} maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100"> <mask
<circle cx="50" cy="50" r="50" className="fill-foreground"/> id="a"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="100"
height="100"
>
<circle cx="50" cy="50" r="50" className="fill-foreground" />
</mask> </mask>
<g mask="url(#a)"> <g mask="url(#a)">
<circle cx="11" cy="119" r="52" className="fill-muted-foreground stroke-foreground" strokeWidth="4"/> <circle
<circle cx="10" cy="125" r="52" className="fill-muted-foreground stroke-foreground" strokeWidth="4"/> cx="11"
<circle cx="9" cy="131" r="52" className="fill-muted-foreground stroke-muted-foreground" strokeWidth="4"/> cy="119"
<circle cx="88" cy="119" r="52" className="fill-muted-foreground stroke-foreground" strokeWidth="4"/> r="52"
<path className="fill-foreground" d="M89 35h2v5h-2zM83 34l2 1-1 4h-2zM77 31l2 1-3 4-2-1zM73 27l1 1-3 4-1-2zM70 23l1 1-4 3-1-2zM68 18v2l-4 1-1-2zM68 11l1 2-5 1-1-2zM69 6v2h-5V6z"/> className="fill-muted-foreground stroke-foreground"
<circle cx="89" cy="125" r="52" className="fill-muted-foreground stroke-foreground" strokeWidth="4"/> strokeWidth="4"
<circle cx="90" cy="131" r="52" className="fill-muted-foreground stroke-muted-foreground" strokeWidth="4"/> />
<ellipse cx="49.5" cy="119" rx="41.5" ry="51" className="fill-muted-foreground"/> <circle
<path d="M34 38v-9c1 1 2 4 5 6l7 30-8 2c-1-23-2-23-4-29Z" className="fill-foreground stroke-muted-foreground"/> cx="10"
<path fillRule="evenodd" clipRule="evenodd" d="M95 123c0 31-20 57-45 57S5 154 5 123c0-27 14-50 33-56l12-2c25 0 45 26 45 58Zm-45 47c22 0 39-22 39-50S72 70 50 70s-39 22-39 50 17 50 39 50Z" className="fill-foreground"/> cy="125"
<path d="M34 29c-4-8-11-5-14-4 2 3 5 4 9 4h5Z" className="fill-foreground stroke-muted-foreground"/> r="52"
<path d="M25 38c-1 6 0 14 2 18 5-7 7-13 7-18v-9c-5 1-7 5-9 9Z" className="fill-muted-foreground"/> className="fill-muted-foreground stroke-foreground"
<path d="M34 29c-1 3-5 11-5 16m5-16c-5 1-7 5-9 9-1 6 0 14 2 18 5-7 7-13 7-18v-9Z" className="stroke-muted-foreground"/> strokeWidth="4"
<path d="M44 18c-10 1-11 7-10 11l4-3c5-4 6-7 6-8Z" className="fill-foreground stroke-muted-foreground"/> />
<path d="M34 29h7l18 4c-3-6-9-14-21-7l-4 3Z" className="fill-foreground"/> <circle
<path d="M34 29c4-2 12-5 18-1m-18 1h7l18 4c-3-6-9-14-21-7l-4 3Z" className="stroke-muted-foreground"/> cx="9"
<path d="M32 29a1189 1189 0 0 1-16 19c0-17 7-18 13-19h5a14 14 0 0 1-2 0Z" className="fill-foreground"/> cy="131"
<path d="M34 29c-5 1-7 5-9 9l-9 10c0-17 7-18 13-19h5Zm0 0c-5 2-11 3-14 10" className="stroke-muted-foreground"/> r="52"
<path d="M41 29c9 2 13 10 15 14a25 25 0 0 1-22-14h7Z" className="fill-foreground"/> className="fill-muted-foreground stroke-muted-foreground"
<path d="M34 29c3 1 11 5 15 9m-15-9h7c9 2 13 10 15 14a25 25 0 0 1-22-14Z" className="stroke-muted-foreground"/> strokeWidth="4"
<circle cx="91.5" cy="12.5" r="18.5" className="fill-foreground stroke-muted-foreground" strokeWidth="2"/> />
</g> <circle
cx="88"
cy="119"
r="52"
className="fill-muted-foreground stroke-foreground"
strokeWidth="4"
/>
<path
className="fill-foreground"
d="M89 35h2v5h-2zM83 34l2 1-1 4h-2zM77 31l2 1-3 4-2-1zM73 27l1 1-3 4-1-2zM70 23l1 1-4 3-1-2zM68 18v2l-4 1-1-2zM68 11l1 2-5 1-1-2zM69 6v2h-5V6z"
/>
<circle
cx="89"
cy="125"
r="52"
className="fill-muted-foreground stroke-foreground"
strokeWidth="4"
/>
<circle
cx="90"
cy="131"
r="52"
className="fill-muted-foreground stroke-muted-foreground"
strokeWidth="4"
/>
<ellipse
cx="49.5"
cy="119"
rx="41.5"
ry="51"
className="fill-muted-foreground"
/>
<path
d="M34 38v-9c1 1 2 4 5 6l7 30-8 2c-1-23-2-23-4-29Z"
className="fill-foreground stroke-muted-foreground"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M95 123c0 31-20 57-45 57S5 154 5 123c0-27 14-50 33-56l12-2c25 0 45 26 45 58Zm-45 47c22 0 39-22 39-50S72 70 50 70s-39 22-39 50 17 50 39 50Z"
className="fill-foreground"
/>
<path
d="M34 29c-4-8-11-5-14-4 2 3 5 4 9 4h5Z"
className="fill-foreground stroke-muted-foreground"
/>
<path
d="M25 38c-1 6 0 14 2 18 5-7 7-13 7-18v-9c-5 1-7 5-9 9Z"
className="fill-muted-foreground"
/>
<path
d="M34 29c-1 3-5 11-5 16m5-16c-5 1-7 5-9 9-1 6 0 14 2 18 5-7 7-13 7-18v-9Z"
className="stroke-muted-foreground"
/>
<path
d="M44 18c-10 1-11 7-10 11l4-3c5-4 6-7 6-8Z"
className="fill-foreground stroke-muted-foreground"
/>
<path
d="M34 29h7l18 4c-3-6-9-14-21-7l-4 3Z"
className="fill-foreground"
/>
<path
d="M34 29c4-2 12-5 18-1m-18 1h7l18 4c-3-6-9-14-21-7l-4 3Z"
className="stroke-muted-foreground"
/>
<path
d="M32 29a1189 1189 0 0 1-16 19c0-17 7-18 13-19h5a14 14 0 0 1-2 0Z"
className="fill-foreground"
/>
<path
d="M34 29c-5 1-7 5-9 9l-9 10c0-17 7-18 13-19h5Zm0 0c-5 2-11 3-14 10"
className="stroke-muted-foreground"
/>
<path
d="M41 29c9 2 13 10 15 14a25 25 0 0 1-22-14h7Z"
className="fill-foreground"
/>
<path
d="M34 29c3 1 11 5 15 9m-15-9h7c9 2 13 10 15 14a25 25 0 0 1-22-14Z"
className="stroke-muted-foreground"
/>
<circle
cx="91.5"
cy="12.5"
r="18.5"
className="fill-foreground stroke-muted-foreground"
strokeWidth="2"
/>
</g>
</svg> </svg>
) ),
}; };

View File

@@ -37,5 +37,5 @@ export const techStackIcons: TechStackIconType = {
tanstack: { tanstack: {
name: "TanStack Start", name: "TanStack Start",
icon: <Icons.tanstack className="w-10 h-10" />, icon: <Icons.tanstack className="w-10 h-10" />,
} },
}; };

View File

@@ -1,6 +1,6 @@
import { import {
createStartAPIHandler, createStartAPIHandler,
defaultAPIFileRouteHandler, defaultAPIFileRouteHandler,
} from "@tanstack/start/api"; } from "@tanstack/start/api";
export default createStartAPIHandler(defaultAPIFileRouteHandler); export default createStartAPIHandler(defaultAPIFileRouteHandler);

View File

@@ -1,50 +1,50 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar" import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "~/lib/utils" import { cn } from "~/lib/utils";
const Avatar = React.forwardRef< const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>, React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Root <AvatarPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className className,
)} )}
{...props} {...props}
/> />
)) ));
Avatar.displayName = AvatarPrimitive.Root.displayName Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef< const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>, React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Image <AvatarPrimitive.Image
ref={ref} ref={ref}
className={cn("aspect-square h-full w-full", className)} className={cn("aspect-square h-full w-full", className)}
{...props} {...props}
/> />
)) ));
AvatarImage.displayName = AvatarPrimitive.Image.displayName AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef< const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>, React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted", "flex h-full w-full items-center justify-center rounded-full bg-muted",
className className,
)} )}
{...props} {...props}
/> />
)) ));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback } export { Avatar, AvatarImage, AvatarFallback };

View File

@@ -1,122 +1,122 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog" import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react" import { X } from "lucide-react";
import { cn } from "~/lib/utils" import { cn } from "~/lib/utils";
const Dialog = DialogPrimitive.Root const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef< const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>, React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
ref={ref} ref={ref}
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef< const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
)) ));
DialogContent.displayName = DialogPrimitive.Content.displayName DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ const DialogHeader = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left", "flex flex-col space-y-1.5 text-center sm:text-left",
className className,
)} )}
{...props} {...props}
/> />
) );
DialogHeader.displayName = "DialogHeader" DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({ const DialogFooter = ({
className, className,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className className,
)} )}
{...props} {...props}
/> />
) );
DialogFooter.displayName = "DialogFooter" DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef< const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>, React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref} ref={ref}
className={cn( className={cn(
"text-lg font-semibold leading-none tracking-tight", "text-lg font-semibold leading-none tracking-tight",
className className,
)} )}
{...props} {...props}
/> />
)) ));
DialogTitle.displayName = DialogPrimitive.Title.displayName DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef< const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>, React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Description <DialogPrimitive.Description
ref={ref} ref={ref}
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
DialogDescription.displayName = DialogPrimitive.Description.displayName DialogDescription.displayName = DialogPrimitive.Description.displayName;
export { export {
Dialog, Dialog,
DialogPortal, DialogPortal,
DialogOverlay, DialogOverlay,
DialogClose, DialogClose,
DialogTrigger, DialogTrigger,
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogFooter, DialogFooter,
DialogTitle, DialogTitle,
DialogDescription, DialogDescription,
} };

View File

@@ -1,26 +1,26 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "~/lib/utils" import { cn } from "~/lib/utils";
const labelVariants = cva( const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
) );
const Label = React.forwardRef< const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants> VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<LabelPrimitive.Root <LabelPrimitive.Root
ref={ref} ref={ref}
className={cn(labelVariants(), className)} className={cn(labelVariants(), className)}
{...props} {...props}
/> />
)) ));
Label.displayName = LabelPrimitive.Root.displayName Label.displayName = LabelPrimitive.Root.displayName;
export { Label } export { Label };

View File

@@ -1,9 +1,12 @@
import { twoFactorClient } from "better-auth/plugins"; import { twoFactorClient } from "better-auth/plugins";
import { createAuthClient } from "better-auth/react"; import { createAuthClient } from "better-auth/react";
export const { useSession, signIn, signOut, signUp, twoFactor } = createAuthClient({ export const { useSession, signIn, signOut, signUp, twoFactor } =
baseURL: "http://localhost:3000", createAuthClient({
plugins: [twoFactorClient({ baseURL: "http://localhost:3000",
twoFactorPage: "/auth/two-factor", plugins: [
})] twoFactorClient({
}); twoFactorPage: "/auth/two-factor",
}),
],
});

View File

@@ -1,4 +1,4 @@
import { twoFactor } from 'better-auth/plugins'; import { twoFactor } from "better-auth/plugins";
import { betterAuth } from "better-auth"; import { betterAuth } from "better-auth";
import Database from "better-sqlite3"; import Database from "better-sqlite3";
@@ -19,5 +19,5 @@ export const auth = betterAuth({
clientSecret: process.env.GITHUB_CLIENT_SECRET!, clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}, },
}, },
plugins: [twoFactor()] plugins: [twoFactor()],
}); });

View File

@@ -1,4 +1,4 @@
import { X } from 'lucide-react-native'; import { X } from "lucide-react-native";
import { iconWithClassName } from './iconWithClassName'; import { iconWithClassName } from "./iconWithClassName";
iconWithClassName(X); iconWithClassName(X);
export { X }; export { X };

View File

@@ -1,14 +1,14 @@
import type { LucideIcon } from 'lucide-react-native'; import type { LucideIcon } from "lucide-react-native";
import { cssInterop } from 'nativewind'; import { cssInterop } from "nativewind";
export function iconWithClassName(icon: LucideIcon) { export function iconWithClassName(icon: LucideIcon) {
cssInterop(icon, { cssInterop(icon, {
className: { className: {
target: 'style', target: "style",
nativeStyleToProp: { nativeStyleToProp: {
color: true, color: true,
opacity: true, opacity: true,
}, },
}, },
}); });
} }

View File

@@ -2,5 +2,5 @@ import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }

View File

@@ -1,9 +1,9 @@
import { LoginForm } from "~/components/login-form" import { LoginForm } from "~/components/login-form";
export default function Page() { export default function Page() {
return ( return (
<div className="flex h-screen w-full items-center justify-center px-4"> <div className="flex h-screen w-full items-center justify-center px-4">
<LoginForm /> <LoginForm />
</div> </div>
) );
} }

View File

@@ -10,123 +10,123 @@
// Import Routes // Import Routes
import { Route as rootRoute } from './routes/__root' import { Route as rootRoute } from "./routes/__root";
import { Route as IndexImport } from './routes/index' import { Route as IndexImport } from "./routes/index";
import { Route as AuthTwoFactorImport } from './routes/auth/two-factor' import { Route as AuthTwoFactorImport } from "./routes/auth/two-factor";
import { Route as AuthSignupImport } from './routes/auth/signup' import { Route as AuthSignupImport } from "./routes/auth/signup";
import { Route as AuthSigninImport } from './routes/auth/signin' import { Route as AuthSigninImport } from "./routes/auth/signin";
// Create/Update Routes // Create/Update Routes
const IndexRoute = IndexImport.update({ const IndexRoute = IndexImport.update({
id: '/', id: "/",
path: '/', path: "/",
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any);
const AuthTwoFactorRoute = AuthTwoFactorImport.update({ const AuthTwoFactorRoute = AuthTwoFactorImport.update({
id: '/auth/two-factor', id: "/auth/two-factor",
path: '/auth/two-factor', path: "/auth/two-factor",
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any);
const AuthSignupRoute = AuthSignupImport.update({ const AuthSignupRoute = AuthSignupImport.update({
id: '/auth/signup', id: "/auth/signup",
path: '/auth/signup', path: "/auth/signup",
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any);
const AuthSigninRoute = AuthSigninImport.update({ const AuthSigninRoute = AuthSigninImport.update({
id: '/auth/signin', id: "/auth/signin",
path: '/auth/signin', path: "/auth/signin",
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any);
// Populate the FileRoutesByPath interface // Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' { declare module "@tanstack/react-router" {
interface FileRoutesByPath { interface FileRoutesByPath {
'/': { "/": {
id: '/' id: "/";
path: '/' path: "/";
fullPath: '/' fullPath: "/";
preLoaderRoute: typeof IndexImport preLoaderRoute: typeof IndexImport;
parentRoute: typeof rootRoute parentRoute: typeof rootRoute;
} };
'/auth/signin': { "/auth/signin": {
id: '/auth/signin' id: "/auth/signin";
path: '/auth/signin' path: "/auth/signin";
fullPath: '/auth/signin' fullPath: "/auth/signin";
preLoaderRoute: typeof AuthSigninImport preLoaderRoute: typeof AuthSigninImport;
parentRoute: typeof rootRoute parentRoute: typeof rootRoute;
} };
'/auth/signup': { "/auth/signup": {
id: '/auth/signup' id: "/auth/signup";
path: '/auth/signup' path: "/auth/signup";
fullPath: '/auth/signup' fullPath: "/auth/signup";
preLoaderRoute: typeof AuthSignupImport preLoaderRoute: typeof AuthSignupImport;
parentRoute: typeof rootRoute parentRoute: typeof rootRoute;
} };
'/auth/two-factor': { "/auth/two-factor": {
id: '/auth/two-factor' id: "/auth/two-factor";
path: '/auth/two-factor' path: "/auth/two-factor";
fullPath: '/auth/two-factor' fullPath: "/auth/two-factor";
preLoaderRoute: typeof AuthTwoFactorImport preLoaderRoute: typeof AuthTwoFactorImport;
parentRoute: typeof rootRoute parentRoute: typeof rootRoute;
} };
} }
} }
// Create and export the route tree // Create and export the route tree
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute "/": typeof IndexRoute;
'/auth/signin': typeof AuthSigninRoute "/auth/signin": typeof AuthSigninRoute;
'/auth/signup': typeof AuthSignupRoute "/auth/signup": typeof AuthSignupRoute;
'/auth/two-factor': typeof AuthTwoFactorRoute "/auth/two-factor": typeof AuthTwoFactorRoute;
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute "/": typeof IndexRoute;
'/auth/signin': typeof AuthSigninRoute "/auth/signin": typeof AuthSigninRoute;
'/auth/signup': typeof AuthSignupRoute "/auth/signup": typeof AuthSignupRoute;
'/auth/two-factor': typeof AuthTwoFactorRoute "/auth/two-factor": typeof AuthTwoFactorRoute;
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRoute __root__: typeof rootRoute;
'/': typeof IndexRoute "/": typeof IndexRoute;
'/auth/signin': typeof AuthSigninRoute "/auth/signin": typeof AuthSigninRoute;
'/auth/signup': typeof AuthSignupRoute "/auth/signup": typeof AuthSignupRoute;
'/auth/two-factor': typeof AuthTwoFactorRoute "/auth/two-factor": typeof AuthTwoFactorRoute;
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath;
fullPaths: '/' | '/auth/signin' | '/auth/signup' | '/auth/two-factor' fullPaths: "/" | "/auth/signin" | "/auth/signup" | "/auth/two-factor";
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo;
to: '/' | '/auth/signin' | '/auth/signup' | '/auth/two-factor' to: "/" | "/auth/signin" | "/auth/signup" | "/auth/two-factor";
id: '__root__' | '/' | '/auth/signin' | '/auth/signup' | '/auth/two-factor' id: "__root__" | "/" | "/auth/signin" | "/auth/signup" | "/auth/two-factor";
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById;
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute;
AuthSigninRoute: typeof AuthSigninRoute AuthSigninRoute: typeof AuthSigninRoute;
AuthSignupRoute: typeof AuthSignupRoute AuthSignupRoute: typeof AuthSignupRoute;
AuthTwoFactorRoute: typeof AuthTwoFactorRoute AuthTwoFactorRoute: typeof AuthTwoFactorRoute;
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
AuthSigninRoute: AuthSigninRoute, AuthSigninRoute: AuthSigninRoute,
AuthSignupRoute: AuthSignupRoute, AuthSignupRoute: AuthSignupRoute,
AuthTwoFactorRoute: AuthTwoFactorRoute, AuthTwoFactorRoute: AuthTwoFactorRoute,
} };
export const routeTree = rootRoute export const routeTree = rootRoute
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>() ._addFileTypes<FileRouteTypes>();
/* prettier-ignore-end */ /* prettier-ignore-end */

View File

@@ -44,7 +44,7 @@ function RootComponent() {
const [theme, setTheme] = useState<"light" | "dark">("light"); const [theme, setTheme] = useState<"light" | "dark">("light");
const { data, isPending, error } = useSession(); const { data, isPending, error } = useSession();
const { navigate } = useRouter(); const { navigate } = useRouter();
console.log() console.log();
useEffect(() => { useEffect(() => {
if (!data?.user) { if (!data?.user) {
@@ -61,7 +61,6 @@ function RootComponent() {
); );
}, [data, navigate]); }, [data, navigate]);
useEffect(() => { useEffect(() => {
const root = window.document.documentElement; const root = window.document.documentElement;
@@ -73,228 +72,228 @@ function RootComponent() {
return ( return (
<RootDocument> <RootDocument>
<> <>
<nav className="grid grid-cols-3 items-center w-full p-4"> <nav className="grid grid-cols-3 items-center w-full p-4">
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<svg <svg
width="60" width="60"
height="45" height="45"
viewBox="0 0 60 45" viewBox="0 0 60 45"
fill="none" fill="none"
className="w-5 h-5" className="w-5 h-5"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
>
<title>Better Auth</title>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 0H15V15H30V30H15V45H0V30V15V0ZM45 30V15H30V0H45H60V15V30V45H45H30V30H45Z"
className="fill-black dark:fill-white"
/>
</svg>
<p>BETTER-AUTH</p>
<p>x</p>
<svg
className="w-5 h-5"
xmlns="http://www.w3.org/2000/svg"
width="45"
height="45"
viewBox="0 0 100 100"
fill="none"
>
<title>TanStack Start</title>
<mask
id="a"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="100"
height="100"
> >
<title>Better Auth</title> <circle cx="50" cy="50" r="50" className="fill-foreground" />
</mask>
<g mask="url(#a)">
<circle
cx="11"
cy="119"
r="52"
className="fill-muted-foreground stroke-foreground"
strokeWidth="4"
/>
<circle
cx="10"
cy="125"
r="52"
className="fill-muted-foreground stroke-foreground"
strokeWidth="4"
/>
<circle
cx="9"
cy="131"
r="52"
className="fill-muted-foreground stroke-muted-foreground"
strokeWidth="4"
/>
<circle
cx="88"
cy="119"
r="52"
className="fill-muted-foreground stroke-foreground"
strokeWidth="4"
/>
<path
className="fill-foreground"
d="M89 35h2v5h-2zM83 34l2 1-1 4h-2zM77 31l2 1-3 4-2-1zM73 27l1 1-3 4-1-2zM70 23l1 1-4 3-1-2zM68 18v2l-4 1-1-2zM68 11l1 2-5 1-1-2zM69 6v2h-5V6z"
/>
<circle
cx="89"
cy="125"
r="52"
className="fill-muted-foreground stroke-foreground"
strokeWidth="4"
/>
<circle
cx="90"
cy="131"
r="52"
className="fill-muted-foreground stroke-muted-foreground"
strokeWidth="4"
/>
<ellipse
cx="49.5"
cy="119"
rx="41.5"
ry="51"
className="fill-muted-foreground"
/>
<path
d="M34 38v-9c1 1 2 4 5 6l7 30-8 2c-1-23-2-23-4-29Z"
className="fill-foreground stroke-muted-foreground"
/>
<path <path
fillRule="evenodd" fillRule="evenodd"
clipRule="evenodd" clipRule="evenodd"
d="M0 0H15V15H30V30H15V45H0V30V15V0ZM45 30V15H30V0H45H60V15V30V45H45H30V30H45Z" d="M95 123c0 31-20 57-45 57S5 154 5 123c0-27 14-50 33-56l12-2c25 0 45 26 45 58Zm-45 47c22 0 39-22 39-50S72 70 50 70s-39 22-39 50 17 50 39 50Z"
className="fill-black dark:fill-white" className="fill-foreground"
/> />
</svg> <path
<p>BETTER-AUTH</p> d="M34 29c-4-8-11-5-14-4 2 3 5 4 9 4h5Z"
<p>x</p> className="fill-foreground stroke-muted-foreground"
<svg />
className="w-5 h-5" <path
xmlns="http://www.w3.org/2000/svg" d="M25 38c-1 6 0 14 2 18 5-7 7-13 7-18v-9c-5 1-7 5-9 9Z"
width="45" className="fill-muted-foreground"
height="45" />
viewBox="0 0 100 100" <path
fill="none" d="M34 29c-1 3-5 11-5 16m5-16c-5 1-7 5-9 9-1 6 0 14 2 18 5-7 7-13 7-18v-9Z"
> className="stroke-muted-foreground"
<title>TanStack Start</title> />
<mask <path
id="a" d="M44 18c-10 1-11 7-10 11l4-3c5-4 6-7 6-8Z"
style={{ maskType: "alpha" }} className="fill-foreground stroke-muted-foreground"
maskUnits="userSpaceOnUse" />
x="0" <path
y="0" d="M34 29h7l18 4c-3-6-9-14-21-7l-4 3Z"
width="100" className="fill-foreground"
height="100" />
> <path
<circle cx="50" cy="50" r="50" className="fill-foreground" /> d="M34 29c4-2 12-5 18-1m-18 1h7l18 4c-3-6-9-14-21-7l-4 3Z"
</mask> className="stroke-muted-foreground"
<g mask="url(#a)"> />
<circle <path
cx="11" d="M32 29a1189 1189 0 0 1-16 19c0-17 7-18 13-19h5a14 14 0 0 1-2 0Z"
cy="119" className="fill-foreground"
r="52" />
className="fill-muted-foreground stroke-foreground" <path
strokeWidth="4" d="M34 29c-5 1-7 5-9 9l-9 10c0-17 7-18 13-19h5Zm0 0c-5 2-11 3-14 10"
/> className="stroke-muted-foreground"
<circle />
cx="10" <path
cy="125" d="M41 29c9 2 13 10 15 14a25 25 0 0 1-22-14h7Z"
r="52" className="fill-foreground"
className="fill-muted-foreground stroke-foreground" />
strokeWidth="4" <path
/> d="M34 29c3 1 11 5 15 9m-15-9h7c9 2 13 10 15 14a25 25 0 0 1-22-14Z"
<circle className="stroke-muted-foreground"
cx="9" />
cy="131" <circle
r="52" cx="91.5"
className="fill-muted-foreground stroke-muted-foreground" cy="12.5"
strokeWidth="4" r="18.5"
/> className="fill-foreground stroke-muted-foreground"
<circle strokeWidth="2"
cx="88" />
cy="119" </g>
r="52" </svg>
className="fill-muted-foreground stroke-foreground" <p>TANSTACK START.</p>
strokeWidth="4" </div>
/> <div className="flex items-center justify-center gap-4">
<path {data?.user ? (
className="fill-foreground" <p>Hello {data.user.name}</p>
d="M89 35h2v5h-2zM83 34l2 1-1 4h-2zM77 31l2 1-3 4-2-1zM73 27l1 1-3 4-1-2zM70 23l1 1-4 3-1-2zM68 18v2l-4 1-1-2zM68 11l1 2-5 1-1-2zM69 6v2h-5V6z" ) : (
/> <NavigationMenu>
<circle <NavigationMenuList>
cx="89" <NavigationMenuItem>
cy="125" <NavigationMenuLink asChild>
r="52" <Link
className="fill-muted-foreground stroke-foreground" to="/auth/signin"
strokeWidth="4" className={navigationMenuTriggerStyle()}
/> activeProps={{ className: "bg-accent/50" }}
<circle >
cx="90" Sign In
cy="131" </Link>
r="52" </NavigationMenuLink>
className="fill-muted-foreground stroke-muted-foreground" </NavigationMenuItem>
strokeWidth="4" <NavigationMenuItem>
/> <NavigationMenuLink asChild>
<ellipse <Link
cx="49.5" to="/auth/signup"
cy="119" className={navigationMenuTriggerStyle()}
rx="41.5" activeProps={{ className: "bg-accent/50" }}
ry="51" >
className="fill-muted-foreground" Sign Up
/> </Link>
<path </NavigationMenuLink>
d="M34 38v-9c1 1 2 4 5 6l7 30-8 2c-1-23-2-23-4-29Z" </NavigationMenuItem>
className="fill-foreground stroke-muted-foreground" </NavigationMenuList>
/> </NavigationMenu>
<path )}
fillRule="evenodd" </div>
clipRule="evenodd" <div className="flex items-center gap-4 justify-center">
d="M95 123c0 31-20 57-45 57S5 154 5 123c0-27 14-50 33-56l12-2c25 0 45 26 45 58Zm-45 47c22 0 39-22 39-50S72 70 50 70s-39 22-39 50 17 50 39 50Z" {data?.user && (
className="fill-foreground"
/>
<path
d="M34 29c-4-8-11-5-14-4 2 3 5 4 9 4h5Z"
className="fill-foreground stroke-muted-foreground"
/>
<path
d="M25 38c-1 6 0 14 2 18 5-7 7-13 7-18v-9c-5 1-7 5-9 9Z"
className="fill-muted-foreground"
/>
<path
d="M34 29c-1 3-5 11-5 16m5-16c-5 1-7 5-9 9-1 6 0 14 2 18 5-7 7-13 7-18v-9Z"
className="stroke-muted-foreground"
/>
<path
d="M44 18c-10 1-11 7-10 11l4-3c5-4 6-7 6-8Z"
className="fill-foreground stroke-muted-foreground"
/>
<path
d="M34 29h7l18 4c-3-6-9-14-21-7l-4 3Z"
className="fill-foreground"
/>
<path
d="M34 29c4-2 12-5 18-1m-18 1h7l18 4c-3-6-9-14-21-7l-4 3Z"
className="stroke-muted-foreground"
/>
<path
d="M32 29a1189 1189 0 0 1-16 19c0-17 7-18 13-19h5a14 14 0 0 1-2 0Z"
className="fill-foreground"
/>
<path
d="M34 29c-5 1-7 5-9 9l-9 10c0-17 7-18 13-19h5Zm0 0c-5 2-11 3-14 10"
className="stroke-muted-foreground"
/>
<path
d="M41 29c9 2 13 10 15 14a25 25 0 0 1-22-14h7Z"
className="fill-foreground"
/>
<path
d="M34 29c3 1 11 5 15 9m-15-9h7c9 2 13 10 15 14a25 25 0 0 1-22-14Z"
className="stroke-muted-foreground"
/>
<circle
cx="91.5"
cy="12.5"
r="18.5"
className="fill-foreground stroke-muted-foreground"
strokeWidth="2"
/>
</g>
</svg>
<p>TANSTACK START.</p>
</div>
<div className="flex items-center justify-center gap-4">
{data?.user ? (
<p>Hello {data.user.name}</p>
) : (
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuLink asChild>
<Link
to="/auth/signin"
className={navigationMenuTriggerStyle()}
activeProps={{ className: "bg-accent/50" }}
>
Sign In
</Link>
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink asChild>
<Link
to="/auth/signup"
className={navigationMenuTriggerStyle()}
activeProps={{ className: "bg-accent/50" }}
>
Sign Up
</Link>
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
)}
</div>
<div className="flex items-center gap-4 justify-center">
{data?.user && (
<Button
onClick={() =>
signOut(
{},
{
onError: (error) => {
console.warn(error);
toast.error(error.error.message);
},
onSuccess: () => {
toast.success("You have been signed out!");
},
},
)
}
variant="destructive"
>
<DoorOpen className="w-5 h-5" />
</Button>
)}
<Button <Button
onClick={() => setTheme(theme === "light" ? "dark" : "light")} onClick={() =>
signOut(
{},
{
onError: (error) => {
console.warn(error);
toast.error(error.error.message);
},
onSuccess: () => {
toast.success("You have been signed out!");
},
},
)
}
variant="destructive"
> >
{theme === "light" ? ( <DoorOpen className="w-5 h-5" />
<Moon onClick={() => setTheme("dark")} className="w-5 h-5" />
) : (
<Sun onClick={() => setTheme("light")} className="w-5 h-5" />
)}
</Button> </Button>
</div> )}
</nav> <Button
<Outlet /> onClick={() => setTheme(theme === "light" ? "dark" : "light")}
</> >
{theme === "light" ? (
<Moon onClick={() => setTheme("dark")} className="w-5 h-5" />
) : (
<Sun onClick={() => setTheme("light")} className="w-5 h-5" />
)}
</Button>
</div>
</nav>
<Outlet />
</>
<Toaster richColors position="bottom-center" /> <Toaster richColors position="bottom-center" />
</RootDocument> </RootDocument>
); );

View File

@@ -2,10 +2,10 @@ import { createAPIFileRoute } from "@tanstack/start/api";
import { auth } from "~/lib/auth"; import { auth } from "~/lib/auth";
export const Route = createAPIFileRoute("/api/auth/$")({ export const Route = createAPIFileRoute("/api/auth/$")({
GET: ({ request }) => { GET: ({ request }) => {
return auth.handler(request); return auth.handler(request);
}, },
POST: ({ request }) => { POST: ({ request }) => {
return auth.handler(request); return auth.handler(request);
}, },
}); });

View File

@@ -1,19 +1,25 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from "@tanstack/react-router";
import { AlertCircle, CheckCircle2 } from 'lucide-react' import { AlertCircle, CheckCircle2 } from "lucide-react";
import { useState } from 'react' import { useState } from "react";
import { Button } from '~/components/ui/button' import { Button } from "~/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '~/components/ui/card' import {
import { Input } from '~/components/ui/input' Card,
import { Label } from '~/components/ui/label' CardContent,
import { twoFactor } from '~/lib/auth-client' CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { twoFactor } from "~/lib/auth-client";
export const Route = createFileRoute('/auth/two-factor')({ export const Route = createFileRoute("/auth/two-factor")({
component: TwoFactor component: TwoFactor,
}) });
function TwoFactor() {
function TwoFactor(){ const [totpCode, setTotpCode] = useState("");
const [totpCode, setTotpCode] = useState("");
const [error, setError] = useState(""); const [error, setError] = useState("");
const [success, setSuccess] = useState(false); const [success, setSuccess] = useState(false);
@@ -36,8 +42,8 @@ function TwoFactor(){
} }
}); });
}; };
return ( return (
<main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]"> <main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
<Card className="w-[350px]"> <Card className="w-[350px]">
<CardHeader> <CardHeader>
<CardTitle>TOTP Verification</CardTitle> <CardTitle>TOTP Verification</CardTitle>
@@ -80,5 +86,5 @@ function TwoFactor(){
</CardContent> </CardContent>
</Card> </Card>
</main> </main>
) );
} }

View File

@@ -1,215 +1,214 @@
import { import {
Card, Card,
CardContent, CardContent,
CardDescription, CardDescription,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from '~/components/ui/card' } from "~/components/ui/card";
import { twoFactor, useSession } from '~/lib/auth-client' import { twoFactor, useSession } from "~/lib/auth-client";
import { UAParser } from 'ua-parser-js' import { UAParser } from "ua-parser-js";
import { Laptop, Loader2, Phone, ShieldCheck, ShieldOff } from 'lucide-react' import { Laptop, Loader2, Phone, ShieldCheck, ShieldOff } from "lucide-react";
import { useState } from 'react' import { useState } from "react";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogFooter, DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from '~/components/ui/dialog' } from "~/components/ui/dialog";
import { Button } from '~/components/ui/button' import { Button } from "~/components/ui/button";
import { Label } from '~/components/ui/label' import { Label } from "~/components/ui/label";
import { Input } from '~/components/ui/input' import { Input } from "~/components/ui/input";
import { toast } from 'sonner' import { toast } from "sonner";
import QRCode from 'react-qr-code' import QRCode from "react-qr-code";
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute('/')({ export const Route = createFileRoute("/")({
component: Home, component: Home,
}) });
function Home() { function Home() {
const { data } = useSession() const { data } = useSession();
const [twoFactorDialog, setTwoFactorDialog] = useState(false) const [twoFactorDialog, setTwoFactorDialog] = useState(false);
const [twoFaPassword, setTwoFaPassword] = useState('') const [twoFaPassword, setTwoFaPassword] = useState("");
const [isPendingTwoFa, setIsPendingTwoFa] = useState(false) const [isPendingTwoFa, setIsPendingTwoFa] = useState(false);
const [twoFactorVerifyURI, setTwoFactorVerifyURI] = useState<string>('') const [twoFactorVerifyURI, setTwoFactorVerifyURI] = useState<string>("");
return ( return (
<div className="container flex justify-center items-center min-h-[80vh]"> <div className="container flex justify-center items-center min-h-[80vh]">
<Card className="w-fit"> <Card className="w-fit">
{data?.user && ( {data?.user && (
<> <>
<CardHeader> <CardHeader>
<CardTitle>Welcome, {data.user.name}!</CardTitle> <CardTitle>Welcome, {data.user.name}!</CardTitle>
<CardDescription> <CardDescription>
You are signed in as {data.user.email}. You are signed in as {data.user.email}.
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="flex flex-col gap-2 justify-start"> <CardContent className="flex flex-col gap-2 justify-start">
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{new UAParser(data.session.userAgent).getDevice().type === {new UAParser(data.session.userAgent).getDevice().type ===
'mobile' ? ( "mobile" ? (
<Phone /> <Phone />
) : ( ) : (
<Laptop size={16} /> <Laptop size={16} />
)} )}
{new UAParser(data.session.userAgent).getOS().name},{' '} {new UAParser(data.session.userAgent).getOS().name},{" "}
{new UAParser(data.session.userAgent).getBrowser().name} {new UAParser(data.session.userAgent).getBrowser().name}
</div> </div>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex gap-2"> <div className="flex gap-2">
<Dialog <Dialog
open={twoFactorDialog} open={twoFactorDialog}
onOpenChange={setTwoFactorDialog} onOpenChange={setTwoFactorDialog}
> >
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button
variant={ variant={
data?.user.twoFactorEnabled data?.user.twoFactorEnabled
? 'destructive' ? "destructive"
: 'outline' : "outline"
} }
className="gap-2" className="gap-2"
> >
{data?.user.twoFactorEnabled ? ( {data?.user.twoFactorEnabled ? (
<ShieldOff size={16} /> <ShieldOff size={16} />
) : ( ) : (
<ShieldCheck size={16} /> <ShieldCheck size={16} />
)} )}
<span className="md:text-sm text-xs"> <span className="md:text-sm text-xs">
{data?.user.twoFactorEnabled {data?.user.twoFactorEnabled
? 'Disable 2FA' ? "Disable 2FA"
: 'Enable 2FA'} : "Enable 2FA"}
</span> </span>
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px] w-11/12"> <DialogContent className="sm:max-w-[425px] w-11/12">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{data?.user.twoFactorEnabled {data?.user.twoFactorEnabled
? 'Disable 2FA' ? "Disable 2FA"
: 'Enable 2FA'} : "Enable 2FA"}
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
{data?.user.twoFactorEnabled {data?.user.twoFactorEnabled
? 'Disable the second factor authentication from your account' ? "Disable the second factor authentication from your account"
: 'Enable 2FA to secure your account'} : "Enable 2FA to secure your account"}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{twoFactorVerifyURI ? ( {twoFactorVerifyURI ? (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
<QRCode value={twoFactorVerifyURI} /> <QRCode value={twoFactorVerifyURI} />
</div> </div>
<Label htmlFor="password"> <Label htmlFor="password">
Scan the QR code with your TOTP app Scan the QR code with your TOTP app
</Label> </Label>
<Input <Input
value={twoFaPassword} value={twoFaPassword}
onChange={(e) => setTwoFaPassword(e.target.value)} onChange={(e) => setTwoFaPassword(e.target.value)}
placeholder="Enter OTP" placeholder="Enter OTP"
/> />
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Label htmlFor="password">Password</Label> <Label htmlFor="password">Password</Label>
<Input <Input
id="password" id="password"
type="password" type="password"
placeholder="Password" placeholder="Password"
value={twoFaPassword} value={twoFaPassword}
onChange={(e) => setTwoFaPassword(e.target.value)} onChange={(e) => setTwoFaPassword(e.target.value)}
/> />
</div> </div>
)} )}
<DialogFooter> <DialogFooter>
<Button <Button
disabled={isPendingTwoFa} disabled={isPendingTwoFa}
onClick={async () => { onClick={async () => {
if ( if (
twoFaPassword.length < 8 && twoFaPassword.length < 8 &&
!twoFactorVerifyURI !twoFactorVerifyURI
) { ) {
toast.error( toast.error(
'Password must be at least 8 characters', "Password must be at least 8 characters",
) );
return return;
} }
setIsPendingTwoFa(true) setIsPendingTwoFa(true);
if (data?.user.twoFactorEnabled) { if (data?.user.twoFactorEnabled) {
const res = await twoFactor.disable({ const res = await twoFactor.disable({
//@ts-ignore //@ts-ignore
password: twoFaPassword, password: twoFaPassword,
fetchOptions: { fetchOptions: {
onError(context) { onError(context) {
toast.error(context.error.message) toast.error(context.error.message);
}, },
onSuccess() { onSuccess() {
toast('2FA disabled successfully') toast("2FA disabled successfully");
setTwoFactorDialog(false) setTwoFactorDialog(false);
}, },
}, },
}) });
} else { } else {
if (twoFactorVerifyURI) { if (twoFactorVerifyURI) {
await twoFactor.verifyTotp({ await twoFactor.verifyTotp({
code: twoFaPassword, code: twoFaPassword,
fetchOptions: { fetchOptions: {
onError(context) { onError(context) {
setIsPendingTwoFa(false) setIsPendingTwoFa(false);
setTwoFaPassword('') setTwoFaPassword("");
toast.error(context.error.message) toast.error(context.error.message);
}, },
onSuccess() { onSuccess() {
toast('2FA enabled successfully') toast("2FA enabled successfully");
setTwoFactorVerifyURI('') setTwoFactorVerifyURI("");
setIsPendingTwoFa(false) setIsPendingTwoFa(false);
setTwoFaPassword('') setTwoFaPassword("");
setTwoFactorDialog(false) setTwoFactorDialog(false);
}, },
}, },
}) });
return return;
} }
await twoFactor.enable({ await twoFactor.enable({
password: twoFaPassword, password: twoFaPassword,
fetchOptions: { fetchOptions: {
onError(context) { onError(context) {
toast.error(context.error.message) toast.error(context.error.message);
}, },
onSuccess(ctx) { onSuccess(ctx) {
setTwoFactorVerifyURI(ctx.data.totpURI) setTwoFactorVerifyURI(ctx.data.totpURI);
},
}, },
}, });
}) }
} setIsPendingTwoFa(false);
setIsPendingTwoFa(false) setTwoFaPassword("");
setTwoFaPassword('') }}
}} >
> {isPendingTwoFa ? (
{isPendingTwoFa ? ( <Loader2 size={15} className="animate-spin" />
<Loader2 size={15} className="animate-spin" /> ) : data?.user.twoFactorEnabled ? (
) : data?.user.twoFactorEnabled ? ( "Disable 2FA"
'Disable 2FA' ) : (
) : ( "Enable 2FA"
'Enable 2FA' )}
)} </Button>
</Button> </DialogFooter>
</DialogFooter> </DialogContent>
</DialogContent> </Dialog>
</Dialog> </div>
</div> </div>
</div> </CardContent>
</CardContent> </>
</> )}
)} </Card>
</Card> </div>
</div> );
)
} }

View File

@@ -1,16 +1,16 @@
{ {
"$schema": "https://ui.shadcn.com/schema.json", "$schema": "https://ui.shadcn.com/schema.json",
"style": "default", "style": "default",
"rsc": true, "rsc": true,
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.js", "config": "tailwind.config.js",
"css": "app/lib/style/global.css", "css": "app/lib/style/global.css",
"baseColor": "slate", "baseColor": "slate",
"cssVariables": true "cssVariables": true
}, },
"aliases": { "aliases": {
"components": "~/components", "components": "~/components",
"utils": "~/lib/utils" "utils": "~/lib/utils"
} }
} }

View File

@@ -1,6 +1,6 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
} };

View File

@@ -137,12 +137,10 @@ export const jwt = (options?: JwtOptions) => {
publicKey: JSON.stringify(publicWebKey), publicKey: JSON.stringify(publicWebKey),
privateKey: privateKeyEncryptionEnabled privateKey: privateKeyEncryptionEnabled
? JSON.stringify( ? JSON.stringify(
await symmetricEncrypt( await symmetricEncrypt({
{ key: ctx.context.options.secret!,
key: ctx.context.options.secret!, data: stringifiedPrivateWebKey,
data: stringifiedPrivateWebKey, }),
}
),
) )
: stringifiedPrivateWebKey, : stringifiedPrivateWebKey,
createdAt: new Date(), createdAt: new Date(),
@@ -152,12 +150,10 @@ export const jwt = (options?: JwtOptions) => {
} }
let privateWebKey = privateKeyEncryptionEnabled let privateWebKey = privateKeyEncryptionEnabled
? await symmetricDecrypt( ? await symmetricDecrypt({
{ key: ctx.context.options.secret!,
key: ctx.context.options.secret!, data: JSON.parse(key.privateKey),
data: JSON.parse(key.privateKey), })
}
)
: key.privateKey; : key.privateKey;
const privateKey = await importJWK(JSON.parse(privateWebKey)); const privateKey = await importJWK(JSON.parse(privateWebKey));