refactor(v4.0.0-alpha): beginning of a new era

This commit is contained in:
Amruth Pillai
2023-11-05 12:31:42 +01:00
parent 0ba6a444e2
commit 22933bd412
505 changed files with 81829 additions and 0 deletions

View File

@ -0,0 +1,231 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Check, UploadSimple, Warning } from "@phosphor-icons/react";
import { UpdateUserDto, updateUserSchema } from "@reactive-resume/dto";
import {
Button,
buttonVariants,
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
} from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useRef } from "react";
import { useForm } from "react-hook-form";
import { UserAvatar } from "@/client/components/user-avatar";
import { useToast } from "@/client/hooks/use-toast";
import { useResendVerificationEmail } from "@/client/services/auth";
import { useUploadImage } from "@/client/services/storage";
import { useUpdateUser, useUser } from "@/client/services/user";
export const AccountSettings = () => {
const { user } = useUser();
const { toast } = useToast();
const { updateUser, loading } = useUpdateUser();
const { uploadImage, loading: isUploading } = useUploadImage();
const { resendVerificationEmail } = useResendVerificationEmail();
const inputRef = useRef<HTMLInputElement>(null);
const form = useForm<UpdateUserDto>({
resolver: zodResolver(updateUserSchema),
defaultValues: {
picture: "",
name: "",
username: "",
email: "",
},
});
useEffect(() => {
user && onReset();
}, [user]);
const onReset = () => {
if (!user) return;
form.reset({
picture: user.picture ?? "",
name: user.name,
username: user.username,
email: user.email,
});
};
const onSubmit = async (data: UpdateUserDto) => {
if (!user) return;
// Check if email has changed and display a toast message to confirm the email change
if (user.email !== data.email) {
toast({
variant: "info",
title: "Check your email for the confirmation link to update your email address.",
});
}
await updateUser({
name: data.name,
email: data.email,
picture: data.picture,
username: data.username,
});
form.reset(data);
};
const onSelectImage = async (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files.length > 0) {
const file = event.target.files[0];
const response = await uploadImage(file);
const url = response.data;
await updateUser({ picture: url });
}
};
const onResendVerificationEmail = async () => {
const data = await resendVerificationEmail();
toast({ variant: "success", title: data.message });
};
if (!user) return null;
return (
<div className="space-y-6">
<div>
<h3 className="text-2xl font-bold leading-relaxed tracking-tight">Account</h3>
<p className="leading-relaxed opacity-75">
Here, you can update your account information such as your profile picture, name and
username.
</p>
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-6 sm:grid-cols-2">
<FormField
name="picture"
control={form.control}
render={({ field, fieldState: { error } }) => (
<div className={cn("flex items-end gap-x-4 sm:col-span-2", error && "items-center")}>
<UserAvatar />
<FormItem className="flex-1">
<FormLabel>Picture</FormLabel>
<FormControl>
<Input placeholder="https://..." {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
{!user.picture && (
<>
<input hidden type="file" ref={inputRef} onChange={onSelectImage} />
<motion.button
disabled={isUploading}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => inputRef.current?.click()}
className={cn(buttonVariants({ size: "icon", variant: "ghost" }))}
>
<UploadSimple />
</motion.button>
</>
)}
</div>
)}
/>
<FormField
name="name"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
</FormItem>
)}
/>
<FormField
name="username"
control={form.control}
render={({ field, fieldState }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
{fieldState.error && (
<FormDescription className="text-error">
{fieldState.error.message}
</FormDescription>
)}
</FormItem>
)}
/>
<FormField
name="email"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormDescription
className={cn(
"flex items-center gap-x-1.5 font-medium opacity-100",
user.emailVerified ? "text-success-accent" : "text-warning-accent",
)}
>
{user.emailVerified ? <Check size={12} /> : <Warning size={12} />}
{user.emailVerified ? "Verified" : "Unverified"}
{!user.emailVerified && (
<Button
variant="link"
className="h-auto text-xs"
onClick={onResendVerificationEmail}
>
Resend confirmation link
</Button>
)}
</FormDescription>
</FormItem>
)}
/>
<AnimatePresence presenceAffectsLayout>
{form.formState.isDirty && (
<motion.div
layout
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -10 }}
className="flex items-center space-x-2 self-center sm:col-start-2"
>
<Button type="submit" disabled={loading}>
Save Changes
</Button>
<Button type="reset" variant="ghost" onClick={onReset}>
Reset
</Button>
</motion.div>
)}
</AnimatePresence>
</form>
</Form>
</div>
);
};