222 lines
7.4 KiB
JavaScript
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;
|