mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-19 19:21:33 +10:00
refactor(v4.0.0-alpha): beginning of a new era
This commit is contained in:
231
apps/client/src/pages/dashboard/settings/_sections/account.tsx
Normal file
231
apps/client/src/pages/dashboard/settings/_sections/account.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user