{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "auth-components", "title": "JB Better Auth UI Components", "description": "A complete set of authentication components using better-auth with email OTP verification, social login, and profile management.", "dependencies": [ "better-auth", "zod", "react-hook-form", "@hookform/resolvers", "sonner", "lucide-react", "input-otp", "@prisma/adapter-pg", "@prisma/client", "pg", "dotenv", "resend", "@react-email/components" ], "devDependencies": [ "prisma", "tsx", "@types/pg" ], "registryDependencies": [ "button", "input", "form", "label", "card", "separator", "input-otp", "avatar", "sidebar", "tabs", "table", "checkbox", "select", "badge", "progress", "textarea" ], "files": [ { "path": "registry/default/ui/auth-forms/sign-in.tsx", "content": "'use client'\n\nimport { useState } from 'react'\nimport Link from 'next/link'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { signInSchema, type SignInInput } from '@/lib/auth-schemas'\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/components/ui/form'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport { Icons, AppLogoIcon } from '@/components/icons'\nimport { authClient } from '@/lib/auth-client'\nimport { useRouter } from 'next/navigation'\nimport { toast } from 'sonner'\nimport { Eye, EyeOff } from 'lucide-react'\n\nexport function SignIn() {\n const router = useRouter()\n const [isLoading, setIsLoading] = useState(false)\n const [showPassword, setShowPassword] = useState(false)\n\n const form = useForm({\n resolver: zodResolver(signInSchema),\n defaultValues: {\n email: '',\n password: '',\n },\n })\n\n async function onSubmit(data: SignInInput) {\n setIsLoading(true)\n await authClient.signIn.email({\n email: data.email,\n password: data.password,\n }, {\n onRequest: () => {\n // toast.info(\"Signing in...\")\n },\n onSuccess: () => {\n toast.success(\"Signed in successfully!\")\n router.push(\"/dashboard\")\n },\n onError: (ctx) => {\n form.setError('root', {\n message: ctx.error.message,\n })\n toast.error(ctx.error.message)\n setIsLoading(false)\n }\n })\n }\n\n async function handleSocialSignIn(provider: \"google\" | \"github\") {\n await authClient.signIn.social({\n provider,\n callbackURL: \"/dashboard\",\n }, {\n onSuccess: () => {\n toast.success(`Signed in with ${provider} successfully!`)\n },\n onError: (ctx) => {\n toast.error(ctx.error.message)\n }\n })\n }\n\n return (\n
\n
\n
\n \n \n \n

Sign in to Tailark

\n

Welcome back! Sign in to continue

\n\n
\n \n \n
\n\n
\n\n
\n \n (\n \n Email\n \n \n \n \n \n )}\n />\n\n (\n \n
\n Password\n \n
\n \n
\n \n setShowPassword(!showPassword)}\n >\n {showPassword ? (\n \n ) : (\n \n )}\n \n {showPassword ? 'Hide password' : 'Show password'}\n \n \n
\n
\n \n
\n )}\n />\n\n \n \n \n
\n\n
\n

\n Don't have an account?\n \n

\n
\n
\n
\n )\n}\n", "type": "registry:component", "target": "components/auth/sign-in.tsx" }, { "path": "registry/default/ui/auth-forms/sign-up.tsx", "content": "'use client'\n\nimport { useState } from 'react'\nimport Link from 'next/link'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { signUpSchema, type SignUpInput } from '@/lib/auth-schemas'\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/components/ui/form'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport { Icons, AppLogoIcon } from '@/components/icons'\nimport { authClient } from '@/lib/auth-client'\nimport { useRouter } from 'next/navigation'\nimport { toast } from 'sonner'\nimport { Eye, EyeOff } from 'lucide-react'\n\nexport function SignUp() {\n const router = useRouter()\n const [isLoading, setIsLoading] = useState(false)\n const [showPassword, setShowPassword] = useState(false)\n const [showConfirmPassword, setShowConfirmPassword] = useState(false)\n\n const form = useForm({\n resolver: zodResolver(signUpSchema),\n defaultValues: {\n name: '',\n email: '',\n password: '',\n confirmPassword: '',\n },\n })\n\n async function onSubmit(data: SignUpInput) {\n setIsLoading(true)\n await authClient.signUp.email({\n email: data.email,\n password: data.password,\n name: data.name,\n callbackURL: \"/dashboard\",\n }, {\n onSuccess: async () => {\n // Send OTP after successful signup\n await authClient.emailOtp.sendVerificationOtp({\n email: data.email,\n type: \"email-verification\"\n }, {\n onSuccess: () => {\n toast.success(\"Account created! Please check your email.\")\n sessionStorage.setItem(\"verify_email\", data.email)\n router.push(\"/auth/verify-email\")\n },\n onError: (ctx) => {\n toast.error(\"Account created but failed to send verification email. Please try resending.\")\n sessionStorage.setItem(\"verify_email\", data.email)\n router.push(\"/auth/verify-email\")\n setIsLoading(false)\n }\n })\n },\n onError: (ctx) => {\n console.log(\"SIGNUP ERROR CONTEXT:\", ctx)\n form.setError('root', {\n message: ctx.error.message || \"Signup failed\",\n })\n toast.error(ctx.error.message)\n setIsLoading(false)\n }\n })\n }\n\n async function handleSocialSignIn(provider: \"google\" | \"github\") {\n await authClient.signIn.social({\n provider,\n callbackURL: \"/dashboard\",\n }, {\n onSuccess: () => {\n toast.success(`Signed in with ${provider} successfully!`)\n },\n onError: (ctx) => {\n toast.error(ctx.error.message)\n }\n })\n }\n\n return (\n
\n
\n
\n \n \n \n

Create your account

\n

Welcome! Please fill in your details to get started

\n\n
\n \n \n
\n\n
\n\n
\n \n (\n \n Name\n \n \n \n \n \n )}\n />\n\n (\n \n Email\n \n \n \n \n \n )}\n />\n\n (\n \n Password\n \n
\n \n setShowPassword(!showPassword)}\n >\n {showPassword ? (\n \n ) : (\n \n )}\n \n {showPassword ? 'Hide password' : 'Show password'}\n \n \n
\n
\n \n
\n )}\n />\n\n (\n \n Confirm Password\n \n
\n \n setShowConfirmPassword(!showConfirmPassword)}\n >\n {showConfirmPassword ? (\n \n ) : (\n \n )}\n \n {showConfirmPassword ? 'Hide password' : 'Show password'}\n \n \n
\n
\n \n
\n )}\n />\n\n \n \n \n
\n\n
\n

\n Already have an account?\n \n

\n
\n
\n
\n )\n}\n", "type": "registry:component", "target": "components/auth/sign-up.tsx" }, { "path": "registry/default/ui/auth-forms/verify-email.tsx", "content": "'use client'\n\nimport { useState } from 'react'\nimport Link from 'next/link'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { verifyEmailSchema, type VerifyEmailInput } from '@/lib/auth-schemas'\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/components/ui/form'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport { AppLogoIcon } from '@/components/icons'\nimport {\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n} from \"@/components/ui/input-otp\"\nimport { authClient } from '@/lib/auth-client'\nimport { useRouter } from 'next/navigation'\nimport { toast } from 'sonner'\nimport { useEffect } from 'react'\n\nexport function VerifyEmail() {\n const router = useRouter()\n const [isLoading, setIsLoading] = useState(false)\n const [resendLoading, setResendLoading] = useState(false)\n const [countdown, setCountdown] = useState(60)\n const [canResend, setCanResend] = useState(false)\n const [email, setEmail] = useState('')\n\n useEffect(() => {\n const storedEmail = sessionStorage.getItem(\"verify_email\")\n if (storedEmail) setEmail(storedEmail)\n }, [])\n\n useEffect(() => {\n let timer: NodeJS.Timeout\n if (countdown > 0 && !canResend) {\n timer = setTimeout(() => setCountdown(countdown - 1), 1000)\n } else {\n setCanResend(true)\n }\n return () => clearTimeout(timer)\n }, [countdown, canResend])\n\n const form = useForm({\n resolver: zodResolver(verifyEmailSchema),\n defaultValues: {\n code: '',\n },\n })\n\n async function onSubmit(data: VerifyEmailInput) {\n setIsLoading(true)\n await authClient.emailOtp.verifyEmail({\n email: email, // Use state email\n otp: data.code,\n }, {\n onSuccess: () => {\n toast.success(\"Email verified successfully!\")\n sessionStorage.removeItem(\"verify_email\")\n router.push(\"/auth/sign-in\")\n },\n onError: (ctx) => {\n form.setError('root', {\n message: ctx.error.message,\n })\n toast.error(ctx.error.message)\n setIsLoading(false)\n }\n })\n }\n\n async function handleResendCode() {\n if (!email) {\n toast.error(\"No email found to resend code.\")\n return\n }\n setResendLoading(true)\n setCanResend(false)\n setCountdown(60)\n\n await authClient.emailOtp.sendVerificationOtp({\n email,\n type: \"email-verification\"\n }, {\n onSuccess: () => {\n toast.success(\"Code resent! Check your email.\")\n setResendLoading(false)\n },\n onError: (ctx) => {\n toast.error(ctx.error.message)\n setCanResend(true)\n setCountdown(0)\n setResendLoading(false)\n }\n })\n }\n\n return (\n
\n
\n
\n \n \n \n

Verify your email

\n

We sent a verification code to {email}

\n\n
\n \n (\n \n Verification Code\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n )}\n />\n\n \n \n \n
\n\n
\n

\n Didn't receive the code?\n {\n e.preventDefault()\n handleResendCode()\n }}\n >\n \n \n

\n
\n
\n
\n )\n}\n", "type": "registry:component", "target": "components/auth/verify-email.tsx" }, { "path": "registry/default/ui/auth-forms/forget-password.tsx", "content": "'use client'\n\nimport { useState } from 'react'\nimport Link from 'next/link'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { forgetPasswordSchema, type ForgetPasswordInput } from '@/lib/auth-schemas'\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/components/ui/form'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\n\nimport { AppLogoIcon } from '@/components/icons'\nimport { authClient } from '@/lib/auth-client'\nimport { useRouter } from 'next/navigation'\nimport { toast } from 'sonner'\n\nexport function ForgetPassword() {\n const router = useRouter()\n const [isLoading, setIsLoading] = useState(false)\n\n const form = useForm({\n resolver: zodResolver(forgetPasswordSchema),\n defaultValues: {\n email: '',\n },\n })\n\n async function onSubmit(data: ForgetPasswordInput) {\n setIsLoading(true)\n await authClient.emailOtp.sendVerificationOtp({\n email: data.email,\n type: \"forget-password\",\n }, {\n onSuccess: () => {\n toast.success(\"If an account exists, a reset code has been sent.\")\n sessionStorage.setItem(\"reset_email\", data.email)\n router.push(\"/auth/reset-password\")\n },\n onError: (ctx) => {\n form.setError('root', {\n message: ctx.error.message,\n })\n toast.error(ctx.error.message)\n setIsLoading(false)\n }\n })\n setIsLoading(false)\n }\n\n return (\n
\n
\n
\n \n \n \n

Reset password

\n

Enter your email address and we'll send you a link to reset your password

\n\n
\n \n (\n \n Email\n \n \n \n \n \n )}\n />\n\n \n \n \n
\n\n
\n

\n Remember your password?\n \n

\n
\n
\n
\n )\n}\n", "type": "registry:component", "target": "components/auth/forget-password.tsx" }, { "path": "registry/default/ui/auth-forms/reset-password.tsx", "content": "'use client'\n\nimport { useState, useEffect } from 'react'\nimport Link from 'next/link'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { z } from 'zod'\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/components/ui/form'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\nimport {\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n} from \"@/components/ui/input-otp\"\n\nimport { AppLogoIcon } from '@/components/icons'\nimport { authClient } from '@/lib/auth-client'\nimport { useRouter } from 'next/navigation'\nimport { toast } from 'sonner'\nimport { Eye, EyeOff } from 'lucide-react'\n\nconst resetPasswordOtpSchema = z.object({\n otp: z.string().min(6, \"Please enter the 6-digit code\"),\n password: z.string().min(8, \"Password must be at least 8 characters\"),\n confirmPassword: z.string(),\n}).refine((data) => data.password === data.confirmPassword, {\n message: \"Passwords don't match\",\n path: [\"confirmPassword\"],\n})\n\ntype ResetPasswordOtpInput = z.infer\n\nexport function ResetPassword() {\n const router = useRouter()\n const [isLoading, setIsLoading] = useState(false)\n const [showPassword, setShowPassword] = useState(false)\n const [showConfirmPassword, setShowConfirmPassword] = useState(false)\n const [email, setEmail] = useState(null)\n\n useEffect(() => {\n const storedEmail = sessionStorage.getItem(\"reset_email\")\n if (storedEmail) {\n setEmail(storedEmail)\n }\n }, [])\n\n const form = useForm({\n resolver: zodResolver(resetPasswordOtpSchema),\n defaultValues: {\n otp: '',\n password: '',\n confirmPassword: '',\n },\n })\n\n async function onSubmit(data: ResetPasswordOtpInput) {\n if (!email) {\n toast.error(\"Email not found. Please go back and request a new reset code.\")\n return\n }\n\n setIsLoading(true)\n await authClient.emailOtp.resetPassword({\n email: email,\n otp: data.otp,\n password: data.password,\n }, {\n onSuccess: () => {\n toast.success(\"Password reset successfully! Please log in.\")\n sessionStorage.removeItem(\"reset_email\")\n router.push(\"/auth/sign-in\")\n },\n onError: (ctx) => {\n form.setError('root', {\n message: ctx.error.message,\n })\n toast.error(ctx.error.message)\n setIsLoading(false)\n }\n })\n setIsLoading(false)\n }\n\n async function handleResendOtp() {\n if (!email) {\n toast.error(\"Email not found. Please go back and request a new reset code.\")\n return\n }\n\n await authClient.emailOtp.sendVerificationOtp({\n email: email,\n type: \"forget-password\",\n }, {\n onSuccess: () => {\n toast.success(\"A new reset code has been sent to your email.\")\n },\n onError: (ctx) => {\n toast.error(ctx.error.message)\n }\n })\n }\n\n if (!email) {\n return (\n
\n
\n
\n \n

Session Expired

\n

\n Please request a new password reset code.\n

\n \n
\n
\n
\n )\n }\n\n return (\n
\n
\n
\n \n \n \n

Reset your password

\n

\n Enter the 6-digit code sent to {email} and your new password.\n

\n\n
\n \n (\n \n Verification Code\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n )}\n />\n\n (\n \n New Password\n \n
\n \n setShowPassword(!showPassword)}\n >\n {showPassword ? (\n \n ) : (\n \n )}\n \n {showPassword ? 'Hide password' : 'Show password'}\n \n \n
\n
\n \n
\n )}\n />\n\n (\n \n Confirm Password\n \n
\n \n setShowConfirmPassword(!showConfirmPassword)}\n >\n {showConfirmPassword ? (\n \n ) : (\n \n )}\n \n {showConfirmPassword ? 'Hide password' : 'Show password'}\n \n \n
\n
\n \n
\n )}\n />\n\n \n \n \n\n
\n \n
\n
\n\n
\n

\n Remember your password?\n \n

\n
\n
\n
\n )\n}\n", "type": "registry:component", "target": "components/auth/reset-password.tsx" }, { "path": "registry/default/ui/auth-forms/reset-password-template.tsx", "content": "\r\nimport * as React from 'react';\r\nimport { Html, Head, Body, Container, Text, Heading, Section, Link } from '@react-email/components';\r\n\r\ninterface ResetPasswordTemplateProps {\r\n url: string;\r\n}\r\n\r\nexport const ResetPasswordTemplate: React.FC = ({ url }) => (\r\n \r\n \r\n \r\n \r\n Reset your password\r\n Click the link below to reset your password. If you didn't request this, please ignore this email.\r\n
\r\n \r\n Reset Password\r\n \r\n
\r\n \r\n Or copy and paste this URL into your browser:\r\n
\r\n {url}\r\n
\r\n
\r\n \r\n \r\n);\r\n\r\nconst main = {\r\n backgroundColor: '#ffffff',\r\n fontFamily: '-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif',\r\n};\r\n\r\nconst container = {\r\n margin: '0 auto',\r\n padding: '20px 0 48px',\r\n width: '560px',\r\n};\r\n\r\nconst h1 = {\r\n fontSize: '24px',\r\n fontWeight: 'bold',\r\n paddingTop: '32px',\r\n paddingBottom: '32px',\r\n};\r\n\r\nconst text = {\r\n fontSize: '16px',\r\n lineHeight: '26px',\r\n};\r\n\r\nconst btnContainer = {\r\n textAlign: 'center' as const,\r\n margin: '20px 0',\r\n};\r\n\r\nconst button = {\r\n backgroundColor: '#0070f3',\r\n borderRadius: '5px',\r\n color: '#fff',\r\n fontSize: '16px',\r\n fontWeight: 'bold',\r\n textDecoration: 'none',\r\n textAlign: 'center' as const,\r\n display: 'inline-block',\r\n width: '100%',\r\n padding: '12px 20px',\r\n};\r\n\r\nconst link = {\r\n color: '#0070f3',\r\n textDecoration: 'underline',\r\n};\r\n", "type": "registry:component", "target": "components/emails/reset-password-template.tsx" }, { "path": "registry/default/ui/auth-forms/change-password.tsx", "content": "'use client'\n\nimport { useState } from 'react'\nimport Link from 'next/link'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { passwordChangeSchema, type PasswordChangeInput } from '@/lib/auth-schemas'\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@/components/ui/form'\nimport { Input } from '@/components/ui/input'\nimport { Button } from '@/components/ui/button'\n\nimport { AppLogoIcon } from '@/components/icons'\nimport { authClient } from '@/lib/auth-client'\nimport { toast } from 'sonner'\nimport { Eye, EyeOff } from 'lucide-react'\n\nexport function ChangePassword() {\n const [isLoading, setIsLoading] = useState(false)\n const [showCurrentPassword, setShowCurrentPassword] = useState(false)\n const [showNewPassword, setShowNewPassword] = useState(false)\n const [showConfirmPassword, setShowConfirmPassword] = useState(false)\n\n const form = useForm({\n resolver: zodResolver(passwordChangeSchema),\n defaultValues: {\n currentPassword: '',\n newPassword: '',\n confirmPassword: '',\n },\n })\n\n async function onSubmit(data: PasswordChangeInput) {\n setIsLoading(true)\n await authClient.changePassword({\n currentPassword: data.currentPassword,\n newPassword: data.newPassword,\n revokeOtherSessions: true,\n }, {\n onSuccess: () => {\n toast.success(\"Password changed successfully.\")\n form.reset()\n setIsLoading(false)\n },\n onError: (ctx) => {\n form.setError('root', {\n message: ctx.error.message,\n })\n toast.error(ctx.error.message)\n setIsLoading(false)\n }\n })\n }\n\n return (\n
\n
\n
\n \n \n \n

Change password

\n

Update your password to keep your account secure

\n\n
\n \n (\n \n Current Password\n \n
\n \n setShowCurrentPassword(!showCurrentPassword)}\n >\n {showCurrentPassword ? (\n \n ) : (\n \n )}\n \n {showCurrentPassword ? 'Hide password' : 'Show password'}\n \n \n
\n
\n \n
\n )}\n />\n\n (\n \n New Password\n \n
\n \n setShowNewPassword(!showNewPassword)}\n >\n {showNewPassword ? (\n \n ) : (\n \n )}\n \n {showNewPassword ? 'Hide password' : 'Show password'}\n \n \n
\n
\n \n
\n )}\n />\n\n (\n \n Confirm Password\n \n
\n \n setShowConfirmPassword(!showConfirmPassword)}\n >\n {showConfirmPassword ? (\n \n ) : (\n \n )}\n \n {showConfirmPassword ? 'Hide password' : 'Show password'}\n \n \n
\n
\n \n
\n )}\n />\n\n \n \n \n
\n\n
\n

\n Go back to your profile\n \n

\n
\n
\n
\n )\n}\n", "type": "registry:component", "target": "components/auth/change-password.tsx" }, { "path": "registry/default/ui/auth-forms/logout-button.tsx", "content": "'use client'\n\nimport { useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Button } from '@/components/ui/button'\nimport { authClient } from '@/lib/auth-client'\nimport { toast } from 'sonner'\nimport { LogOut } from 'lucide-react'\n\ninterface LogoutButtonProps {\n variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'\n size?: 'default' | 'sm' | 'lg' | 'icon'\n showIcon?: boolean\n className?: string\n}\n\nexport function LogoutButton({\n variant = 'outline',\n size = 'default',\n showIcon = true,\n className\n}: LogoutButtonProps) {\n const router = useRouter()\n const [isLoading, setIsLoading] = useState(false)\n\n async function handleLogout() {\n setIsLoading(true)\n await authClient.signOut({\n fetchOptions: {\n onSuccess: () => {\n toast.success(\"Logged out successfully\")\n router.push(\"/auth/sign-in\")\n },\n onError: (ctx) => {\n toast.error(ctx.error.message || \"Failed to logout\")\n setIsLoading(false)\n }\n }\n })\n }\n\n return (\n \n {showIcon && }\n {isLoading ? 'Logging out...' : 'Logout'}\n \n )\n}\n", "type": "registry:component", "target": "components/auth/logout-button.tsx" }, { "path": "registry/default/ui/auth-forms/profile.tsx", "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { z } from \"zod\";\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\";\nimport { ArrowLeft, Upload, RefreshCw, Sun, Bell } from \"lucide-react\";\nimport { toast } from \"sonner\";\n\nconst personalInfoSchema = z.object({\n firstName: z.string().min(1, \"First name is required\"),\n lastName: z.string().min(1, \"Last name is required\"),\n});\n\nconst professionalInfoSchema = z.object({\n jobTitle: z.string().optional(),\n bio: z.string().optional(),\n});\n\ntype PersonalInfoInput = z.infer;\ntype ProfessionalInfoInput = z.infer;\n\ninterface ProfileProps {\n user?: {\n name?: string | null;\n email?: string | null;\n image?: string | null;\n };\n}\n\nexport function Profile({ user }: ProfileProps) {\n const [isLoadingPersonal, setIsLoadingPersonal] = useState(false);\n const [isLoadingProfessional, setIsLoadingProfessional] = useState(false);\n\n const nameParts = user?.name?.split(\" \") || [\"\", \"\"];\n const firstName = nameParts[0] || \"\";\n const lastName = nameParts.slice(1).join(\" \") || \"\";\n\n const personalForm = useForm({\n resolver: zodResolver(personalInfoSchema),\n defaultValues: {\n firstName,\n lastName,\n },\n });\n\n const professionalForm = useForm({\n resolver: zodResolver(professionalInfoSchema),\n defaultValues: {\n jobTitle: \"\",\n bio: \"\",\n },\n });\n\n const initials =\n `${firstName[0] || \"\"}${lastName[0] || \"\"}`.toUpperCase() || \"U\";\n\n async function onSubmitPersonal(data: PersonalInfoInput) {\n setIsLoadingPersonal(true);\n try {\n console.log(\"Update personal info:\", data);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n toast.success(\"Personal information updated successfully\");\n } catch (error) {\n console.error(\"Update personal info error:\", error);\n toast.error(\"Failed to update personal information\");\n } finally {\n setIsLoadingPersonal(false);\n }\n }\n\n async function onSubmitProfessional(data: ProfessionalInfoInput) {\n setIsLoadingProfessional(true);\n try {\n console.log(\"Update professional info:\", data);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n toast.success(\"Professional information updated successfully\");\n } catch (error) {\n console.error(\"Update professional info error:\", error);\n toast.error(\"Failed to update professional information\");\n } finally {\n setIsLoadingProfessional(false);\n }\n }\n\n return (\n
\n {/* Header */}\n
\n
\n \n \n \n
\n

Profile Information

\n

\n Update your personal information and profile picture\n

\n
\n
\n
\n \n \n \n
\n
\n\n {/* Main Content */}\n
\n
\n {/* Left Column */}\n
\n {/* Profile Picture Card */}\n \n \n Profile Picture\n \n Update your profile picture to be recognized by your team.\n \n \n \n \n \n \n {initials}\n \n \n
\n \n

\n Recommended: Square JPG, PNG, or GIF, at least 1000x1000\n pixels.\n

\n
\n
\n
\n\n {/* Account Settings Card */}\n \n \n Account Settings\n \n Manage your account credentials and verification.\n \n \n \n
\n \n \n

\n Email address cannot be changed directly. Please contact\n support if you need to update it.\n

\n
\n
\n \n \n \n
\n
\n
\n
\n\n {/* Right Column */}\n
\n {/* Personal Information Card */}\n \n \n Personal Information\n \n Update your personal details here.\n \n \n \n
\n \n
\n (\n \n First Name\n \n \n \n \n \n )}\n />\n (\n \n Last Name\n \n \n \n \n \n )}\n />\n
\n
\n \n {isLoadingPersonal ? \"Saving...\" : \"Save Personal Info\"}\n \n
\n \n \n
\n
\n\n {/* Professional Information Card */}\n \n \n \n Professional Information\n \n \n Share your professional background and role.\n \n \n \n
\n \n (\n \n Job Title\n \n \n \n \n \n )}\n />\n (\n \n Bio\n \n \n \n \n \n )}\n />\n
\n \n {isLoadingProfessional\n ? \"Saving...\"\n : \"Save Professional Info\"}\n \n
\n \n \n
\n
\n
\n
\n
\n
\n );\n}\n", "type": "registry:component", "target": "components/auth/profile.tsx" }, { "path": "registry/default/ui/auth-forms/icons.tsx", "content": "import React from 'react'\nimport { ChevronLeft, Github, Loader2, LucideCrop as LucideProps } from 'lucide-react'\n\nexport const Icons = {\n chevronLeft: ChevronLeft,\n google: GoogleIcon,\n gitHub: GithubIcon,\n spinner: Loader2,\n}\n\nexport function AppLogoIcon(props: React.SVGProps) {\n return (\n \n \n \n )\n}\n\nexport function GoogleIcon(props: React.SVGProps) {\n return (\n \n \n \n \n \n \n )\n}\n\nexport function GithubIcon(props: React.SVGProps) {\n return (\n \n \n \n )\n}\n", "type": "registry:component", "target": "components/icons.tsx" }, { "path": "registry/default/ui/auth-forms/index.ts", "content": "export { SignIn } from './sign-in'\nexport { SignUp } from './sign-up'\nexport { ForgetPassword } from './forget-password'\nexport { ResetPassword } from './reset-password'\nexport { VerifyEmail } from './verify-email'\nexport { Profile } from './profile'\nexport { ChangePassword } from './change-password'\nexport { LogoutButton } from './logout-button'\n", "type": "registry:component", "target": "components/auth/index.ts" }, { "path": "registry/default/ui/emails/otp-template.tsx", "content": "import * as React from \"react\";\nimport {\n Html,\n Head,\n Body,\n Container,\n Text,\n Heading,\n Section,\n} from \"@react-email/components\";\n\ninterface OTPTemplateProps {\n otp: string;\n}\n\nexport const OTPTemplate: React.FC = ({ otp }) => (\n \n \n \n \n Verification Code\n Your verification code is:\n
\n {otp}\n
\n It will expire in 10 minutes.\n
\n \n \n);\n\nconst main = {\n backgroundColor: \"#ffffff\",\n fontFamily:\n '-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif',\n};\n\nconst container = {\n margin: \"0 auto\",\n padding: \"20px 0 48px\",\n width: \"560px\",\n};\n\nconst h1 = {\n fontSize: \"24px\",\n fontWeight: \"bold\",\n paddingTop: \"32px\",\n paddingBottom: \"32px\",\n};\n\nconst text = {\n fontSize: \"16px\",\n lineHeight: \"26px\",\n};\n\nconst codeBox = {\n background: \"#f4f4f4\",\n borderRadius: \"4px\",\n margin: \"16px 0\",\n padding: \"16px\",\n textAlign: \"center\" as const,\n};\n\nconst code = {\n fontSize: \"24px\",\n fontWeight: \"bold\",\n letterSpacing: \"4px\",\n margin: \"0\",\n};\n", "type": "registry:component", "target": "components/emails/otp-template.tsx" }, { "path": "registry/default/lib/auth-client.ts", "content": "import { createAuthClient } from \"better-auth/react\";\r\nimport { emailOTPClient } from \"better-auth/client/plugins\";\r\n\r\nexport const authClient = createAuthClient({\r\n baseURL: process.env.BETTER_AUTH_URL, // e.g. http://localhost:3000\r\n plugins: [\r\n emailOTPClient(),\r\n ],\r\n});\r\n", "type": "registry:lib", "target": "lib/auth-client.ts" }, { "path": "registry/default/lib/auth-schemas.ts", "content": "import { z } from 'zod'\n\nexport const signUpSchema = z.object({\n name: z.string().min(2, 'Name must be at least 2 characters'),\n email: z.string().email('Please enter a valid email address'),\n password: z.string()\n .min(8, 'Password must be at least 8 characters')\n .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')\n .regex(/[0-9]/, 'Password must contain at least one number'),\n confirmPassword: z.string(),\n}).refine((data) => data.password === data.confirmPassword, {\n message: \"Passwords don't match\",\n path: [\"confirmPassword\"],\n})\n\nexport const signInSchema = z.object({\n email: z.string().email('Please enter a valid email address'),\n password: z.string().min(1, 'Password is required'),\n})\n\nexport const forgetPasswordSchema = z.object({\n email: z.string().email('Please enter a valid email address'),\n})\n\nexport const resetPasswordSchema = z.object({\n password: z.string()\n .min(8, 'Password must be at least 8 characters')\n .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')\n .regex(/[0-9]/, 'Password must contain at least one number'),\n confirmPassword: z.string(),\n}).refine((data) => data.password === data.confirmPassword, {\n message: \"Passwords don't match\",\n path: [\"confirmPassword\"],\n})\n\nexport const verifyEmailSchema = z.object({\n code: z.string().length(6, 'Code must be 6 digits'),\n})\n\nexport const profileSchema = z.object({\n firstName: z.string().min(2, 'First name must be at least 2 characters'),\n lastName: z.string().min(2, 'Last name must be at least 2 characters'),\n email: z.string().email('Please enter a valid email address'),\n phone: z.string().optional(),\n imageUrl: z.string().url().optional().or(z.literal('')),\n})\n\nexport const passwordChangeSchema = z.object({\n currentPassword: z.string().min(1, 'Current password is required'),\n newPassword: z.string()\n .min(8, 'Password must be at least 8 characters')\n .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')\n .regex(/[0-9]/, 'Password must contain at least one number'),\n confirmPassword: z.string(),\n}).refine((data) => data.newPassword === data.confirmPassword, {\n message: \"Passwords don't match\",\n path: [\"confirmPassword\"],\n}).refine((data) => data.currentPassword !== data.newPassword, {\n message: \"New password must be different from current password\",\n path: [\"newPassword\"],\n})\n\nexport type SignUpInput = z.infer\nexport type SignInInput = z.infer\nexport type ForgetPasswordInput = z.infer\nexport type ResetPasswordInput = z.infer\nexport type VerifyEmailInput = z.infer\nexport type ProfileInput = z.infer\nexport type PasswordChangeInput = z.infer\n", "type": "registry:lib", "target": "lib/auth-schemas.ts" }, { "path": "registry/default/lib/prisma.ts", "content": "import { PrismaPg } from \"@prisma/adapter-pg\";\nimport { PrismaClient } from \"./generated/prisma/client\";\n\nconst globalForPrisma = global as unknown as {\n prisma: PrismaClient;\n};\n\nconst adapter = new PrismaPg({\n connectionString: process.env.DATABASE_URL,\n});\n\nconst prisma =\n globalForPrisma.prisma ||\n new PrismaClient({\n adapter,\n });\n\nif (process.env.NODE_ENV !== \"production\") globalForPrisma.prisma = prisma;\n\nexport default prisma;\n", "type": "registry:lib", "target": "lib/prisma.ts" }, { "path": "registry/default/lib/auth.ts", "content": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport prisma from \"./prisma\";\nimport { emailOTP } from \"better-auth/plugins\";\nimport { sendVerificationEmail, sendResetPasswordEmail } from \"./email\";\n\nexport const auth = betterAuth({\n database: prismaAdapter(prisma, {\n provider: \"postgresql\",\n }),\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: true,\n async sendResetPassword(data, request) {\n try {\n await sendResetPasswordEmail(data.user.email, data.url);\n } catch (error) {\n console.error(\"Error sending reset password email:\", error);\n }\n },\n },\n socialProviders: {\n google: {\n clientId: process.env.GOOGLE_CLIENT_ID || \"\",\n clientSecret: process.env.GOOGLE_CLIENT_SECRET || \"\",\n },\n github: {\n clientId: process.env.GITHUB_CLIENT_ID || \"\",\n clientSecret: process.env.GITHUB_CLIENT_SECRET || \"\",\n },\n },\n plugins: [\n emailOTP({\n async sendVerificationOTP({ email, otp, type }) {\n try {\n await sendVerificationEmail(email, otp);\n } catch (e) {\n console.error(\"Error in sendVerificationOTP plugin wrapper:\", e);\n // Fallback logging for development\n console.log(`[DEV FALLBACK] OTP for ${email}: ${otp}`);\n }\n },\n }),\n ],\n});\n", "type": "registry:lib", "target": "lib/auth.ts" }, { "path": "registry/default/lib/email.tsx", "content": "import { Resend } from \"resend\";\nimport { OTPTemplate } from \"@/components/emails/otp-template\";\nimport { ResetPasswordTemplate } from \"@/components/emails/reset-password-template\";\n\nconst resend = new Resend(process.env.RESEND_API_KEY);\n\nexport async function sendVerificationEmail(email: string, otp: string) {\n try {\n console.log(`Attempting to send OTP email to ${email}...`);\n\n if (!process.env.RESEND_API_KEY) {\n console.warn(\"WARNING: RESEND_API_KEY is not set. Email sending will likely fail.\");\n }\n\n const { data, error } = await resend.emails.send({\n from: process.env.RESEND_FROM_EMAIL || \"onboarding@resend.dev\",\n to: email,\n subject: \"Your Verification Code\",\n react: ,\n });\n\n if (error) {\n console.error(\"Resend API Error:\", error);\n throw error;\n }\n\n console.log(`Email sent successfully to ${email}. ID:`, data?.id);\n return data;\n } catch (error) {\n console.error(\"Failed to send verification email:\", error);\n if (error instanceof Error) {\n console.error(\"Error Message:\", error.message);\n console.error(\"Error Stack:\", error.stack);\n }\n throw error;\n }\n}\n\nexport async function sendResetPasswordEmail(email: string, url: string) {\n try {\n console.log(`Attempting to send Reset Password email to ${email}...`);\n\n if (!process.env.RESEND_API_KEY) {\n console.warn(\"WARNING: RESEND_API_KEY is not set. Email sending will likely fail.\");\n }\n\n const { data, error } = await resend.emails.send({\n from: process.env.RESEND_FROM_EMAIL || \"onboarding@resend.dev\",\n to: email,\n subject: \"Reset your password\",\n react: ,\n });\n\n if (error) {\n console.error(\"Resend API Error:\", error);\n throw error;\n }\n\n console.log(`Reset Password email sent successfully to ${email}. ID:`, data?.id);\n return data;\n } catch (error) {\n console.error(\"Failed to send reset password email:\", error);\n if (error instanceof Error) {\n console.error(\"Error Message:\", error.message);\n console.error(\"Error Stack:\", error.stack);\n }\n throw error;\n }\n}\n", "type": "registry:lib", "target": "lib/email.tsx" }, { "path": "registry/default/prisma/schema.prisma", "content": "generator client {\n provider = \"prisma-client\"\n output = \"../lib/generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel User {\n id String @id @default(cuid())\n email String @unique\n name String?\n emailVerified Boolean\n image String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n role String? @default(\"user\")\n sessions Session[]\n accounts Account[]\n\n @@map(\"user\")\n}\n\nmodel Session {\n id String @id @default(cuid())\n userId String\n token String\n expiresAt DateTime\n ipAddress String?\n userAgent String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([token])\n @@map(\"session\")\n}\n\nmodel Account {\n id String @id @default(cuid())\n userId String\n accountId String\n providerId String\n accessToken String?\n refreshToken String?\n accessTokenExpiresAt DateTime?\n refreshTokenExpiresAt DateTime?\n scope String?\n password String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@map(\"account\")\n}\n\nmodel Verification {\n id String @id @default(cuid())\n identifier String\n value String\n expiresAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@map(\"verification\")\n}\n", "type": "registry:file", "target": "prisma/schema.prisma" }, { "path": "registry/default/prisma.config.ts", "content": "import \"dotenv/config\";\nimport { defineConfig } from \"prisma/config\";\n\nexport default defineConfig({\n schema: \"prisma/schema.prisma\",\n migrations: {\n path: \"prisma/migrations\",\n },\n datasource: {\n url: process.env[\"DATABASE_URL\"],\n },\n});\n", "type": "registry:file", "target": "prisma.config.ts" }, { "path": "registry/default/pages/api/auth/[...all]/route.ts", "content": "import { auth } from \"@/lib/auth\";\nimport { toNextJsHandler } from \"better-auth/next-js\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport const { GET, POST } = toNextJsHandler(auth);\n", "type": "registry:page", "target": "app/api/auth/[...all]/route.ts" }, { "path": "registry/default/pages/auth/sign-in/page.tsx", "content": "import { SignIn } from \"@/components/auth\";\n\nexport default function SignInPage() {\n return ;\n}\n", "type": "registry:page", "target": "app/(auth)/auth/sign-in/page.tsx" }, { "path": "registry/default/pages/auth/sign-up/page.tsx", "content": "import { SignUp } from \"@/components/auth\";\n\nexport default function SignUpPage() {\n return ;\n}\n", "type": "registry:page", "target": "app/(auth)/auth/sign-up/page.tsx" }, { "path": "registry/default/pages/auth/verify-email/page.tsx", "content": "import { VerifyEmail } from \"@/components/auth\";\n\nexport default function VerifyEmailPage() {\n return ;\n}\n", "type": "registry:page", "target": "app/(auth)/auth/verify-email/page.tsx" }, { "path": "registry/default/pages/auth/forgot-password/page.tsx", "content": "import { ForgetPassword } from \"@/components/auth\";\n\nexport default function ForgotPasswordPage() {\n return ;\n}\n", "type": "registry:page", "target": "app/(auth)/auth/forgot-password/page.tsx" }, { "path": "registry/default/pages/auth/reset-password/page.tsx", "content": "import { ResetPassword } from \"@/components/auth\";\n\nexport default function ResetPasswordPage() {\n return ;\n}\n", "type": "registry:page", "target": "app/(auth)/auth/reset-password/page.tsx" }, { "path": "registry/default/pages/auth/change-password/page.tsx", "content": "import { ChangePassword } from \"@/components/auth\";\n\nexport default function ChangePasswordPage() {\n return ;\n}\n", "type": "registry:page", "target": "app/(auth)/auth/change-password/page.tsx" }, { "path": "registry/default/pages/auth/profile/page.tsx", "content": "import { Profile } from \"@/components/auth\";\n\nexport default function ProfilePage() {\n return ;\n}\n", "type": "registry:page", "target": "app/(auth)/auth/profile/page.tsx" }, { "path": "registry/default/env.example", "content": "# Better Auth Configuration\n# Generate secret with: openssl rand -base64 32\nBETTER_AUTH_SECRET=\"\"\nBETTER_AUTH_URL=\"http://localhost:3000\"\n\n# Database (PostgreSQL recommended)\n# Example: postgresql://user:password@host:5432/dbname\nDATABASE_URL=\"\"\n\n# Email Provider (Resend)\n# Get your API key at: https://resend.com\nRESEND_FROM_EMAIL=\"\"\nRESEND_API_KEY=\"\"\n\n# OAuth Providers (Optional)\n# Google: https://console.cloud.google.com/apis/credentials\nGOOGLE_CLIENT_ID=\"\"\nGOOGLE_CLIENT_SECRET=\"\"\n\n# GitHub: https://github.com/settings/developers\nGITHUB_CLIENT_ID=\"\"\nGITHUB_CLIENT_SECRET=\"\"\n", "type": "registry:file", "target": ".env.example" }, { "path": "registry/default/pages/layout.tsx", "content": "import type { Metadata } from \"next\";\nimport { Inter } from \"next/font/google\";\nimport \"./globals.css\";\nimport { Toaster } from \"sonner\";\n\nconst inter = Inter({ subsets: [\"latin\"] });\n\nexport const metadata: Metadata = {\n title: \"My App\",\n description: \"My application with Better Auth\",\n};\n\nexport default function RootLayout({\n children,\n}: Readonly<{\n children: React.ReactNode;\n}>) {\n return (\n \n \n {children}\n \n \n \n );\n}\n", "type": "registry:page", "target": "app/layout.tsx" }, { "path": "registry/default/pages/dashboard/page.tsx", "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport {\n TrendingUp,\n TrendingDown,\n DollarSign,\n CreditCard,\n Activity,\n Users,\n HelpCircle,\n Search,\n Download,\n Filter,\n MoreHorizontal,\n Globe,\n Mail,\n Share2,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Avatar, AvatarFallback } from \"@/components/ui/avatar\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { SidebarTrigger } from \"@/components/ui/sidebar\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Tabs, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/components/ui/table\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { LogoutButton } from \"@/components/auth/logout-button\";\n\nconst stats = [\n {\n title: \"Total Sales\",\n value: \"$18,200\",\n change: \"+10.2%\",\n trend: \"up\",\n description: \"vs last month\",\n },\n {\n title: \"Operating Expenses\",\n value: \"$18,200\",\n change: \"-5.75%\",\n trend: \"down\",\n description: \"vs last month\",\n },\n {\n title: \"Gross Profit\",\n value: \"$18,200\",\n change: \"+8.65%\",\n trend: \"up\",\n description: \"vs last month\",\n },\n];\n\nconst sourceData = [\n { name: \"Website\", value: 5846, percentage: 65, color: \"bg-primary\" },\n { name: \"Social Media\", value: 2490, percentage: 20, color: \"bg-blue-400\" },\n { name: \"Email\", value: 1857, percentage: 10, color: \"bg-violet-400\" },\n { name: \"Referral\", value: 1245, percentage: 5, color: \"bg-orange-400\" },\n];\n\nconst revenueData = [\n { month: \"Jan\", revenue: 8000 },\n { month: \"Feb\", revenue: 12000 },\n { month: \"Mar\", revenue: 15000 },\n { month: \"April\", revenue: 18000 },\n { month: \"May\", revenue: 28000 },\n { month: \"Jun\", revenue: 22000 },\n { month: \"July\", revenue: 19000 },\n { month: \"Aug\", revenue: 16000 },\n { month: \"Sep\", revenue: 14000 },\n];\n\nconst salesData = [\n {\n id: 1,\n dealName: \"Bargain Bonanza\",\n company: \"Amazon.com, Inc\",\n companyLogo: \"A\",\n price: \"$850,000.00\",\n dateCreated: \"Mon, 12 April 2025\",\n owner: \"Jenny Wilson\",\n ownerAvatar: \"JW\",\n stage: \"New\",\n },\n {\n id: 2,\n dealName: \"Discount Delights\",\n company: \"Xiaomi Corporation\",\n companyLogo: \"X\",\n price: \"$990,000.00\",\n dateCreated: \"Mon, 11 April 2025\",\n owner: \"Leslie Alexander\",\n ownerAvatar: \"LA\",\n stage: \"New\",\n },\n {\n id: 3,\n dealName: \"Price Slayers\",\n company: \"Apple, Inc\",\n companyLogo: \"A\",\n price: \"$450,000.00\",\n dateCreated: \"Mon, 11 April 2025\",\n owner: \"Cody Fisher\",\n ownerAvatar: \"CF\",\n stage: \"New\",\n },\n];\n\nexport default function DashboardPage() {\n const [selectedTab, setSelectedTab] = useState(\"overview\");\n const maxRevenue = Math.max(...revenueData.map((d) => d.revenue));\n\n return (\n
\n {/* Header */}\n
\n
\n \n \n

Dashboard

\n
\n
\n \n \n
\n \n \n JD\n \n \n \n \n AB\n \n \n \n \n CD\n \n \n
\n \n
\n
\n\n {/* Main Content */}\n
\n {/* Tabs */}\n
\n \n \n Overview\n Sales\n Order\n Report\n \n \n
\n \n \n
\n
\n\n {/* Stats Cards */}\n
\n {stats.map((stat) => (\n \n \n
\n
\n

\n {stat.title}\n

\n
\n

{stat.value}

\n
\n
\n {[1, 2, 3, 4, 5].map((i) => (\n \n ))}\n
\n
\n
\n
\n \n {stat.trend === \"up\" ? (\n \n ) : (\n \n )}\n {stat.change}\n \n \n {stat.description}\n \n
\n
\n
\n
\n
\n ))}\n
\n\n {/* Charts Row */}\n
\n {/* Revenue Forecast */}\n \n \n
\n \n Revenue Forecast\n \n
\n
\n \n \n
\n
\n \n
\n {revenueData.map((data, index) => (\n
\n \n \n {data.month}\n \n
\n ))}\n
\n
\n
\n
\n \n Sales revenue\n \n
\n
\n
\n \n Sales revenue\n \n
\n
\n
\n \n Sales revenue\n \n
\n
\n \n \n\n {/* Source */}\n \n \n
\n Source\n
\n \n
\n \n
\n
\n

12,450

\n

Total source

\n
\n
\n {sourceData.map((source) => (\n \n ))}\n
\n
\n
\n {sourceData.map((source) => (\n
\n
\n {source.name === \"Website\" && (\n \n )}\n {source.name === \"Social Media\" && (\n \n )}\n {source.name === \"Email\" && (\n \n )}\n {source.name === \"Referral\" && (\n \n )}\n {source.name}\n
\n \n {source.value.toLocaleString()}\n \n \n {source.percentage}%\n \n
\n ))}\n
\n \n
\n
\n
\n\n {/* Table */}\n \n \n \n Table Data Sales\n \n
\n
\n \n \n
\n \n \n
\n
\n \n \n \n \n \n \n \n Deal name\n Company\n Price\n Date created\n Owner\n Stage\n \n \n \n \n {salesData.map((sale) => (\n \n \n \n \n \n {sale.dealName}\n \n \n
\n \n \n {sale.companyLogo}\n \n \n {sale.company}\n
\n
\n {sale.price}\n \n {sale.dateCreated}\n \n \n
\n \n \n {sale.ownerAvatar}\n \n \n {sale.owner}\n
\n
\n \n \n {sale.stage}\n \n \n \n \n \n
\n ))}\n
\n
\n
\n
\n
\n
\n );\n}\n", "type": "registry:page", "target": "app/dashboard/page.tsx" }, { "path": "registry/default/pages/dashboard/layout.tsx", "content": "import { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { SidebarProvider, SidebarInset } from \"@/components/ui/sidebar\";\nimport { AppSidebar } from \"@/components/dashboard/app-sidebar\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport default async function DashboardLayout({\n children,\n}: {\n children: React.ReactNode;\n}) {\n const session = await auth.api.getSession({\n headers: await headers(),\n });\n\n if (!session) {\n redirect(\"/auth/sign-in\");\n }\n\n return (\n \n \n \n {children}\n \n \n );\n}\n", "type": "registry:page", "target": "app/dashboard/layout.tsx" }, { "path": "registry/default/ui/dashboard/app-sidebar.tsx", "content": "\"use client\";\n\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport {\n LayoutDashboard,\n Users,\n Building2,\n Handshake,\n CheckSquare,\n Package,\n Mail,\n Puzzle,\n Settings,\n ChevronLeft,\n User,\n} from \"lucide-react\";\n\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarRail,\n useSidebar,\n} from \"@/components/ui/sidebar\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nconst mainMenuItems = [\n {\n title: \"Dashboard\",\n url: \"/dashboard\",\n icon: LayoutDashboard,\n },\n {\n title: \"Contacts\",\n url: \"/dashboard/contacts\",\n icon: Users,\n },\n {\n title: \"Companies\",\n url: \"/dashboard/companies\",\n icon: Building2,\n },\n {\n title: \"Deals\",\n url: \"/dashboard/deals\",\n icon: Handshake,\n },\n {\n title: \"Tasks\",\n url: \"/dashboard/tasks\",\n icon: CheckSquare,\n },\n];\n\nconst marketingItems = [\n {\n title: \"Products\",\n url: \"/dashboard/products\",\n icon: Package,\n },\n {\n title: \"Emails\",\n url: \"/dashboard/emails\",\n icon: Mail,\n },\n];\n\nconst preferencesItems = [\n {\n title: \"Integrations\",\n url: \"/dashboard/integrations\",\n icon: Puzzle,\n },\n {\n title: \"Settings\",\n url: \"/dashboard/settings\",\n icon: Settings,\n },\n];\n\ninterface AppSidebarProps {\n user?: {\n name?: string | null;\n email?: string | null;\n image?: string | null;\n };\n}\n\nexport function AppSidebar({ user }: AppSidebarProps) {\n const pathname = usePathname();\n const { state, toggleSidebar } = useSidebar();\n const isCollapsed = state === \"collapsed\";\n\n const initials = user?.name\n ?.split(\" \")\n .map((n) => n[0])\n .join(\"\")\n .toUpperCase() || \"U\";\n\n return (\n \n \n
\n \n
\n JB\n
\n {!isCollapsed && (\n Better Auth\n )}\n \n {!isCollapsed && (\n \n \n \n )}\n
\n
\n\n \n \n \n Main Menu\n \n \n \n {mainMenuItems.map((item) => (\n \n \n \n \n {item.title}\n \n \n \n ))}\n \n \n \n\n \n \n Marketing\n \n \n \n {marketingItems.map((item) => (\n \n \n \n \n {item.title}\n \n \n \n ))}\n \n \n \n\n \n \n Preferences\n \n \n \n {preferencesItems.map((item) => (\n \n \n \n \n {item.title}\n \n \n \n ))}\n \n \n \n \n\n \n {!isCollapsed && (\n
\n

20 days left

\n

\n Upgrade to premium and enjoy the benefits for a long time.\n

\n \n
\n )}\n \n \n \n \n {initials}\n \n \n {!isCollapsed && (\n
\n

\n {user?.name || \"User\"}\n

\n

\n {user?.email || \"user@example.com\"}\n

\n
\n )}\n \n
\n\n \n
\n );\n}\n", "type": "registry:component", "target": "components/dashboard/app-sidebar.tsx" }, { "path": "registry/default/pages/profile/page.tsx", "content": "import { auth } from \"@/lib/auth\";\nimport { headers } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { Profile } from \"@/components/auth\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport default async function ProfilePage() {\n const session = await auth.api.getSession({\n headers: await headers(),\n });\n\n if (!session) {\n redirect(\"/auth/sign-in\");\n }\n\n return (\n \n );\n}\n", "type": "registry:page", "target": "app/profile/page.tsx" } ], "type": "registry:block" }