215 lines
10 KiB
TypeScript
215 lines
10 KiB
TypeScript
import { useForm, SubmitHandler } from "react-hook-form"
|
|
import { rpc } from "../api"
|
|
import { z } from "zod"
|
|
import { zodResolver } from "@hookform/resolvers/zod"
|
|
import { Layout2 } from "@/components/Layout2"
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardDescription,
|
|
CardFooter,
|
|
} from "@/components/ui/card"
|
|
import {
|
|
Form,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormControl,
|
|
FormMessage,
|
|
} from "@/components/ui/form"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Button } from "@/components/ui/button"
|
|
import { ArrowLeft, EyeOffIcon, EyeIcon } from "lucide-react"
|
|
import { cn } from "@/lib/utils"
|
|
import { useNavigate } from "react-router-dom"
|
|
import { useState } from "react"
|
|
import { Header } from "../components"
|
|
|
|
const changePasswordSchema = z
|
|
.object({
|
|
currentPassword: z.string().min(1, "Current password is required"),
|
|
newPassword: z.string().min(1, "New password is required"),
|
|
newPasswordConfirm: z.string(),
|
|
})
|
|
.refine((data) => data.newPassword === data.newPasswordConfirm, {
|
|
message: "New password and confirm new password must match",
|
|
path: ["newPasswordConfirm"],
|
|
})
|
|
|
|
type FormFields = z.infer<typeof changePasswordSchema>
|
|
|
|
export const ChangePassword = () => {
|
|
const navigate = useNavigate()
|
|
|
|
const [showCurrentPassword, setShowCurrentPassword] = useState(false)
|
|
const [showNewPassword, setShowNewPassword] = useState(false)
|
|
const [showConfirmNewPassword, setShowConfirmNewPassword] = useState(false)
|
|
|
|
const form = useForm<FormFields>({
|
|
resolver: zodResolver(changePasswordSchema),
|
|
})
|
|
const {
|
|
handleSubmit,
|
|
reset,
|
|
formState: { isSubmitting },
|
|
control,
|
|
setError,
|
|
} = form
|
|
|
|
const onSubmit: SubmitHandler<FormFields> = async ({
|
|
currentPassword,
|
|
newPassword,
|
|
}) => {
|
|
try {
|
|
// await rpc.client.changePassword(currentPassword, newPassword)
|
|
reset()
|
|
navigate("/settings")
|
|
} catch (e) {
|
|
setError("currentPassword", {
|
|
type: "server",
|
|
message: "Current password is incorrect",
|
|
})
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Layout2 innerClassName="bg-muted">
|
|
<Header />
|
|
<Form {...form}>
|
|
<form className="relative grow" onSubmit={handleSubmit(onSubmit)}>
|
|
<Card className="border-none bg-muted h-full flex flex-col flex-grow w-full max-w-md shadow-none">
|
|
<CardHeader>
|
|
<CardTitle>Change Password</CardTitle>
|
|
</CardHeader>
|
|
|
|
<CardContent className="content-center flex flex-col flex-grow justify-start">
|
|
<FormField
|
|
control={control}
|
|
name="currentPassword"
|
|
render={({ field, fieldState }) => (
|
|
<FormItem className="grid gap-2 mb-4">
|
|
<FormControl>
|
|
<div className="relative">
|
|
<Input
|
|
{...field}
|
|
aria-label="Current Password"
|
|
type={showCurrentPassword ? "text" : "password"}
|
|
className="pr-10"
|
|
placeholder="Enter current password"
|
|
/>
|
|
<button
|
|
className="absolute -translate-y-1/2 top-1/2 -translate-x-1/2 right-2"
|
|
onClick={() => setShowCurrentPassword(!showCurrentPassword)}
|
|
>
|
|
{showCurrentPassword ? (
|
|
<EyeOffIcon className="w-5 h-5" />
|
|
) : (
|
|
<EyeIcon className="w-5 h-5" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</FormControl>
|
|
{fieldState.invalid && fieldState.error?.message && (
|
|
<span className="text-xs text-red-500">
|
|
{fieldState.error?.message}
|
|
</span>
|
|
)}
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={control}
|
|
name="newPassword"
|
|
render={({ field, fieldState }) => (
|
|
<FormItem className="grid gap-2 mb-4">
|
|
<FormControl>
|
|
<div className="relative">
|
|
<Input
|
|
{...field}
|
|
aria-label="New Password"
|
|
type={showNewPassword ? "text" : "password"}
|
|
className="pr-10"
|
|
placeholder="Enter new password"
|
|
/>
|
|
<button
|
|
className="absolute -translate-y-1/2 top-1/2 -translate-x-1/2 right-2"
|
|
onClick={() => setShowNewPassword(!showNewPassword)}
|
|
>
|
|
{showNewPassword ? (
|
|
<EyeOffIcon className="w-5 h-5" />
|
|
) : (
|
|
<EyeIcon className="w-5 h-5" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</FormControl>
|
|
{fieldState.invalid && fieldState.error?.message && (
|
|
<span className="text-xs text-red-500">
|
|
{fieldState.error?.message}
|
|
</span>
|
|
)}
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={control}
|
|
name="newPasswordConfirm"
|
|
render={({ field, fieldState }) => (
|
|
<FormItem className="grid gap-2 mb-4">
|
|
<FormControl>
|
|
<div className="relative">
|
|
<Input
|
|
{...field}
|
|
aria-label="Confirm New Password"
|
|
type={showConfirmNewPassword ? "text" : "password"}
|
|
className="pr-10"
|
|
placeholder="Confirm new password"
|
|
/>
|
|
<button
|
|
onClick={() => setShowConfirmNewPassword(!showConfirmNewPassword)}
|
|
className="absolute -translate-y-1/2 top-1/2 -translate-x-1/2 right-2"
|
|
>
|
|
{showConfirmNewPassword ? (
|
|
<EyeOffIcon className="w-5 h-5" />
|
|
) : (
|
|
<EyeIcon className="w-5 h-5" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</FormControl>
|
|
{fieldState.invalid && fieldState.error?.message && (
|
|
<span className="text-xs text-red-500">
|
|
{fieldState.error?.message}
|
|
</span>
|
|
)}
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</CardContent>
|
|
<CardFooter className="flex flex-col space-y-4 justify-end">
|
|
<Button disabled={isSubmitting} type="submit" size="full" className={cn("text-lg py-3")}>
|
|
Change Password
|
|
</Button>
|
|
<Button
|
|
disabled={isSubmitting}
|
|
variant="secondary"
|
|
size="full"
|
|
className={cn("text-lg py-3")}
|
|
onClick={() => navigate("/settings")}
|
|
>
|
|
<ArrowLeft
|
|
className="w-5 h-5 mr-2"
|
|
aria-hidden="true"
|
|
/>
|
|
Back
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
</form>
|
|
</Form>
|
|
</Layout2>
|
|
)
|
|
}
|