Merge pull request #90 from Since-3/dokumentation/add-comments

added auth comments
This commit is contained in:
Rajpreet0 2026-05-05 18:11:42 +02:00 • committed by GitHub
commit 384f62d46a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 159 additions and 12 deletions

View file

@ -1,3 +1,4 @@
{
"prisma.pinToPrisma6": true
"prisma.pinToPrisma6": true,
"docwriter.style": "JSDoc"
}

View file

@ -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<boolean>} - 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<string, string> = {};
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);
}
};

View file

@ -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<Record<string, string>>({});
// Form states
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [tel, setTel] = useState("");
@ -65,7 +66,18 @@ const EmployeeOnboardingView = () => {
const [avatarFile, setAvatarFile] = useState<File | null>(null);
const [avatarPreview, setAvatarPreview] = useState<string | null>(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<boolean>}
*/
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 (
<div className="flex min-h-screen justify-center items-center">
@ -164,6 +193,7 @@ const EmployeeOnboardingView = () => {
);
}
// Invalid or expired token
if (tokenError) {
return (
<div className="flex min-h-screen justify-center items-center flex-col gap-4 px-4">
@ -177,6 +207,7 @@ const EmployeeOnboardingView = () => {
return (
<div className="flex min-h-screen">
{/* Sidebar with onboarding steps */}
<div className="hidden lg:block lg:w-[40%]">
<div className="hidden lg:block fixed left-0 top-0 h-screen w-[40%] p-2 z-10">
<AuthSidebarComponent
@ -190,11 +221,13 @@ const EmployeeOnboardingView = () => {
</div>
</div>
{/* Main onboarding form */}
<div className="w-full lg:w-[60%] lg:ml-auto flex justify-center items-center min-h-screen px-4">
<div className="w-full max-w-lg flex flex-col gap-4 z-20">
<h1 className="text-2xl font-extrabold text-blue mb-2 mt-8">Herzlich Willkommen!</h1>
<p className="text-gray-500 mb-6">{employeeEmail}</p>
{/* Step 1: Personal data + login data*/}
{step === 1 && (
<form onSubmit={handleNext} className="space-y-5">
<div className="flex gap-4">
@ -249,6 +282,7 @@ const EmployeeOnboardingView = () => {
</form>
)}
{/* Step 2: Addtional (optinal) Employee Data */}
{step === 2 && (
<form onSubmit={handleSubmit} className="space-y-5">
<div className="flex flex-col items-center">

View file

@ -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<void>}
*/
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`,
});

View file

@ -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 = () => {
<hr className="mt-6 border-blue" />
{/* Social Login Buttons */}
<div className="flex flex-col sm:flex-row gap-4 mt-4">
<AuthButton Icon={FaGoogle} label="Login mit Google" />
<AuthButton Icon={FaFacebook} label="Login mit Facebook" />

View file

@ -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<string, string> = {};
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 (
<div className="flex min-h-screen">
{/* Sidebar with step indicator */}
<div className="hidden lg:block lg:w-[40%]">
<div className="hidden lg:block fixed left-0 top-0 h-screen w-[40%] p-2 z-10">
<AuthSidebarComponent
@ -124,10 +149,12 @@ const RegisterAdmin = () => {
</div>
</div>
{/* Main form */}
<div className="w-full lg:w-[60%] lg:ml-auto flex justify-center items-center min-h-screen px-4">
<div className="w-full max-w-lg flex flex-col gap-4 z-20">
<h1 className="text-2xl font-extrabold text-blue mb-8 mt-8 ">Sign up for free</h1>
{/* Step 1: Personal + login data */}
{step === 1 && (
<form onSubmit={handleNext} className="space-y-5">
<InputComponent
@ -169,7 +196,7 @@ const RegisterAdmin = () => {
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 = () => {
</form>
)}
{/* Step 2: Private address */}
{step === 2 && (
<form onSubmit={handleNext} className="space-y-5">
<AddressAutocompleteComponent
@ -207,6 +235,7 @@ const RegisterAdmin = () => {
</form>
)}
{/* Step 3: Company data */}
{step === 3 && (
<form onSubmit={handleRegister} className="space-y-5">
<InputComponent

View file

@ -15,15 +15,28 @@ import { useRouter } from "next/navigation";
import { useAuth } from "@/src/hooks/useAuth";
const RegisterUserView = () => {
// ------------- 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<string, string> = {};
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 (
<div className="flex min-h-screen">
{/* Sidebar */}
{/* Sidebar with step indicator */}
<div className="hidden lg:block lg:w-[40%]">
<div className="hidden lg:block fixed left-0 top-0 h-screen w-[40%] p-2 z-10">
<AuthSidebarComponent
@ -87,10 +111,12 @@ const RegisterUserView = () => {
</div>
</div>
{/* Main registration form */}
<div className="w-full lg:w-[60%] lg:ml-auto flex justify-center items-center min-h-screen px-4">
<div className="w-full max-w-lg flex flex-col gap-4 z-20">
<h1 className="text-2xl font-extrabold text-blue mb-8 mt-8 ">Sign up for free</h1>
{/* Step 1: Basic account data */}
{step === 1 && (
<form onSubmit={handleNext} className="space-y-5">
<InputComponent
@ -125,7 +151,7 @@ const RegisterUserView = () => {
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 = () => {
</form>
)}
{/* Step 2: Additional user details */}
{step === 2 && (
<form onSubmit={handleRegister} className="space-y-5">
<InputComponent
@ -144,7 +171,7 @@ const RegisterUserView = () => {
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 = () => {
<hr className="mt-6 border-blue" />
{/* Social login options */}
<div className="flex flex-col sm:flex-row gap-4 mt-4">
<AuthButton Icon={FaGoogle} label="Login mit Google" />
<AuthButton Icon={FaFacebook} label="Login mit Facebook" />

View file

@ -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<void>}
*/
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);