diff options
Diffstat (limited to 'src/app/settings/password-form.tsx')
| -rw-r--r-- | src/app/settings/password-form.tsx | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/src/app/settings/password-form.tsx b/src/app/settings/password-form.tsx new file mode 100644 index 0000000..2377408 --- /dev/null +++ b/src/app/settings/password-form.tsx @@ -0,0 +1,182 @@ +"use client" + +import { useState } from "react" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { Eye, EyeOff, Loader2, Lock, Save } from "lucide-react" +import { toast } from "sonner" + +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { passwordChangeSchema, type PasswordChangeInput } from "@/lib/validation" + +export function PasswordForm() { + const [showCurrentPassword, setShowCurrentPassword] = useState(false) + const [showNewPassword, setShowNewPassword] = useState(false) + const [showConfirmPassword, setShowConfirmPassword] = useState(false) + const [isLoading, setIsLoading] = useState(false) + + const form = useForm<PasswordChangeInput>({ + resolver: zodResolver(passwordChangeSchema), + defaultValues: { + currentPassword: "", + newPassword: "", + confirmPassword: "", + }, + }) + + const onSubmit = async (data: PasswordChangeInput) => { + setIsLoading(true) + + try { + const response = await fetch("/api/user/password", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + currentPassword: data.currentPassword, + newPassword: data.newPassword, + }), + }) + + const result = await response.json() + + if (!response.ok) { + toast.error(result.error || "Failed to update password") + return + } + + toast.success("Password updated successfully!") + form.reset() + } catch { + toast.error("An unexpected error occurred") + } finally { + setIsLoading(false) + } + } + + return ( + <Card> + <CardHeader> + <CardTitle className="flex items-center"> + <Lock className="mr-2 h-5 w-5" /> + Change Password + </CardTitle> + <CardDescription> + Update your password to keep your account secure + </CardDescription> + </CardHeader> + <CardContent> + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> + <FormField + control={form.control} + name="currentPassword" + render={({ field }) => ( + <FormItem> + <FormLabel>Current Password</FormLabel> + <FormControl> + <div className="relative"> + <Input + type={showCurrentPassword ? "text" : "password"} + placeholder="Enter your current password" + {...field} + /> + <Button + type="button" + variant="ghost" + size="sm" + className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent" + onClick={() => setShowCurrentPassword(!showCurrentPassword)} + > + {showCurrentPassword ? ( + <EyeOff className="h-4 w-4" /> + ) : ( + <Eye className="h-4 w-4" /> + )} + </Button> + </div> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="newPassword" + render={({ field }) => ( + <FormItem> + <FormLabel>New Password</FormLabel> + <FormControl> + <div className="relative"> + <Input + type={showNewPassword ? "text" : "password"} + placeholder="Enter your new password" + {...field} + /> + <Button + type="button" + variant="ghost" + size="sm" + className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent" + onClick={() => setShowNewPassword(!showNewPassword)} + > + {showNewPassword ? ( + <EyeOff className="h-4 w-4" /> + ) : ( + <Eye className="h-4 w-4" /> + )} + </Button> + </div> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="confirmPassword" + render={({ field }) => ( + <FormItem> + <FormLabel>Confirm New Password</FormLabel> + <FormControl> + <div className="relative"> + <Input + type={showConfirmPassword ? "text" : "password"} + placeholder="Confirm your new password" + {...field} + /> + <Button + type="button" + variant="ghost" + size="sm" + className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent" + onClick={() => setShowConfirmPassword(!showConfirmPassword)} + > + {showConfirmPassword ? ( + <EyeOff className="h-4 w-4" /> + ) : ( + <Eye className="h-4 w-4" /> + )} + </Button> + </div> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + <div className="text-xs text-muted-foreground"> + Password must contain at least 8 characters with uppercase, lowercase, and a number. + </div> + <Button type="submit" disabled={isLoading}> + {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} + <Save className="mr-2 h-4 w-4" /> + Update Password + </Button> + </form> + </Form> + </CardContent> + </Card> + ) +} |
