Skip to content
Snippets Groups Projects
user-name-form.tsx 3.92 KiB
Newer Older
Yusuf Akgül's avatar
Yusuf Akgül committed
'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>
    )
}