Files
encoach_frontend_new_v3/src/pages/ResetPassword.tsx
Yamen Ahmad 88a385ed07 feat: initial frontend codebase — EnCoach v3
React 18 + TypeScript + Vite 5 + Tailwind CSS + shadcn/ui frontend:
- Multi-role auth: student, teacher, admin, corporate, agent, developer
- Student portal: dashboard, courses, adaptive learning, exams, placement
- Teacher portal: courses, assignments, grading, attendance, AI workbench
- Admin portal: 60+ management pages (LMS, exams, users, entities, AI)
- AI-powered features: study coach, generation page, gap analysis, TTS
- Exam system: session, auto-save, results, grading queue
- Full IELTS workflow: reading/listening/writing/speaking modules

Made-with: Cursor
2026-04-11 15:47:32 +04:00

148 lines
5.4 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useMutation, useQuery } from "@tanstack/react-query";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
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 { authService } from "@/services/auth.service";
import { brandingService } from "@/services/branding.service";
import { cn } from "@/lib/utils";
import { toast } from "sonner";
const schema = z
.object({
password: z.string().min(8, "At least 8 characters"),
confirm: z.string(),
})
.refine((d) => d.password === d.confirm, { message: "Passwords must match", path: ["confirm"] });
type FormValues = z.infer<typeof schema>;
function strengthLabel(p: string): "weak" | "medium" | "strong" {
let s = 0;
if (p.length >= 8) s++;
if (p.length >= 12) s++;
if (/[A-Z]/.test(p) && /[a-z]/.test(p)) s++;
if (/\d/.test(p)) s++;
if (/[^A-Za-z0-9]/.test(p)) s++;
if (s <= 2) return "weak";
if (s <= 4) return "medium";
return "strong";
}
export default function ResetPassword() {
const [searchParams] = useSearchParams();
const token = searchParams.get("token") ?? "";
const navigate = useNavigate();
const { data: branding } = useQuery({
queryKey: ["branding", "current"],
queryFn: () => brandingService.getCurrentBranding(),
});
const entityName = branding?.entity_name ?? "your organization";
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { password: "", confirm: "" },
});
const pwd = form.watch("password");
const strength = useMemo(() => strengthLabel(pwd), [pwd]);
useEffect(() => {
const root = document.documentElement;
if (branding?.primary_color) root.style.setProperty("--encoach-brand-primary", branding.primary_color);
if (branding?.secondary_color) root.style.setProperty("--encoach-brand-secondary", branding.secondary_color);
if (branding?.background_color) root.style.setProperty("--encoach-brand-bg", branding.background_color);
return () => {
root.style.removeProperty("--encoach-brand-primary");
root.style.removeProperty("--encoach-brand-secondary");
root.style.removeProperty("--encoach-brand-bg");
};
}, [branding]);
const submitMut = useMutation({
mutationFn: (values: FormValues) => authService.setInvitePassword({ token, password: values.password }),
onSuccess: () => {
toast.success("Password saved.");
navigate("/student/placement", { replace: true });
},
onError: (e) => toast.error(e instanceof Error ? e.message : "Could not set password"),
});
const onSubmit = (values: FormValues) => {
if (!token) {
toast.error("Missing invite token.");
return;
}
submitMut.mutate(values);
};
return (
<div
className="min-h-screen flex items-center justify-center p-6"
style={{
backgroundColor: "var(--encoach-brand-bg, hsl(var(--background)))",
}}
>
<Card className="w-full max-w-md border shadow-lg">
<CardHeader className="text-center space-y-1">
<CardTitle style={{ color: "var(--encoach-brand-primary, hsl(var(--primary)))" }}>
Welcome to {entityName}&apos;s Learning Platform
</CardTitle>
<CardDescription>Set a new password</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>New password</FormLabel>
<FormControl>
<Input type="password" autoComplete="new-password" {...field} />
</FormControl>
<div
className={cn(
"text-xs font-medium",
strength === "weak" && "text-red-600",
strength === "medium" && "text-amber-600",
strength === "strong" && "text-green-600",
)}
>
{pwd ? `${strength.charAt(0).toUpperCase() + strength.slice(1)} password` : " "}
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="confirm"
render={({ field }) => (
<FormItem>
<FormLabel>Confirm password</FormLabel>
<FormControl>
<Input type="password" autoComplete="new-password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full" disabled={submitMut.isPending}>
Set Password & Continue
</Button>
</form>
</Form>
</CardContent>
</Card>
</div>
);
}