monorepo/web/maplefile-frontend/src/pages/Anonymous/Recovery/InitiateRecovery.jsx
2025-12-03 01:20:30 -05:00

222 lines
7.4 KiB
JavaScript

// File: monorepo/web/maplefile-frontend/src/pages/Anonymous/Recovery/InitiateRecovery.jsx
// UIX version - Theme-aware Account Recovery with UIX components
import React, { useState, useCallback, useRef, useEffect } from "react";
import { useNavigate, Link } from "react-router";
import { useServices } from "../../../services/Services";
import { Button, Input, Alert, Card, GDPRFooter, ProgressIndicator, NavBar, AuthPageHeader } from "../../../components/UIX";
import { useUIXTheme } from "../../../components/UIX/themes/useUIXTheme";
import {
ArrowRightIcon,
ArrowLeftIcon,
CheckIcon,
EnvelopeIcon,
KeyIcon,
} from "@heroicons/react/24/outline";
const InitiateRecovery = () => {
const navigate = useNavigate();
const { recoveryManager } = useServices();
const { getThemeClasses } = useUIXTheme();
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
// Refs for cleanup
const isMountedRef = useRef(true);
// Cleanup on unmount
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
const handleSubmit = useCallback(async (e) => {
e.preventDefault();
setLoading(true);
setError("");
try {
// Validate email
if (!email) {
throw new Error("Email address is required");
}
if (!email.includes("@")) {
throw new Error("Please enter a valid email address");
}
if (import.meta.env.DEV) {
console.log("[InitiateRecovery] Starting recovery process");
}
const response = await recoveryManager.initiateRecovery(email);
if (import.meta.env.DEV) {
console.log("[InitiateRecovery] Recovery initiated successfully");
console.log("[InitiateRecovery] Response:", response);
console.log("[InitiateRecovery] session_id:", response.session_id);
console.log("[InitiateRecovery] encrypted_challenge:", response.encrypted_challenge);
}
// Check if session was actually created (user exists)
if (!response.session_id || !response.encrypted_challenge) {
// User not found - show generic message for security (prevents email enumeration)
if (isMountedRef.current) {
setError(
"Unable to initiate recovery. Please ensure you entered the correct email address associated with your account. If you need assistance, please contact support.",
);
}
return;
}
// Navigate to verification step
navigate("/recovery/verify");
} catch (error) {
if (import.meta.env.DEV) {
console.error("[InitiateRecovery] Recovery initiation failed:", error);
}
if (isMountedRef.current) {
setError(error.message);
}
} finally {
if (isMountedRef.current) {
setLoading(false);
}
}
}, [email, recoveryManager, navigate]);
const handleBackToLogin = useCallback(() => {
navigate("/login");
}, [navigate]);
return (
<div className={`min-h-screen flex flex-col ${getThemeClasses("bg-gradient-primary")}`}>
{/* Navigation */}
<NavBar
links={[{ to: "/register", label: "Need an account?" }]}
/>
{/* Main Content */}
<div className="flex-1 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
{/* Progress Indicator */}
<ProgressIndicator
steps={[
{ label: "Email" },
{ label: "Verify" },
{ label: "Reset" }
]}
currentStep={1}
className="mb-0"
/>
{/* Header */}
<AuthPageHeader
title="Account Recovery"
icon={KeyIcon}
/>
{/* Important Notice */}
<Alert type="warning">
<div>
<h3 className="text-sm font-semibold mb-1">
Before You Begin
</h3>
<p className="text-sm">
You'll need your <strong>12-word recovery phrase</strong> to
complete this process. Make sure you have it ready before
proceeding.
</p>
</div>
</Alert>
{/* Form Card */}
<Card className={`shadow-2xl animate-fade-in-up ${getThemeClasses("bg-card")}`}>
<div className="px-8 pt-4 pb-8">
{/* Error Message */}
{error && (
<Alert type="error" className="mb-6">
<div>
<h3 className="text-sm font-semibold mb-1">
Recovery Error
</h3>
<p className="text-sm">{error}</p>
</div>
</Alert>
)}
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-6">
<Input
type="email"
id="email"
label="Email Address"
value={email}
onChange={setEmail}
placeholder="Enter your email address"
required
disabled={loading}
autoComplete="email"
icon={email && email.includes("@") && email.includes(".") ? CheckIcon : EnvelopeIcon}
helperText="We'll use this to verify your identity and guide you through recovery"
/>
<Button
type="submit"
disabled={loading || !email.includes("@")}
variant="primary"
size="lg"
fullWidth
loading={loading}
loadingText="Starting Recovery..."
>
<span className="flex items-center">
Start Account Recovery
<ArrowRightIcon className="ml-2 h-4 w-4" />
</span>
</Button>
{/* GDPR Notice */}
<Alert type="info">
<div className="text-xs">
<p className="font-semibold mb-1">Data Processing Notice</p>
<p>
Your email is temporarily stored in your browser to verify your identity—see our{" "}
<Link to="/privacy" className={`${getThemeClasses("link-primary")} underline`}>
Privacy Policy
</Link>{" "}
for full details.
</p>
</div>
</Alert>
</form>
{/* Back to Login Button */}
<div className="mt-4">
<Button
type="button"
onClick={handleBackToLogin}
disabled={loading}
variant="secondary"
size="md"
fullWidth
>
<span className="flex items-center justify-center whitespace-nowrap">
<ArrowLeftIcon className="mr-2 h-4 w-4 flex-shrink-0" />
Back to Login
</span>
</Button>
</div>
</div>
</Card>
</div>
</div>
{/* Footer */}
<GDPRFooter containerClassName={`${getThemeClasses("bg-card")}/75`} />
</div>
);
};
export default InitiateRecovery;