Authentication Pages¶
Purpose: Exhaustive page-level documentation for all 40+ routes
Repository:machineagents-fe/src/app/
Last Updated: 2025-12-29
Coverage: In Progress (Phase 2 of Exhaustive Documentation)
1. Login Page¶
File: src/app/components/Login.tsx (441 lines)
Route: /login
Type: Public
Purpose: User authentication with reCAPTCHA protection
State Variables (6)¶
- email (
string) - User email/username input - password (
string) - User password input - showPassword (
boolean) - Toggle password visibility - error (
string) - Error message display - recaptchaToken (
string) - Google reCAPTCHA v2 token - recaptchaRef (
useRef<any>) - reCAPTCHA instance reference - containerRef (
useRef<HTMLDivElement>) - reCAPTCHA container DOM ref
Form Validation¶
Requirements: -✅ Email/username not empty
- ✅ Password not empty
- ✅ reCAPTCHA completed
reCAPTCHA Integration¶
Configuration:
Rendering Logic:
useEffect(() => {
const renderRecaptcha = () => {
if (!window.grecaptcha || !containerRef.current) return;
// Reset existing instance
if (recaptchaRef.current !== null && window.grecaptcha.reset) {
try {
window.grecaptcha.reset(recaptchaRef.current);
} catch (error) {
// Ignore reset errors
}
}
// Clear container completely
if (containerRef.current) {
containerRef.current.innerHTML = "";
const newDiv = document.createElement("div");
containerRef.current.appendChild(newDiv);
}
recaptchaRef.current = null;
try {
const newDiv = containerRef.current?.firstChild as HTMLElement;
if (newDiv) {
recaptchaRef.current = window.grecaptcha.render(newDiv, {
sitekey: RECAPTCHA_SITE_KEY,
callback: (token: string) => setRecaptchaToken(token),
"expired-callback": () => setRecaptchaToken(""),
theme: "dark",
});
}
} catch (error) {
console.error("reCAPTCHA render error:", error);
// Retry after 1 second on error
setTimeout(() => {
if (containerRef.current) {
containerRef.current.innerHTML = "";
renderRecaptcha();
}
}, 1000);
}
};
const initRecaptcha = () => {
if (window.grecaptcha && window.grecaptcha.render) {
window.grecaptcha.ready(() => {
renderRecaptcha();
});
} else {
// Wait 500ms and retry if not ready
setTimeout(initRecaptcha, 500);
}
};
// Start initialization after 100ms
const timeoutId = setTimeout(initRecaptcha, 100);
return () => {
clearTimeout(timeoutId);
if (
recaptchaRef.current !== null &&
window.grecaptcha &&
window.grecaptcha.reset
) {
try {
window.grecaptcha.reset(recaptchaRef.current);
} catch (error) {
// Ignore cleanup errors
}
}
if (containerRef.current) {
containerRef.current.innerHTML = "";
}
recaptchaRef.current = null;
setRecaptchaToken("");
};
}, []);
Features:
- ✅ Automatic retry on render failure
- ✅ Dark theme
- ✅ Proper cleanup on unmount
- ✅ Token expiration handling
- ✅ Container reset to avoid duplicate renders
Admin Redirect Feature¶
Purpose: Allow superadmin to log in as user via URL parameters
Implementation:
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const isAdminRedirect = urlParams.get("adminRedirect");
if (isAdminRedirect === "true") {
const token = urlParams.get("token");
const userId = urlParams.get("user_id");
const sessionId = urlParams.get("session_id");
const email = urlParams.get("email");
if (token && userId) {
// Set session storage with the provided data
sessionStorage.setItem("isLoggedIn", "true");
sessionStorage.setItem("authToken", token);
sessionStorage.setItem("user_id", userId);
sessionStorage.setItem("session_id", sessionId || generateSessionId());
sessionStorage.setItem("userEmail", email || "");
sessionStorage.setItem("connectedFromSuperAdmin", "true");
// Clean URL and redirect to chatbots
window.history.replaceState({}, document.title, window.location.pathname);
router.push("/chatbots");
return;
}
}
}, [router]);
URL Format:
Login Flow¶
API Endpoint: POST /v2/login
Request:
const formData = new FormData();
formData.append("email", email);
formData.append("password", password);
formData.append("recaptcha_token", recaptchaToken);
const response = await fetch(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/v2/login`,
{
method: "POST",
body: formData,
}
);
Success Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user_id": "user_12345",
"session_id": "session_abc123"
}
Session Storage on Success:
sessionStorage.setItem("isLoggedIn", "true");
sessionStorage.setItem("authToken", data.token);
sessionStorage.setItem("user_id", data.user_id);
sessionStorage.setItem("session_id", data.session_id || generateSessionId());
sessionStorage.setItem("userEmail", email);
Payment Plan Redirect¶
Purpose: Resume payment flow after login if user was redirected from pricing page
Logic:
// Check if there's a stored plan for payment
const storedPlan = sessionStorage.getItem("selectedPlanForPayment");
if (storedPlan) {
try {
const plan = JSON.parse(storedPlan);
sessionStorage.removeItem("selectedPlanForPayment"); // Clean up
const planData = {
...plan,
category: plan.name.toLowerCase(),
};
// Free plan - skip payment
if (plan.name == "Free") {
router.push("/chatbots");
return;
}
// Paid plan - redirect to payment
const queryParams = new URLSearchParams({
plan: encodeURIComponent(JSON.stringify(planData)),
});
router.push(`/payment?${queryParams.toString()}`);
} catch (error) {
console.error("Error parsing stored plan:", error);
sessionStorage.removeItem("selectedPlanForPayment");
router.push("/chatbots");
}
} else {
router.push("/chatbots");
}
Error Handling¶
Error Response Parsing:
const errorData = await response.json();
let errorMessage = "Invalid email or password";
if (errorData.detail) {
if (
typeof errorData.detail === "string" &&
errorData.detail.startsWith("{")
) {
try {
// Backend sometimes returns JSON string in detail field
const parsed = JSON.parse(errorData.detail);
errorMessage = parsed.detail || errorData.detail;
} catch {
errorMessage = errorData.detail;
}
} else {
errorMessage = errorData.detail;
}
}
toast.error(errorMessage);
resetRecaptcha(); // Reset reCAPTCHA on error
reCAPTCHA Reset:
const resetRecaptcha = () => {
if (
window.grecaptcha &&
window.grecaptcha.reset &&
recaptchaRef.current !== null
) {
try {
window.grecaptcha.reset(recaptchaRef.current);
} catch (error) {
console.error("Error resetting reCAPTCHA:", error);
}
}
setRecaptchaToken("");
};
Auto-Redirect for LoggedIn Users¶
Purpose: Redirect to dashboard if already authenticated
useEffect(() => {
const isLoggedIn = sessionStorage.getItem("isLoggedIn");
const authToken = sessionStorage.getItem("authToken");
// Check both isLoggedIn flag and authToken to ensure user is actually logged in
if (isLoggedIn === "true" && authToken) {
router.push("/chatbots");
}
}, [router]);
UI Features¶
- Password Toggle:
<button onClick={() => setShowPassword(!showPassword)}>
{showPassword ? <EyeSlashIcon /> : <EyeIcon />}
</button>
- Disabled Submit Button:
<button
type="submit"
disabled={!isFormValid}
className={
isFormValid
? "bg-[#FF6622] hover:bg-orange-600 cursor-pointer"
: "bg-gray-600 cursor-not-allowed opacity-50"
}
>
Log in
</button>
- Toast Notifications:
- Navigation Links:
- Forgot Password:
/forgot-password - Sign Up:
/signup
Environment Variables¶
NEXT_PUBLIC_BACKEND_URL=https://api.machineavatars.com
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=6LeXXXXXXXXXXXXXXXXXXXXXXXXX
Dependencies¶
next/link- Client-side navigationnext/script- reCAPTCHA script loadingnext/navigation(useRouter) - Programmatic navigationreact(useState, useEffect, useRef)react-toastify- Error/success notifications- Google reCAPTCHA v2
Security Features¶
- ✅ reCAPTCHA v2 - Bot protection
- ✅ Password masking - Hidden by default
- ✅ HTTPS required - Backend URL must be HTTPS
- ✅ Token expiration - reCAPTCHA tokens expire after 2 minutes
- ✅ Error message sanitization - Prevents XSS in error display
Session ID Generation¶
Format: session_xxxxxxxxx (9 random characters)
Related Documentation¶
-
- useSessionData
-
- Auth endpoints
Status: 5/40 pages documented
Next: Chatbot creation and interface pages