diff --git a/package.json b/package.json
index 9e2a2087..7c12f5d5 100644
--- a/package.json
+++ b/package.json
@@ -18,9 +18,16 @@
"@hookform/resolvers": "3.9.0",
"@lucia-auth/adapter-drizzle": "1.1.0",
"@node-rs/argon2": "1.8.3",
+ "@radix-ui/react-checkbox": "1.1.2",
+ "@radix-ui/react-dialog": "1.1.2",
"@radix-ui/react-icons": "1.3.0",
"@radix-ui/react-label": "2.1.0",
+ "@radix-ui/react-popover": "1.1.2",
+ "@radix-ui/react-radio-group": "1.2.1",
+ "@radix-ui/react-select": "2.1.2",
+ "@radix-ui/react-separator": "1.1.0",
"@radix-ui/react-slot": "1.1.0",
+ "@radix-ui/react-switch": "1.1.1",
"@radix-ui/react-toast": "1.2.2",
"@react-email/components": "0.0.25",
"@react-email/tailwind": "0.1.0",
@@ -28,6 +35,7 @@
"better-sqlite3": "11.3.0",
"class-variance-authority": "0.7.0",
"clsx": "2.1.1",
+ "cmdk": "1.0.0",
"cookie-parser": "1.4.6",
"cors": "2.8.5",
"drizzle-orm": "0.33.0",
diff --git a/src/app/configuration/page.tsx b/src/app/configuration/page.tsx
new file mode 100644
index 00000000..9af30d8d
--- /dev/null
+++ b/src/app/configuration/page.tsx
@@ -0,0 +1,7 @@
+export default async function Page() {
+ return (
+ <>
+
IDK what this will show...
+ >
+ );
+}
diff --git a/src/app/configuration/sites/[siteId]/account/page.tsx b/src/app/configuration/sites/[siteId]/account/page.tsx
new file mode 100644
index 00000000..03df0d89
--- /dev/null
+++ b/src/app/configuration/sites/[siteId]/account/page.tsx
@@ -0,0 +1,18 @@
+import { Separator } from "@/components/ui/separator"
+import { AccountForm } from "@/components/account-form"
+
+export default function SettingsAccountPage() {
+ return (
+
+
+
Account
+
+ Update your account settings. Set your preferred language and
+ timezone.
+
+
+
+
+
+ )
+}
diff --git a/src/app/configuration/sites/[siteId]/appearance/page.tsx b/src/app/configuration/sites/[siteId]/appearance/page.tsx
new file mode 100644
index 00000000..ca038aa2
--- /dev/null
+++ b/src/app/configuration/sites/[siteId]/appearance/page.tsx
@@ -0,0 +1,18 @@
+import { Separator } from "@/components/ui/separator"
+import { AppearanceForm } from "@/components/appearance-form"
+
+export default function SettingsAppearancePage() {
+ return (
+
+
+
Appearance
+
+ Customize the appearance of the app. Automatically switch between day
+ and night themes.
+
+
+
+
+
+ )
+}
diff --git a/src/app/configuration/sites/[siteId]/display/page.tsx b/src/app/configuration/sites/[siteId]/display/page.tsx
new file mode 100644
index 00000000..f934f6e6
--- /dev/null
+++ b/src/app/configuration/sites/[siteId]/display/page.tsx
@@ -0,0 +1,17 @@
+import { Separator } from "@/components/ui/separator"
+import { DisplayForm } from "@/components/display-form"
+
+export default function SettingsDisplayPage() {
+ return (
+
+
+
Display
+
+ Turn items on or off to control what's displayed in the app.
+
+
+
+
+
+ )
+}
diff --git a/src/app/configuration/sites/[siteId]/layout.tsx b/src/app/configuration/sites/[siteId]/layout.tsx
new file mode 100644
index 00000000..24bc7e64
--- /dev/null
+++ b/src/app/configuration/sites/[siteId]/layout.tsx
@@ -0,0 +1,76 @@
+import { Metadata } from "next"
+import Image from "next/image"
+
+import { Separator } from "@/components/ui/separator"
+import { SidebarNav } from "@/components/sidebar-nav"
+
+export const metadata: Metadata = {
+ title: "Forms",
+ description: "Advanced form example using react-hook-form and Zod.",
+}
+
+const sidebarNavItems = [
+ {
+ title: "Profile",
+ href: "/configuration/sites/{siteId}/",
+ },
+ {
+ title: "Account",
+ href: "/configuration/sites/{siteId}/account",
+ },
+ {
+ title: "Appearance",
+ href: "/configuration/sites/{siteId}/appearance",
+ },
+ {
+ title: "Notifications",
+ href: "/configuration/sites/{siteId}/notifications",
+ },
+ {
+ title: "Display",
+ href: "/configuration/sites/{siteId}/display",
+ },
+]
+
+interface SettingsLayoutProps {
+ children: React.ReactNode,
+ params: { siteId: string }
+}
+
+export default function SettingsLayout({ children, params }: SettingsLayoutProps) {
+ return (
+ <>
+
+
+
+
+
+
+
Settings
+
+ Manage your account settings and set e-mail preferences.
+
+
+
+
+
+
{children}
+
+
+ >
+ )
+}
diff --git a/src/app/configuration/sites/[siteId]/notifications/page.tsx b/src/app/configuration/sites/[siteId]/notifications/page.tsx
new file mode 100644
index 00000000..7c5c5ec0
--- /dev/null
+++ b/src/app/configuration/sites/[siteId]/notifications/page.tsx
@@ -0,0 +1,17 @@
+import { Separator } from "@/components/ui/separator"
+import { NotificationsForm } from "@/components/notifications-form"
+
+export default function SettingsNotificationsPage() {
+ return (
+
+
+
Notifications
+
+ Configure how you receive notifications.
+
+
+
+
+
+ )
+}
diff --git a/src/app/configuration/sites/[siteId]/page.tsx b/src/app/configuration/sites/[siteId]/page.tsx
new file mode 100644
index 00000000..66744634
--- /dev/null
+++ b/src/app/configuration/sites/[siteId]/page.tsx
@@ -0,0 +1,17 @@
+import { Separator } from "@/components/ui/separator"
+import { ProfileForm } from "@app/components/profile-form"
+
+export default function SettingsProfilePage() {
+ return (
+
+
+
Profile
+
+ This is how others will see you on the site.
+
+
+
+
+
+ )
+}
diff --git a/src/app/configuration/sites/page.tsx b/src/app/configuration/sites/page.tsx
new file mode 100644
index 00000000..f720649d
--- /dev/null
+++ b/src/app/configuration/sites/page.tsx
@@ -0,0 +1,7 @@
+export default async function Page() {
+ return (
+ <>
+ This is where the table goes...
+ >
+ );
+}
diff --git a/src/app/profile/account/account-form.tsx b/src/app/profile/account/account-form.tsx
new file mode 100644
index 00000000..2cb9bf1e
--- /dev/null
+++ b/src/app/profile/account/account-form.tsx
@@ -0,0 +1,176 @@
+"use client"
+
+import { zodResolver } from "@hookform/resolvers/zod"
+import { CalendarIcon, CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { cn } from "@/lib/utils"
+import { toast } from "@/hooks/use-toast"
+import { Button } from "@/components/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { Input } from "@/components/ui/input"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+
+const languages = [
+ { label: "English", value: "en" },
+ { label: "French", value: "fr" },
+ { label: "German", value: "de" },
+ { label: "Spanish", value: "es" },
+ { label: "Portuguese", value: "pt" },
+ { label: "Russian", value: "ru" },
+ { label: "Japanese", value: "ja" },
+ { label: "Korean", value: "ko" },
+ { label: "Chinese", value: "zh" },
+] as const
+
+const accountFormSchema = z.object({
+ name: z
+ .string()
+ .min(2, {
+ message: "Name must be at least 2 characters.",
+ })
+ .max(30, {
+ message: "Name must not be longer than 30 characters.",
+ }),
+ dob: z.date({
+ required_error: "A date of birth is required.",
+ }),
+ language: z.string({
+ required_error: "Please select a language.",
+ }),
+})
+
+type AccountFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ // name: "Your name",
+ // dob: new Date("2023-01-23"),
+}
+
+export function AccountForm() {
+ const form = useForm({
+ resolver: zodResolver(accountFormSchema),
+ defaultValues,
+ })
+
+ function onSubmit(data: AccountFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/app/profile/account/page.tsx b/src/app/profile/account/page.tsx
new file mode 100644
index 00000000..a8c3faef
--- /dev/null
+++ b/src/app/profile/account/page.tsx
@@ -0,0 +1,18 @@
+import { Separator } from "@/components/ui/separator"
+import { AccountForm } from "@/app/configuration/account/account-form"
+
+export default function SettingsAccountPage() {
+ return (
+
+
+
Account
+
+ Update your account settings. Set your preferred language and
+ timezone.
+
+
+
+
+
+ )
+}
diff --git a/src/app/profile/appearance/appearance-form.tsx b/src/app/profile/appearance/appearance-form.tsx
new file mode 100644
index 00000000..9a81f37a
--- /dev/null
+++ b/src/app/profile/appearance/appearance-form.tsx
@@ -0,0 +1,164 @@
+"use client"
+
+import { zodResolver } from "@hookform/resolvers/zod"
+import { ChevronDownIcon } from "@radix-ui/react-icons"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { cn } from "@/lib/utils"
+import { toast } from "@/hooks/use-toast"
+import { Button, buttonVariants } from "@/components/ui/button"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
+
+const appearanceFormSchema = z.object({
+ theme: z.enum(["light", "dark"], {
+ required_error: "Please select a theme.",
+ }),
+ font: z.enum(["inter", "manrope", "system"], {
+ invalid_type_error: "Select a font",
+ required_error: "Please select a font.",
+ }),
+})
+
+type AppearanceFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ theme: "light",
+}
+
+export function AppearanceForm() {
+ const form = useForm({
+ resolver: zodResolver(appearanceFormSchema),
+ defaultValues,
+ })
+
+ function onSubmit(data: AppearanceFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/app/profile/appearance/page.tsx b/src/app/profile/appearance/page.tsx
new file mode 100644
index 00000000..a8d65df1
--- /dev/null
+++ b/src/app/profile/appearance/page.tsx
@@ -0,0 +1,18 @@
+import { Separator } from "@/components/ui/separator"
+import { AppearanceForm } from "@/app/configuration/appearance/appearance-form"
+
+export default function SettingsAppearancePage() {
+ return (
+
+
+
Appearance
+
+ Customize the appearance of the app. Automatically switch between day
+ and night themes.
+
+
+
+
+
+ )
+}
diff --git a/src/app/profile/components/sidebar-nav.tsx b/src/app/profile/components/sidebar-nav.tsx
new file mode 100644
index 00000000..addcfefd
--- /dev/null
+++ b/src/app/profile/components/sidebar-nav.tsx
@@ -0,0 +1,44 @@
+"use client"
+
+import Link from "next/link"
+import { usePathname } from "next/navigation"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+interface SidebarNavProps extends React.HTMLAttributes {
+ items: {
+ href: string
+ title: string
+ }[]
+}
+
+export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
+ const pathname = usePathname()
+
+ return (
+
+ )
+}
diff --git a/src/app/profile/display/display-form.tsx b/src/app/profile/display/display-form.tsx
new file mode 100644
index 00000000..cf1c6c0d
--- /dev/null
+++ b/src/app/profile/display/display-form.tsx
@@ -0,0 +1,132 @@
+"use client"
+
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { toast } from "@/hooks/use-toast"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+
+const items = [
+ {
+ id: "recents",
+ label: "Recents",
+ },
+ {
+ id: "home",
+ label: "Home",
+ },
+ {
+ id: "applications",
+ label: "Applications",
+ },
+ {
+ id: "desktop",
+ label: "Desktop",
+ },
+ {
+ id: "downloads",
+ label: "Downloads",
+ },
+ {
+ id: "documents",
+ label: "Documents",
+ },
+] as const
+
+const displayFormSchema = z.object({
+ items: z.array(z.string()).refine((value) => value.some((item) => item), {
+ message: "You have to select at least one item.",
+ }),
+})
+
+type DisplayFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ items: ["recents", "home"],
+}
+
+export function DisplayForm() {
+ const form = useForm({
+ resolver: zodResolver(displayFormSchema),
+ defaultValues,
+ })
+
+ function onSubmit(data: DisplayFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/app/profile/display/page.tsx b/src/app/profile/display/page.tsx
new file mode 100644
index 00000000..a5579fbe
--- /dev/null
+++ b/src/app/profile/display/page.tsx
@@ -0,0 +1,17 @@
+import { Separator } from "@/components/ui/separator"
+import { DisplayForm } from "@/app/configuration/display/display-form"
+
+export default function SettingsDisplayPage() {
+ return (
+
+
+
Display
+
+ Turn items on or off to control what's displayed in the app.
+
+
+
+
+
+ )
+}
diff --git a/src/app/profile/layout.tsx b/src/app/profile/layout.tsx
new file mode 100644
index 00000000..caa96a32
--- /dev/null
+++ b/src/app/profile/layout.tsx
@@ -0,0 +1,75 @@
+import { Metadata } from "next"
+import Image from "next/image"
+
+import { Separator } from "@/components/ui/separator"
+import { SidebarNav } from "@/app/configuration/components/sidebar-nav"
+
+export const metadata: Metadata = {
+ title: "Forms",
+ description: "Advanced form example using react-hook-form and Zod.",
+}
+
+const sidebarNavItems = [
+ {
+ title: "Profile",
+ href: "/configuration",
+ },
+ {
+ title: "Account",
+ href: "/configuration/account",
+ },
+ {
+ title: "Appearance",
+ href: "/configuration/appearance",
+ },
+ {
+ title: "Notifications",
+ href: "/configuration/notifications",
+ },
+ {
+ title: "Display",
+ href: "/configuration/display",
+ },
+]
+
+interface SettingsLayoutProps {
+ children: React.ReactNode
+}
+
+export default function SettingsLayout({ children }: SettingsLayoutProps) {
+ return (
+ <>
+
+
+
+
+
+
+
Settings
+
+ Manage your account settings and set e-mail preferences.
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/app/profile/notifications/notifications-form.tsx b/src/app/profile/notifications/notifications-form.tsx
new file mode 100644
index 00000000..cc191252
--- /dev/null
+++ b/src/app/profile/notifications/notifications-form.tsx
@@ -0,0 +1,222 @@
+"use client"
+
+import Link from "next/link"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { toast } from "@/hooks/use-toast"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
+import { Switch } from "@/components/ui/switch"
+
+const notificationsFormSchema = z.object({
+ type: z.enum(["all", "mentions", "none"], {
+ required_error: "You need to select a notification type.",
+ }),
+ mobile: z.boolean().default(false).optional(),
+ communication_emails: z.boolean().default(false).optional(),
+ social_emails: z.boolean().default(false).optional(),
+ marketing_emails: z.boolean().default(false).optional(),
+ security_emails: z.boolean(),
+})
+
+type NotificationsFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ communication_emails: false,
+ marketing_emails: false,
+ social_emails: true,
+ security_emails: true,
+}
+
+export function NotificationsForm() {
+ const form = useForm({
+ resolver: zodResolver(notificationsFormSchema),
+ defaultValues,
+ })
+
+ function onSubmit(data: NotificationsFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/app/profile/notifications/page.tsx b/src/app/profile/notifications/page.tsx
new file mode 100644
index 00000000..efc2da63
--- /dev/null
+++ b/src/app/profile/notifications/page.tsx
@@ -0,0 +1,17 @@
+import { Separator } from "@/components/ui/separator"
+import { NotificationsForm } from "@/app/configuration/notifications/notifications-form"
+
+export default function SettingsNotificationsPage() {
+ return (
+
+
+
Notifications
+
+ Configure how you receive notifications.
+
+
+
+
+
+ )
+}
diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx
new file mode 100644
index 00000000..66744634
--- /dev/null
+++ b/src/app/profile/page.tsx
@@ -0,0 +1,17 @@
+import { Separator } from "@/components/ui/separator"
+import { ProfileForm } from "@app/components/profile-form"
+
+export default function SettingsProfilePage() {
+ return (
+
+
+
Profile
+
+ This is how others will see you on the site.
+
+
+
+
+
+ )
+}
diff --git a/src/app/profile/profile-form.tsx b/src/app/profile/profile-form.tsx
new file mode 100644
index 00000000..0911ac0f
--- /dev/null
+++ b/src/app/profile/profile-form.tsx
@@ -0,0 +1,192 @@
+"use client"
+
+import Link from "next/link"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useFieldArray, useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { cn } from "@/lib/utils"
+import { toast } from "@/hooks/use-toast"
+
+import { Button } from "@/components/ui/button"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { Input } from "@/components/ui/input"
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select"
+import { Textarea } from "@/components/ui/textarea"
+
+const profileFormSchema = z.object({
+ username: z
+ .string()
+ .min(2, {
+ message: "Username must be at least 2 characters.",
+ })
+ .max(30, {
+ message: "Username must not be longer than 30 characters.",
+ }),
+ email: z
+ .string({
+ required_error: "Please select an email to display.",
+ })
+ .email(),
+ bio: z.string().max(160).min(4),
+ urls: z
+ .array(
+ z.object({
+ value: z.string().url({ message: "Please enter a valid URL." }),
+ })
+ )
+ .optional(),
+})
+
+type ProfileFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ bio: "I own a computer.",
+ urls: [
+ { value: "https://shadcn.com" },
+ { value: "http://twitter.com/shadcn" },
+ ],
+}
+
+export function ProfileForm() {
+ const form = useForm({
+ resolver: zodResolver(profileFormSchema),
+ defaultValues,
+ mode: "onChange",
+ })
+
+ const { fields, append } = useFieldArray({
+ name: "urls",
+ control: form.control,
+ })
+
+ function onSubmit(data: ProfileFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/components/SiteForm.tsx b/src/components/SiteForm.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/src/components/account-form.tsx b/src/components/account-form.tsx
new file mode 100644
index 00000000..2cb9bf1e
--- /dev/null
+++ b/src/components/account-form.tsx
@@ -0,0 +1,176 @@
+"use client"
+
+import { zodResolver } from "@hookform/resolvers/zod"
+import { CalendarIcon, CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { cn } from "@/lib/utils"
+import { toast } from "@/hooks/use-toast"
+import { Button } from "@/components/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { Input } from "@/components/ui/input"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+
+const languages = [
+ { label: "English", value: "en" },
+ { label: "French", value: "fr" },
+ { label: "German", value: "de" },
+ { label: "Spanish", value: "es" },
+ { label: "Portuguese", value: "pt" },
+ { label: "Russian", value: "ru" },
+ { label: "Japanese", value: "ja" },
+ { label: "Korean", value: "ko" },
+ { label: "Chinese", value: "zh" },
+] as const
+
+const accountFormSchema = z.object({
+ name: z
+ .string()
+ .min(2, {
+ message: "Name must be at least 2 characters.",
+ })
+ .max(30, {
+ message: "Name must not be longer than 30 characters.",
+ }),
+ dob: z.date({
+ required_error: "A date of birth is required.",
+ }),
+ language: z.string({
+ required_error: "Please select a language.",
+ }),
+})
+
+type AccountFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ // name: "Your name",
+ // dob: new Date("2023-01-23"),
+}
+
+export function AccountForm() {
+ const form = useForm({
+ resolver: zodResolver(accountFormSchema),
+ defaultValues,
+ })
+
+ function onSubmit(data: AccountFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/components/appearance-form.tsx b/src/components/appearance-form.tsx
new file mode 100644
index 00000000..9a81f37a
--- /dev/null
+++ b/src/components/appearance-form.tsx
@@ -0,0 +1,164 @@
+"use client"
+
+import { zodResolver } from "@hookform/resolvers/zod"
+import { ChevronDownIcon } from "@radix-ui/react-icons"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { cn } from "@/lib/utils"
+import { toast } from "@/hooks/use-toast"
+import { Button, buttonVariants } from "@/components/ui/button"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
+
+const appearanceFormSchema = z.object({
+ theme: z.enum(["light", "dark"], {
+ required_error: "Please select a theme.",
+ }),
+ font: z.enum(["inter", "manrope", "system"], {
+ invalid_type_error: "Select a font",
+ required_error: "Please select a font.",
+ }),
+})
+
+type AppearanceFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ theme: "light",
+}
+
+export function AppearanceForm() {
+ const form = useForm({
+ resolver: zodResolver(appearanceFormSchema),
+ defaultValues,
+ })
+
+ function onSubmit(data: AppearanceFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/components/display-form.tsx b/src/components/display-form.tsx
new file mode 100644
index 00000000..cf1c6c0d
--- /dev/null
+++ b/src/components/display-form.tsx
@@ -0,0 +1,132 @@
+"use client"
+
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { toast } from "@/hooks/use-toast"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+
+const items = [
+ {
+ id: "recents",
+ label: "Recents",
+ },
+ {
+ id: "home",
+ label: "Home",
+ },
+ {
+ id: "applications",
+ label: "Applications",
+ },
+ {
+ id: "desktop",
+ label: "Desktop",
+ },
+ {
+ id: "downloads",
+ label: "Downloads",
+ },
+ {
+ id: "documents",
+ label: "Documents",
+ },
+] as const
+
+const displayFormSchema = z.object({
+ items: z.array(z.string()).refine((value) => value.some((item) => item), {
+ message: "You have to select at least one item.",
+ }),
+})
+
+type DisplayFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ items: ["recents", "home"],
+}
+
+export function DisplayForm() {
+ const form = useForm({
+ resolver: zodResolver(displayFormSchema),
+ defaultValues,
+ })
+
+ function onSubmit(data: DisplayFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/components/notifications-form.tsx b/src/components/notifications-form.tsx
new file mode 100644
index 00000000..cc191252
--- /dev/null
+++ b/src/components/notifications-form.tsx
@@ -0,0 +1,222 @@
+"use client"
+
+import Link from "next/link"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { toast } from "@/hooks/use-toast"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
+import { Switch } from "@/components/ui/switch"
+
+const notificationsFormSchema = z.object({
+ type: z.enum(["all", "mentions", "none"], {
+ required_error: "You need to select a notification type.",
+ }),
+ mobile: z.boolean().default(false).optional(),
+ communication_emails: z.boolean().default(false).optional(),
+ social_emails: z.boolean().default(false).optional(),
+ marketing_emails: z.boolean().default(false).optional(),
+ security_emails: z.boolean(),
+})
+
+type NotificationsFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ communication_emails: false,
+ marketing_emails: false,
+ social_emails: true,
+ security_emails: true,
+}
+
+export function NotificationsForm() {
+ const form = useForm({
+ resolver: zodResolver(notificationsFormSchema),
+ defaultValues,
+ })
+
+ function onSubmit(data: NotificationsFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/components/profile-form.tsx b/src/components/profile-form.tsx
new file mode 100644
index 00000000..0911ac0f
--- /dev/null
+++ b/src/components/profile-form.tsx
@@ -0,0 +1,192 @@
+"use client"
+
+import Link from "next/link"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useFieldArray, useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { cn } from "@/lib/utils"
+import { toast } from "@/hooks/use-toast"
+
+import { Button } from "@/components/ui/button"
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import { Input } from "@/components/ui/input"
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select"
+import { Textarea } from "@/components/ui/textarea"
+
+const profileFormSchema = z.object({
+ username: z
+ .string()
+ .min(2, {
+ message: "Username must be at least 2 characters.",
+ })
+ .max(30, {
+ message: "Username must not be longer than 30 characters.",
+ }),
+ email: z
+ .string({
+ required_error: "Please select an email to display.",
+ })
+ .email(),
+ bio: z.string().max(160).min(4),
+ urls: z
+ .array(
+ z.object({
+ value: z.string().url({ message: "Please enter a valid URL." }),
+ })
+ )
+ .optional(),
+})
+
+type ProfileFormValues = z.infer
+
+// This can come from your database or API.
+const defaultValues: Partial = {
+ bio: "I own a computer.",
+ urls: [
+ { value: "https://shadcn.com" },
+ { value: "http://twitter.com/shadcn" },
+ ],
+}
+
+export function ProfileForm() {
+ const form = useForm({
+ resolver: zodResolver(profileFormSchema),
+ defaultValues,
+ mode: "onChange",
+ })
+
+ const { fields, append } = useFieldArray({
+ name: "urls",
+ control: form.control,
+ })
+
+ function onSubmit(data: ProfileFormValues) {
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ })
+ }
+
+ return (
+
+
+ )
+}
diff --git a/src/components/sidebar-nav.tsx b/src/components/sidebar-nav.tsx
new file mode 100644
index 00000000..addcfefd
--- /dev/null
+++ b/src/components/sidebar-nav.tsx
@@ -0,0 +1,44 @@
+"use client"
+
+import Link from "next/link"
+import { usePathname } from "next/navigation"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+interface SidebarNavProps extends React.HTMLAttributes {
+ items: {
+ href: string
+ title: string
+ }[]
+}
+
+export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
+ const pathname = usePathname()
+
+ return (
+
+ )
+}
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 00000000..df61a138
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
new file mode 100644
index 00000000..1a37e67d
--- /dev/null
+++ b/src/components/ui/command.tsx
@@ -0,0 +1,155 @@
+"use client"
+
+import * as React from "react"
+import { type DialogProps } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+
+const Command = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Command.displayName = CommandPrimitive.displayName
+
+interface CommandDialogProps extends DialogProps {}
+
+const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+ return (
+
+ )
+}
+
+const CommandInput = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>((props, ref) => (
+
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 00000000..01ff19c7
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,122 @@
+"use client"
+
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
new file mode 100644
index 00000000..a0ec48be
--- /dev/null
+++ b/src/components/ui/popover.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent }
diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx
new file mode 100644
index 00000000..e9bde179
--- /dev/null
+++ b/src/components/ui/radio-group.tsx
@@ -0,0 +1,44 @@
+"use client"
+
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const RadioGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+
+
+
+
+ )
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
new file mode 100644
index 00000000..cbe5a36b
--- /dev/null
+++ b/src/components/ui/select.tsx
@@ -0,0 +1,160 @@
+"use client"
+
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { Check, ChevronDown, ChevronUp } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Select = SelectPrimitive.Root
+
+const SelectGroup = SelectPrimitive.Group
+
+const SelectValue = SelectPrimitive.Value
+
+const SelectTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+ span]:line-clamp-1",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+
+))
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+
+const SelectScrollUpButton = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+
+const SelectScrollDownButton = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+SelectScrollDownButton.displayName =
+ SelectPrimitive.ScrollDownButton.displayName
+
+const SelectContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, position = "popper", ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+
+
+))
+SelectContent.displayName = SelectPrimitive.Content.displayName
+
+const SelectLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SelectLabel.displayName = SelectPrimitive.Label.displayName
+
+const SelectItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+
+ {children}
+
+))
+SelectItem.displayName = SelectPrimitive.Item.displayName
+
+const SelectSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+
+export {
+ Select,
+ SelectGroup,
+ SelectValue,
+ SelectTrigger,
+ SelectContent,
+ SelectLabel,
+ SelectItem,
+ SelectSeparator,
+ SelectScrollUpButton,
+ SelectScrollDownButton,
+}
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
new file mode 100644
index 00000000..12d81c4a
--- /dev/null
+++ b/src/components/ui/separator.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+const Separator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(
+ (
+ { className, orientation = "horizontal", decorative = true, ...props },
+ ref
+ ) => (
+
+ )
+)
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
new file mode 100644
index 00000000..bc69cf2d
--- /dev/null
+++ b/src/components/ui/switch.tsx
@@ -0,0 +1,29 @@
+"use client"
+
+import * as React from "react"
+import * as SwitchPrimitives from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+const Switch = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+Switch.displayName = SwitchPrimitives.Root.displayName
+
+export { Switch }
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
new file mode 100644
index 00000000..9f9a6dc5
--- /dev/null
+++ b/src/components/ui/textarea.tsx
@@ -0,0 +1,24 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+export interface TextareaProps
+ extends React.TextareaHTMLAttributes {}
+
+const Textarea = React.forwardRef(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Textarea.displayName = "Textarea"
+
+export { Textarea }