From 230a81dc07b49b2f7fcfacf2627a5764de186fab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yusuf=20Akg=C3=BCl?= <s86116@bht-berlin.de>
Date: Thu, 13 Jul 2023 00:08:17 +0200
Subject: [PATCH] settings

---
 app/(content)/(user)/help/page.tsx     |  28 +++---
 app/(content)/(user)/settings/page.tsx |  37 +++++++-
 app/api/username/route.ts              |  51 +++++++++++
 components/nav-mobile.tsx              |   2 +-
 components/user-name-form.tsx          | 114 +++++++++++++++++++++++++
 lib/config/dashboard.ts                |  10 +--
 lib/validations/username.ts            |   9 ++
 7 files changed, 224 insertions(+), 27 deletions(-)
 create mode 100644 app/api/username/route.ts
 create mode 100644 components/user-name-form.tsx
 create mode 100644 lib/validations/username.ts

diff --git a/app/(content)/(user)/help/page.tsx b/app/(content)/(user)/help/page.tsx
index 20507e4..b7ccf79 100644
--- a/app/(content)/(user)/help/page.tsx
+++ b/app/(content)/(user)/help/page.tsx
@@ -5,7 +5,7 @@ export default function HelpPage() {
     return (
         <GlobalLayout
             mainContent={
-                <Card>
+                <Card className="w-full overflow-hidden p-0 md:p-6">
                     <CardHeader>
                         <CardTitle>
                             Help / FAQ
@@ -18,45 +18,39 @@ export default function HelpPage() {
                             Fill in the required information and follow the prompts to complete the registration process.
                         </p>
 
-                        <p className="font-bold pt-6">2. How can I reset my password?</p>
-                        <p>
-                            If you forgot your password, go to the login page and click on the &quot;Forgot Password&quot; link.
-                            Enter your email address and follow the instructions sent to your email to reset your password.
-                        </p>
-
-                        <p className="font-bold pt-6">3. How can I create a new post?</p>
+                        <p className="font-bold pt-6">2. How can I create a new post?</p>
                         <p>
                             To create a new post, you have multiple options depending on where you are on the website.
-                            If you&apos;re on the homepage, community page, or your profile page, you&apos;ll find a text field where you can write your post.
+                            If you&apos;re on the homepage, or your profile page, you&apos;ll find a text field where you can write your post.
                             Once you&apos;re done, simply click the &quot;Post&quot; button to publish your content and share it with others.
                         </p>
 
-                        <p className="font-bold pt-6">4. How can I edit or delete my post?</p>
+                        <p className="font-bold pt-6">3. How can I delete my post?</p>
                         <p>
-                            To edit or delete your post, go to the Home page or your profile page and locate your post.
-                            Depending on the platform, there may be options to edit or delete the post.
+                            To  delete your post, go to the Home page or your profile page and locate your post.
+                            Depending on the platform, there may be options to delete the post.
                             Click on the respective option and follow the prompts to make the desired changes.
                         </p>
 
-                        <p className="font-bold pt-6">5. How do I follow a community or user?</p>
+                        <p className="font-bold pt-6">4. How do I follow a user?</p>
                         <p>
-                            To follow a community or user, visit their profile or community page and look for the &quot;Follow&quot; button.
+                            To follow a user, visit their profile page and look for the &quot;Follow&quot; button.
                             Click on it, and you&apos;ll start receiving updates and notifications from them.
                         </p>
 
-                        <p className="font-bold pt-6">6. How do I customize my profile?</p>
+                        <p className="font-bold pt-6">5. How do I customize my profile?</p>
                         <p>
                             To customize your profile, go to the settings of your account.
                             There, you can upload a profile picture, add a bio, update your personal information, and adjust privacy settings according to your preferences.
                         </p>
 
-                        <p className="font-bold pt-6">7. Can I change my username?</p>
+                        <p className="font-bold pt-6">6. Can I change my username?</p>
                         <p>
                             In most cases, you can change your username by going to the account settings.
                             Look for the option to edit your name and follow the provided instructions to make the change.
                         </p>
 
-                        <p className="font-bold pt-6"> 8. How do I delete my account?</p>
+                        <p className="font-bold pt-6">7. How do I delete my account?</p>
                         <p>
                             If you wish to delete your account, go to the account settings “Account Settings” section and look for the option to delete your account.
                             Follow the provided steps and confirm your decision to permanently delete your account. Please note that this action is irreversible and will result in the loss of all associated data.
diff --git a/app/(content)/(user)/settings/page.tsx b/app/(content)/(user)/settings/page.tsx
index b72aec2..e4c048e 100644
--- a/app/(content)/(user)/settings/page.tsx
+++ b/app/(content)/(user)/settings/page.tsx
@@ -1,7 +1,36 @@
-export default function Settings() {
+import { redirect } from 'next/navigation'
+
+import { GlobalLayout } from '@/components/global-layout'
+import { Card } from '@/components/ui/card'
+import { UserNameForm } from '@/components/user-name-form'
+import { authOptions } from '@/lib/auth'
+import { getCurrentUser } from '@/lib/session'
+
+export const metadata = {
+    title: 'Settings',
+    description: 'Manage account and website settings.',
+}
+
+export default async function SettingsPage() {
+    const session = await getCurrentUser()
+
+    if (!session) {
+        redirect(authOptions?.pages?.signIn || '/login')
+    }
+
     return (
-        <div>
-            <h1>Settings Page WIP</h1>
-        </div>
+        <GlobalLayout
+            mainContent={
+                <Card className="w-full overflow-hidden p-6 md:p-12 space-y-6">
+                    <h1 className="text-2xl font-semibold leading-none tracking-tight">Settings</h1>
+                    <UserNameForm
+                        user={{
+                            id: session.id,
+                            username: session.username || '',
+                        }}
+                    />
+                </Card>
+            }
+        />
     )
 }
\ No newline at end of file
diff --git a/app/api/username/route.ts b/app/api/username/route.ts
new file mode 100644
index 0000000..396daa0
--- /dev/null
+++ b/app/api/username/route.ts
@@ -0,0 +1,51 @@
+import { db } from '@/lib/db'
+import { getCurrentUser } from '@/lib/session'
+import { UsernameValidator } from '@/lib/validations/username'
+import { z } from 'zod'
+
+export async function PATCH(req: Request) {
+    try {
+        const session = await getCurrentUser()
+
+        if (!session) {
+            return new Response('Unauthorized', { status: 401 })
+        }
+
+        const body = await req.json()
+        const { name } = UsernameValidator.parse(body)
+
+        // check if username is taken
+        const username = await db.user.findFirst({
+            where: {
+                username: name,
+            },
+        })
+
+        if (username) {
+            return new Response('Username is taken', { status: 409 })
+        }
+
+        // update username
+        await db.user.update({
+            where: {
+                id: session.id,
+            },
+            data: {
+                username: name,
+            },
+        })
+
+        return new Response('OK')
+    } catch (error) {
+        (error)
+
+        if (error instanceof z.ZodError) {
+            return new Response(error.message, { status: 400 })
+        }
+
+        return new Response(
+            'Could not update username at this time. Please try later',
+            { status: 500 }
+        )
+    }
+}
\ No newline at end of file
diff --git a/components/nav-mobile.tsx b/components/nav-mobile.tsx
index 2196f5b..3d24e88 100644
--- a/components/nav-mobile.tsx
+++ b/components/nav-mobile.tsx
@@ -26,7 +26,7 @@ export const MobileNav = ({ items, user }: { items: SidebarNavItem[], user: User
                 if (item.title === "Settings") {
                     return
                 }
-                if (item.title === "Help") {
+                if (item.title === "Help / FAQ") {
                     return
                 }
                 return (
diff --git a/components/user-name-form.tsx b/components/user-name-form.tsx
new file mode 100644
index 0000000..a9a6bb6
--- /dev/null
+++ b/components/user-name-form.tsx
@@ -0,0 +1,114 @@
+'use client'
+
+import { zodResolver } from '@hookform/resolvers/zod'
+import { User } from '@prisma/client'
+import { useRouter } from 'next/navigation'
+import * as React from 'react'
+import { useForm } from 'react-hook-form'
+import * as z from 'zod'
+
+import { cn } from '@/lib/utils'
+import { UsernameValidator } from '@/lib/validations/username'
+import { useMutation } from '@tanstack/react-query'
+import axios, { AxiosError } from 'axios'
+import { Icons } from './icons'
+import { Button } from './ui/button'
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './ui/card'
+import { Input } from './ui/input'
+import { Label } from './ui/label'
+import { toast } from './ui/use-toast'
+
+interface UserNameFormProps extends React.HTMLAttributes<HTMLFormElement> {
+    user: Pick<User, 'id' | 'username'>
+}
+
+type FormData = z.infer<typeof UsernameValidator>
+
+export function UserNameForm({ user, className, ...props }: UserNameFormProps) {
+    const router = useRouter()
+    const {
+        handleSubmit,
+        register,
+        formState: { errors },
+    } = useForm<FormData>({
+        resolver: zodResolver(UsernameValidator),
+        defaultValues: {
+            name: user?.username || '',
+        },
+    })
+
+    const { mutate: updateUsername, isLoading } = useMutation({
+        mutationFn: async ({ name }: FormData) => {
+            const payload: FormData = { name }
+
+            const { data } = await axios.patch(`/api/username/`, payload)
+            return data
+        },
+        onError: (err) => {
+            if (err instanceof AxiosError) {
+                if (err.response?.status === 409) {
+                    return toast({
+                        title: 'Username already taken.',
+                        description: 'Please choose another username.',
+                        variant: 'destructive',
+                    })
+                }
+            }
+
+            return toast({
+                title: 'Something went wrong.',
+                description: 'Your username was not updated. Please try again.',
+                variant: 'destructive',
+            })
+        },
+        onSuccess: () => {
+            toast({
+                description: 'Your username has been updated.',
+            })
+            router.refresh()
+        },
+    })
+
+    return (
+        <form
+            className={cn(className)}
+            onSubmit={handleSubmit((e) => updateUsername(e))}
+            {...props}>
+            <Card>
+                <CardHeader>
+                    <CardTitle>Your username</CardTitle>
+                    <CardDescription>
+                        Please enter a display name you are comfortable with.
+                    </CardDescription>
+                </CardHeader>
+                <CardContent>
+                    <div className='relative grid gap-1'>
+                        <div className='absolute top-0 left-0 w-8 h-10 grid place-items-center'>
+                            <span className='text-sm text-zinc-400'>@</span>
+                        </div>
+                        <Label className='sr-only' htmlFor='name'>
+                            Name
+                        </Label>
+                        <Input
+                            id='name'
+                            className='w-[400px] pl-6'
+                            size={32}
+                            {...register('name')}
+                        />
+                        {errors?.name && (
+                            <p className='px-1 text-xs text-red-600'>{errors.name.message}</p>
+                        )}
+                    </div>
+                </CardContent>
+                <CardFooter>
+                    <Button disabled={isLoading} type="submit">
+                        {isLoading && (
+                            <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
+                        )}
+                        Change name
+                    </Button>
+                </CardFooter>
+            </Card>
+        </form>
+    )
+}
\ No newline at end of file
diff --git a/lib/config/dashboard.ts b/lib/config/dashboard.ts
index 8f3fda3..bdc91b9 100644
--- a/lib/config/dashboard.ts
+++ b/lib/config/dashboard.ts
@@ -32,11 +32,11 @@ export const dashboardConfig: DashboardConfig = {
             href: "",
             icon: "user",
         },
-        // {
-        //     title: "Settings",
-        //     href: "/settings",
-        //     icon: "settings",
-        // },
+        {
+            title: "Settings",
+            href: "/settings",
+            icon: "settings",
+        },
         {
             title: "Help / FAQ",
             href: "/help",
diff --git a/lib/validations/username.ts b/lib/validations/username.ts
new file mode 100644
index 0000000..8d5202d
--- /dev/null
+++ b/lib/validations/username.ts
@@ -0,0 +1,9 @@
+import { z } from 'zod'
+
+export const UsernameValidator = z.object({
+    name: z
+        .string()
+        .min(3)
+        .max(32)
+        .regex(/^[a-zA-Z0-9_]+$/),
+})
\ No newline at end of file
-- 
GitLab