From 6636083e71844a5fadd44926b8814eaff80be02f Mon Sep 17 00:00:00 2001 From: Rajpreet0 Date: Tue, 5 May 2026 18:08:58 +0200 Subject: [PATCH] added auth comments --- .vscode/settings.json | 3 +- src/hooks/useAuth.ts | 19 ++++++++- .../auth/ui/views/employee-onboaring-view.tsx | 42 +++++++++++++++++-- .../auth/ui/views/forgot-password-view.tsx | 11 +++++ src/modules/auth/ui/views/login-view.tsx | 10 ++++- .../auth/ui/views/register-admin-view.tsx | 31 +++++++++++++- .../auth/ui/views/register-user-view.tsx | 35 ++++++++++++++-- .../auth/ui/views/reset-password-view.tsx | 20 +++++++++ 8 files changed, 159 insertions(+), 12 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 97f0e81..54a56b1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "prisma.pinToPrisma6": true + "prisma.pinToPrisma6": true, + "docwriter.style": "JSDoc" } diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 5d12552..a699cf0 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -68,13 +68,24 @@ export const useAuth = () => { } }; + /** + * Authenticates the user and initializes the session in global state. + * @param email - User's email address + * @param password - Plain text password + * @param remember - Boolean to determine session persistence + * @returns {Promise} - True if login and redirect were successful + */ const loginUser = async (email: string, password: string, remember: boolean) => { + //TODO: Implement 'remember me' persistence logic + setErrors({}); setLoading(true); try { + // 1. Validate inputs userLoginSchema.parse({ email, password }); + // 2. Attempt login via Supabase const { data, error } = await supabase.auth.signInWithPassword({ email, password, @@ -86,16 +97,18 @@ export const useAuth = () => { return false; } - // Update Zustand store + // 3. Update global store + // The User-Store must manually update for Navbar etc. setUser(data.user); toast.success("Erfolgreich eingeloggt 🎉"); - // Navigate to dashboard (no refresh needed - middleware handles it) + // 4. Navigation router.push("/dashboard"); return true; } catch (err) { + // Zod Validation Error (for UI) if (err instanceof ZodError) { const fieldErrors: Record = {}; err.issues.forEach((issue) => { @@ -105,9 +118,11 @@ export const useAuth = () => { setErrors(fieldErrors); return false; } + // Catch-all for Network Errors or Supbase Auth Failure toast.error("Ein unerwarteter Fehler ist aufgetreten."); return false; } finally { + // Reset Loading to free the Login Button setLoading(false); } }; diff --git a/src/modules/auth/ui/views/employee-onboaring-view.tsx b/src/modules/auth/ui/views/employee-onboaring-view.tsx index 2db3823..dd126b6 100644 --- a/src/modules/auth/ui/views/employee-onboaring-view.tsx +++ b/src/modules/auth/ui/views/employee-onboaring-view.tsx @@ -16,6 +16,7 @@ import { } from "@/src/modules/employee/actions/employee-actions"; import { toast } from "sonner"; +// Step 1: Personal + account data validation const step1Schema = z .object({ firstName: z.string().min(2, "Vorname ist zu kurz"), @@ -35,6 +36,7 @@ const step1Schema = z path: ["passwordConfirm"], }); +// Step 2: Additional profile data const step2Schema = z.object({ gender: z .enum(["Men", "Woman", "Various"]) @@ -44,10 +46,8 @@ const step2Schema = z.object({ }); const EmployeeOnboardingView = () => { - const params = useParams(); - const router = useRouter(); - const token = params?.onboardingToken as string; - + // ------------- States ------------- + // Multi-step flow + UI states const [step, setStep] = useState(1); const [loading, setLoading] = useState(false); const [tokenLoading, setTokenLoading] = useState(true); @@ -55,6 +55,7 @@ const EmployeeOnboardingView = () => { const [employeeEmail, setEmployeeEmail] = useState(""); const [errors, setErrors] = useState>({}); + // Form states const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [tel, setTel] = useState(""); @@ -65,7 +66,18 @@ const EmployeeOnboardingView = () => { const [avatarFile, setAvatarFile] = useState(null); const [avatarPreview, setAvatarPreview] = useState(null); + // ------------- Hooks ------------- + const params = useParams(); + const router = useRouter(); + const token = params?.onboardingToken as string; + useEffect(() => { + /** + * Validates onboarding token on page load + * + * - Fetches employee data via token + * - Prevents access if token is invalid/expired + */ const checkToken = async () => { if (!token) { setTokenError("Kein Onboarding-Token gefunden"); @@ -83,6 +95,11 @@ const EmployeeOnboardingView = () => { checkToken(); }, [token]); + /** + * Handles step 1 validation and navigation + * + * @param {React.FormEvent} e - Form submit event + */ const handleNext = (e: React.FormEvent) => { e.preventDefault(); setErrors({}); @@ -101,6 +118,16 @@ const EmployeeOnboardingView = () => { } }; + /** + * Final onboarding submission + * + * - Validates step 2 data + * - Uploads avatar (optional) + * - Completes onboarding via backend + * + * @param {React.FormEvent} e - Form submit event + * @returns {Promise} + */ const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setErrors({}); @@ -120,6 +147,7 @@ const EmployeeOnboardingView = () => { setLoading(true); + // Optional avatar upload before completing onboarding let pbSrc: string | undefined; if (avatarFile) { try { @@ -156,6 +184,7 @@ const EmployeeOnboardingView = () => { } }; + // Loading state while validating token if (tokenLoading) { return (
@@ -164,6 +193,7 @@ const EmployeeOnboardingView = () => { ); } + // Invalid or expired token if (tokenError) { return (
@@ -177,6 +207,7 @@ const EmployeeOnboardingView = () => { return (
+ {/* Sidebar with onboarding steps */}
{
+ {/* Main onboarding form */}

Herzlich Willkommen!

{employeeEmail}

+ {/* Step 1: Personal data + login data*/} {step === 1 && (
@@ -249,6 +282,7 @@ const EmployeeOnboardingView = () => { )} + {/* Step 2: Addtional (optinal) Employee Data */} {step === 2 && (
diff --git a/src/modules/auth/ui/views/forgot-password-view.tsx b/src/modules/auth/ui/views/forgot-password-view.tsx index f89e091..cac4e04 100644 --- a/src/modules/auth/ui/views/forgot-password-view.tsx +++ b/src/modules/auth/ui/views/forgot-password-view.tsx @@ -8,13 +8,23 @@ import { Spinner } from "@/src/components/ui/spinner"; import { createClient } from "@/src/lib/supabase/client"; const ForgotPasswordView = () => { + // ------------- States ------------- const [email, setEmail] = useState(""); const [loading, setLoading] = useState(false); const [emailError, setEmailError] = useState(""); const [emailSent, setEmailSent] = useState(false); + + // ------------- Hooks ------------- const router = useRouter(); const supabase = createClient(); + /** + * Sends a password reset email via Supabase + * Validates input and updates error/success state + * + * @param {React.FormEvent} e - Form submit event + * @returns {Promise} + */ const handleReset = async (e: React.FormEvent) => { e.preventDefault(); setEmailError(""); @@ -27,6 +37,7 @@ const ForgotPasswordView = () => { } const { error } = await supabase.auth.resetPasswordForEmail(email, { + // Redirects user to custom reset-password page after clicking email link redirectTo: `${window.location.origin}/reset-password`, }); diff --git a/src/modules/auth/ui/views/login-view.tsx b/src/modules/auth/ui/views/login-view.tsx index 0edd20d..e0b9b9a 100644 --- a/src/modules/auth/ui/views/login-view.tsx +++ b/src/modules/auth/ui/views/login-view.tsx @@ -12,12 +12,19 @@ import { Spinner } from "@/src/components/ui/spinner"; import { useAuth } from "@/src/hooks/useAuth"; const LoginView = () => { - const { loginUser, errors, loading } = useAuth(); + // ------------- States ------------- const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [remember, setRemember] = useState(false); + + // ------------- Hooks ------------- + const { loginUser, errors, loading } = useAuth(); const router = useRouter(); + /** + * Login of a Manager/User/Employee using loginUser Function from useAuth + * @param e - React Form Event (like klicking) + */ const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); const success = await loginUser(email, password, remember); @@ -92,6 +99,7 @@ const LoginView = () => {
+ {/* Social Login Buttons */}
diff --git a/src/modules/auth/ui/views/register-admin-view.tsx b/src/modules/auth/ui/views/register-admin-view.tsx index 0bec551..c4263d6 100644 --- a/src/modules/auth/ui/views/register-admin-view.tsx +++ b/src/modules/auth/ui/views/register-admin-view.tsx @@ -17,15 +17,28 @@ import { Spinner } from "@/src/components/ui/spinner"; import { useRouter } from "next/navigation"; const RegisterAdmin = () => { + // ------------- States ------------- + // Controls multi-step registration flow const [step, setStep] = useState(1); + + // ------------- Hooks ------------- const { registerAdmin, loading, errors, setErrors } = useAuth(); const router = useRouter(); const formData = useAuthFormStore(); + // Generic handler to update form state const handleChange = (field: string, value: string) => { formData.setField(field, value); }; + /** + * Handles step 1 and 2 validation before moving to step 3 + * + * Uses Zod schema to validate user input + * Maps validation errors to UI-friendly error-object. + * + * @param {React.FormEvent} e - Form submit event + */ const handleNext = (e: React.FormEvent) => { e.preventDefault(); setErrors({}); @@ -48,6 +61,7 @@ const RegisterAdmin = () => { setStep(3); } } catch (err) { + // Transform Zod erros into field-based error object if (err instanceof ZodError) { const fieldErrors: Record = {}; err.issues.forEach((issue) => { @@ -61,6 +75,15 @@ const RegisterAdmin = () => { } }; + /** + * Final step: validates company data and registers admin + * + * - Validates step 3 (company info) + * - Sends full dataset to backend + * - Resets form and redirects on success + * + * @param {React.FormEvent} e - Form submit event + */ const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); setErrors({}); @@ -92,6 +115,7 @@ const RegisterAdmin = () => { }); if (success) { + // Clear persisted form state formData.resetForm(); router.push("/login"); } @@ -111,6 +135,7 @@ const RegisterAdmin = () => { return (
+ {/* Sidebar with step indicator */}
{
+ {/* Main form */}

Sign up for free

+ {/* Step 1: Personal + login data */} {step === 1 && ( { isLabel label="Passwort bestätigen" type="password" - id="register-admin-passwordConform" + id="register-admin-passwordConfirm" value={formData.passwordConfirm} onChange={(e) => handleChange("passwordConfirm", e.target.value)} error={errors.passwordConfirm} @@ -181,6 +208,7 @@ const RegisterAdmin = () => { )} + {/* Step 2: Private address */} {step === 2 && (
{ )} + {/* Step 3: Company data */} {step === 3 && (
{ + // ------------- States ------------- + // Controls multi-step registration flow const [step, setStep] = useState(1); + + // ------------- Hooks ------------- const { registerUser, loading, errors, setErrors } = useAuth(); const router = useRouter(); const formData = useAuthFormStore(); + // Generic handler to update form state const handleChange = (field: string, value: string) => { formData.setField(field, value); }; + /** + * Handles step 1 validation before moving to step 2 + * + * Uses Zod schema to validate user input + * Maps validation errors to UI-friendly error-object. + * + * @param {React.FormEvent} e - Form submit event + */ const handleNext = (e: React.FormEvent) => { e.preventDefault(); setErrors({}); @@ -38,6 +51,7 @@ const RegisterUserView = () => { setStep(2); } } catch (err) { + // Transform Zod erros into field-based error object if (err instanceof ZodError) { const fieldErrors: Record = {}; err.issues.forEach((issue) => { @@ -51,6 +65,15 @@ const RegisterUserView = () => { } }; + /** + * Final registration handler + * + * - Sends full form data to backend + * - Resets form on success + * - Redirects user to login + * + * @param {React.FormEvent} e - Form submit event + */ const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); const success = await registerUser({ @@ -66,6 +89,7 @@ const RegisterUserView = () => { }); if (success) { + // Clear persisted form state formData.resetForm(); router.push("/login"); } @@ -73,7 +97,7 @@ const RegisterUserView = () => { return (
- {/* Sidebar */} + {/* Sidebar with step indicator */}
{
+ {/* Main registration form */}

Sign up for free

+ {/* Step 1: Basic account data */} {step === 1 && ( { isLabel label="Passwort bestätigen" type="password" - id="register-user-passwordConform" + id="register-user-passwordConfirm" value={formData.passwordConfirm} onChange={(e) => handleChange("passwordConfirm", e.target.value)} error={errors.passwordConfirm} @@ -137,6 +163,7 @@ const RegisterUserView = () => { )} + {/* Step 2: Additional user details */} {step === 2 && (
{ label="Geburtsdatum" type="date" id="register-user-birthday" - max={new Date().toISOString().split("T")[0]} + max={new Date().toISOString().split("T")[0]} // Prevent future dates value={formData.birthday} onChange={(e) => handleChange("birthday", e.target.value)} error={errors.birthday} @@ -158,6 +185,7 @@ const RegisterUserView = () => { onPlzChange={(v) => handleChange("plz", v)} onPlaceChange={(v) => handleChange("place", v)} onSelect={({ street, plz, place }) => { + // Autofill address fields from selection handleChange("street", street); handleChange("plz", plz); handleChange("place", place); @@ -182,6 +210,7 @@ const RegisterUserView = () => {
+ {/* Social login options */}
diff --git a/src/modules/auth/ui/views/reset-password-view.tsx b/src/modules/auth/ui/views/reset-password-view.tsx index 20209de..1cc89c4 100644 --- a/src/modules/auth/ui/views/reset-password-view.tsx +++ b/src/modules/auth/ui/views/reset-password-view.tsx @@ -8,16 +8,21 @@ import { Spinner } from "@/src/components/ui/spinner"; import { createClient } from "@/src/lib/supabase/client"; const ResetPasswordView = () => { + // ------------- States ------------- const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [loading, setLoading] = useState(false); const [passwordError, setPasswordError] = useState(""); const [confirmPasswordError, setConfirmPasswordError] = useState(""); const [resetSuccess, setResetSuccess] = useState(false); + + // ------------- Hooks ------------- const router = useRouter(); const supabase = createClient(); useEffect(() => { + // Ensure user is authenticated via reset link session + // If not, redirect to login (invalid or expired link) supabase.auth.getUser().then(({ data }) => { if (!data.user) { router.replace("/login"); @@ -25,6 +30,17 @@ const ResetPasswordView = () => { }); }, [router, supabase]); + /** + * Handles password reset submission + * + * - Validation password rules + * - Updates password via Supabase + * - Signs user out after success (security best practice) + * - Redirects to login after short delay + * + * @param {React.FormEvent} e - Form submit event + * @returns {Promise} + */ const handleResetPassword = async (e: React.FormEvent) => { e.preventDefault(); @@ -32,6 +48,7 @@ const ResetPasswordView = () => { setPasswordError(""); setConfirmPasswordError(""); + // Basic Password validation if (password.length < 8) { setPasswordError("Mindestens 8 Zeichen"); setLoading(false); @@ -44,6 +61,7 @@ const ResetPasswordView = () => { return; } + // Update password for the currently authenticated user const { error } = await supabase.auth.updateUser({ password }); if (error) { @@ -54,12 +72,14 @@ const ResetPasswordView = () => { setResetSuccess(true); + // Force logout after password change to refresh session try { await supabase.auth.signOut(); } catch (signOutError) { console.error("Sign out error:", signOutError); } + // Redirect to login after success feedback setTimeout(() => { router.push("/login"); }, 2500);