additional GUI work

This commit is contained in:
Rodolfo Martinez 2025-12-03 01:20:30 -05:00
parent 3c6899ace5
commit 81f60acd06
8 changed files with 467 additions and 285 deletions

View file

@ -0,0 +1,105 @@
// File: src/components/UIX/AuthPageHeader/AuthPageHeader.jsx
// AuthPageHeader Component - Centered icon with title for auth pages
import React, { memo, useMemo } from "react";
import { useUIXTheme } from "../themes/useUIXTheme.jsx";
/**
* AuthPageHeader Component - Performance Optimized
* Displays a centered icon with title for authentication page headers
*
* @param {string} title - The page title
* @param {React.Component} icon - Icon component to display
* @param {string} size - Size variant: 'sm', 'md', 'lg' (default: 'md')
* @param {string} className - Additional CSS classes
* @param {boolean} animate - Whether to animate on load (default: true)
* @param {boolean} showGlow - Whether to show glow effect (default: true)
*/
const SIZE_CONFIG = Object.freeze({
sm: {
container: "h-12 w-12",
icon: "h-6 w-6",
title: "text-2xl",
radius: "rounded-xl",
glowInset: "-inset-0.5",
},
md: {
container: "h-16 w-16",
icon: "h-8 w-8",
title: "text-3xl",
radius: "rounded-2xl",
glowInset: "-inset-1",
},
lg: {
container: "h-20 w-20",
icon: "h-10 w-10",
title: "text-4xl",
radius: "rounded-3xl",
glowInset: "-inset-1.5",
},
});
const AuthPageHeader = memo(function AuthPageHeader({
title,
icon: Icon,
size = "md",
className = "",
animate = true,
showGlow = true,
}) {
const { getThemeClasses } = useUIXTheme();
const sizeConfig = SIZE_CONFIG[size] || SIZE_CONFIG.md;
const themeClasses = useMemo(
() => ({
bgGradient: getThemeClasses("bg-gradient-secondary"),
textPrimary: getThemeClasses("text-primary"),
}),
[getThemeClasses]
);
const containerClasses = useMemo(
() =>
`text-center ${animate ? "animate-fade-in-up" : ""} ${className}`.trim(),
[animate, className]
);
const iconContainerClasses = useMemo(
() =>
`flex items-center justify-center ${sizeConfig.container} ${themeClasses.bgGradient} ${sizeConfig.radius} shadow-lg`,
[sizeConfig, themeClasses.bgGradient]
);
const glowClasses = useMemo(
() =>
`absolute ${sizeConfig.glowInset} ${themeClasses.bgGradient} ${sizeConfig.radius} blur opacity-20`,
[sizeConfig, themeClasses.bgGradient]
);
const titleClasses = useMemo(
() => `${sizeConfig.title} font-bold ${themeClasses.textPrimary}`,
[sizeConfig.title, themeClasses.textPrimary]
);
return (
<div className={containerClasses}>
{Icon && (
<div className="flex justify-center mb-6">
<div className="relative">
<div className={iconContainerClasses}>
<Icon className={`${sizeConfig.icon} text-white`} />
</div>
{showGlow && <div className={glowClasses}></div>}
</div>
</div>
)}
{title && <h1 className={titleClasses}>{title}</h1>}
</div>
);
});
AuthPageHeader.displayName = "AuthPageHeader";
export default AuthPageHeader;

View file

@ -1,11 +1,25 @@
// File: src/components/UIX/Input/Input.jsx // File: src/components/UIX/Input/Input.jsx
// Input Component - Performance Optimized
import React, { useMemo } from "react"; import React, { useMemo, useCallback, memo } from "react";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { useUIXTheme } from "../themes/useUIXTheme.jsx"; import { useUIXTheme } from "../themes/useUIXTheme.jsx";
// Static size configurations - moved outside component to prevent recreation
const SIZE_CLASSES = Object.freeze({
sm: "px-3 py-2 text-sm",
md: "px-4 py-3 text-base",
lg: "px-5 py-4 text-base sm:text-lg",
});
const LABEL_SIZE_CLASSES = Object.freeze({
sm: "text-sm",
md: "text-base",
lg: "text-base sm:text-lg",
});
/** /**
* Input Component * Input Component - Performance Optimized
* Text input field with label, validation, and icon support * Text input field with label, validation, and icon support
* *
* @param {string} label - Input label * @param {string} label - Input label
@ -30,7 +44,7 @@ import { useUIXTheme } from "../themes/useUIXTheme.jsx";
* Correct usage: onChange={(value) => setMyValue(value)} * Correct usage: onChange={(value) => setMyValue(value)}
* Incorrect usage: onChange={(e) => setMyValue(e.target.value)} // This will cause errors! * Incorrect usage: onChange={(e) => setMyValue(e.target.value)} // This will cause errors!
*/ */
function Input({ const Input = memo(function Input({
label, label,
type = "text", type = "text",
placeholder, placeholder,
@ -65,33 +79,51 @@ function Input({
); );
}, [props.id, props.name]); }, [props.id, props.name]);
const sizeClasses = { // Memoize theme classes to prevent multiple getThemeClasses calls on each render
sm: "px-3 py-2 text-sm", const themeClasses = useMemo(
md: "px-4 py-3 text-base", () => ({
lg: "px-5 py-4 text-base sm:text-lg", textPrimary: getThemeClasses("text-primary"),
}; textDanger: getThemeClasses("text-danger"),
textMuted: getThemeClasses("text-muted"),
textSecondary: getThemeClasses("text-secondary"),
inputBorder: getThemeClasses("input-border"),
inputBorderError: getThemeClasses("input-border-error"),
inputFocusRing: getThemeClasses("input-focus-ring"),
bgDisabled: getThemeClasses("bg-disabled"),
}),
[getThemeClasses]
);
const labelSizeClasses = { // Memoize the onChange handler to prevent recreation on each render
sm: "text-sm", const handleChange = useCallback(
md: "text-base", (e) => {
lg: "text-base sm:text-lg", if (onChange) {
}; onChange(e.target.value);
}
},
[onChange]
);
// Memoize computed values
const currentLength = value ? value.length : 0; const currentLength = value ? value.length : 0;
const warningThreshold = characterCountWarning || characterWarningThreshold; const warningThreshold = characterCountWarning || characterWarningThreshold;
const isNearLimit = const isNearLimit =
maxLength && currentLength > (maxLength * warningThreshold) / 100; maxLength && currentLength > (maxLength * warningThreshold) / 100;
// Memoize size class lookups
const sizeClass = SIZE_CLASSES[size] || SIZE_CLASSES.lg;
const labelSizeClass = LABEL_SIZE_CLASSES[size] || LABEL_SIZE_CLASSES.lg;
return ( return (
<div className={className}> <div className={className}>
{label && ( {label && (
<label <label
htmlFor={inputId} htmlFor={inputId}
className={`block ${labelSizeClasses[size]} font-semibold ${getThemeClasses("text-primary")} mb-3 flex items-center`} className={`block ${labelSizeClass} font-semibold ${themeClasses.textPrimary} mb-3 flex items-center`}
> >
{label} {label}
{required && ( {required && (
<span className={`ml-1 ${getThemeClasses("text-danger")}`}>*</span> <span className={`ml-1 ${themeClasses.textDanger}`}>*</span>
)} )}
</label> </label>
)} )}
@ -102,14 +134,14 @@ function Input({
flex items-center w-full flex items-center w-full
border-2 rounded-xl shadow-sm border-2 rounded-xl shadow-sm
transition-all duration-200 transition-all duration-200
${error ? getThemeClasses("input-border-error") : getThemeClasses("input-border")} ${error ? themeClasses.inputBorderError : themeClasses.inputBorder}
${disabled ? `${getThemeClasses("bg-disabled")} cursor-not-allowed` : "bg-white"} ${disabled ? `${themeClasses.bgDisabled} cursor-not-allowed` : "bg-white"}
focus-within:outline-none focus-within:${getThemeClasses("input-focus-ring")} focus-within:outline-none focus-within:${themeClasses.inputFocusRing}
`}> `}>
<span className={` <span className={`
${sizeClasses[size]} ${sizeClass}
pr-0 font-medium pr-0 font-medium
${disabled ? getThemeClasses("text-muted") : getThemeClasses("text-secondary")} ${disabled ? themeClasses.textMuted : themeClasses.textSecondary}
select-none whitespace-nowrap select-none whitespace-nowrap
`}> `}>
{prefix} {prefix}
@ -120,14 +152,14 @@ function Input({
type={type} type={type}
placeholder={placeholder} placeholder={placeholder}
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={handleChange}
disabled={disabled} disabled={disabled}
required={required} required={required}
readOnly={readOnly} readOnly={readOnly}
maxLength={maxLength} maxLength={maxLength}
className={` className={`
flex-1 flex-1
${sizeClasses[size]} ${sizeClass}
pl-0 pl-0
bg-transparent bg-transparent
border-0 border-0
@ -142,7 +174,7 @@ function Input({
<> <>
{Icon && ( {Icon && (
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Icon className={`h-5 w-5 ${getThemeClasses("text-muted")}`} /> <Icon className={`h-5 w-5 ${themeClasses.textMuted}`} />
</div> </div>
)} )}
<input <input
@ -151,25 +183,21 @@ function Input({
type={type} type={type}
placeholder={placeholder} placeholder={placeholder}
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={handleChange}
disabled={disabled} disabled={disabled}
required={required} required={required}
readOnly={readOnly} readOnly={readOnly}
maxLength={maxLength} maxLength={maxLength}
className={` className={`
w-full w-full
${sizeClasses[size]} ${sizeClass}
${Icon ? "pl-10" : "pl-5"} ${Icon ? "pl-10" : "pl-5"}
border-2 rounded-xl shadow-sm border-2 rounded-xl shadow-sm
transition-all duration-200 transition-all duration-200
placeholder-gray-400 placeholder-gray-400
focus:outline-none ${getThemeClasses("input-focus-ring")} ${getThemeClasses("input-border")} focus:outline-none ${themeClasses.inputFocusRing} ${themeClasses.inputBorder}
${ ${error ? themeClasses.inputBorderError : themeClasses.inputBorder}
error ${disabled ? `${themeClasses.bgDisabled} cursor-not-allowed` : "bg-white"}
? getThemeClasses("input-border-error")
: getThemeClasses("input-border")
}
${disabled ? `${getThemeClasses("bg-disabled")} cursor-not-allowed` : "bg-white"}
`} `}
{...props} {...props}
/> />
@ -182,7 +210,7 @@ function Input({
<div className="mt-1 text-right"> <div className="mt-1 text-right">
<span <span
className={`text-xs ${ className={`text-xs ${
isNearLimit ? "text-amber-600" : getThemeClasses("text-muted") isNearLimit ? "text-amber-600" : themeClasses.textMuted
}`} }`}
> >
{currentLength}/{maxLength} characters {currentLength}/{maxLength} characters
@ -192,7 +220,7 @@ function Input({
{/* Helper text */} {/* Helper text */}
{helperText && !error && ( {helperText && !error && (
<p className={`mt-1 text-xs ${getThemeClasses("text-muted")}`}> <p className={`mt-1 text-xs ${themeClasses.textMuted}`}>
{helperText} {helperText}
</p> </p>
)} )}
@ -200,16 +228,42 @@ function Input({
{/* Error message */} {/* Error message */}
{error && ( {error && (
<p <p
className={`mt-1 text-sm ${getThemeClasses("text-danger")} flex items-center animate-fade-in`} className={`mt-1 text-sm ${themeClasses.textDanger} flex items-center animate-fade-in`}
> >
<ExclamationTriangleIcon <ExclamationTriangleIcon
className={`h-4 w-4 mr-1 ${getThemeClasses("text-danger")}`} className={`h-4 w-4 mr-1 ${themeClasses.textDanger}`}
/> />
{error} {error}
</p> </p>
)} )}
</div> </div>
); );
} },
// Custom comparison function for memo - only re-render when these props change
(prevProps, nextProps) => {
return (
prevProps.value === nextProps.value &&
prevProps.error === nextProps.error &&
prevProps.disabled === nextProps.disabled &&
prevProps.label === nextProps.label &&
prevProps.placeholder === nextProps.placeholder &&
prevProps.type === nextProps.type &&
prevProps.required === nextProps.required &&
prevProps.readOnly === nextProps.readOnly &&
prevProps.helperText === nextProps.helperText &&
prevProps.maxLength === nextProps.maxLength &&
prevProps.showCharacterCount === nextProps.showCharacterCount &&
prevProps.size === nextProps.size &&
prevProps.className === nextProps.className &&
prevProps.prefix === nextProps.prefix &&
prevProps.icon === nextProps.icon &&
prevProps.onChange === nextProps.onChange &&
prevProps.id === nextProps.id &&
prevProps.name === nextProps.name
);
});
// Set display name for React DevTools
Input.displayName = "Input";
export default Input; export default Input;

View file

@ -0,0 +1,104 @@
// File: src/components/UIX/Logo/Logo.jsx
// Logo Component - MapleFile branding with icon
import React, { memo, useMemo } from "react";
import { Link } from "react-router";
import { LockClosedIcon } from "@heroicons/react/24/outline";
import { useUIXTheme } from "../themes/useUIXTheme.jsx";
/**
* Logo Component - Performance Optimized
* Displays the MapleFile logo with icon and optional link
*
* @param {string} to - Link destination (default: "/")
* @param {string} size - Logo size: 'sm', 'md', 'lg' (default: 'md')
* @param {boolean} showText - Whether to show the text (default: true)
* @param {string} className - Additional CSS classes
* @param {React.Component} icon - Custom icon component (default: LockClosedIcon)
*/
const SIZE_CONFIG = Object.freeze({
sm: {
container: "h-8 w-8",
icon: "h-4 w-4",
text: "text-lg",
iconRadius: "rounded-lg",
},
md: {
container: "h-10 w-10",
icon: "h-5 w-5",
text: "text-xl",
iconRadius: "rounded-xl",
},
lg: {
container: "h-12 w-12",
icon: "h-6 w-6",
text: "text-2xl",
iconRadius: "rounded-xl",
},
});
const Logo = memo(function Logo({
to = "/",
size = "md",
showText = true,
className = "",
icon: Icon = LockClosedIcon,
}) {
const { getThemeClasses } = useUIXTheme();
const sizeConfig = SIZE_CONFIG[size] || SIZE_CONFIG.md;
const themeClasses = useMemo(
() => ({
bgGradient: getThemeClasses("bg-gradient-secondary"),
textPrimary: getThemeClasses("text-primary"),
}),
[getThemeClasses]
);
const containerClasses = useMemo(
() => `inline-flex items-center space-x-3 group ${className}`.trim(),
[className]
);
const iconContainerClasses = useMemo(
() =>
`relative flex items-center justify-center ${sizeConfig.container} ${themeClasses.bgGradient} ${sizeConfig.iconRadius} shadow-md group-hover:shadow-lg transform group-hover:scale-105 transition-all duration-200`,
[sizeConfig, themeClasses.bgGradient]
);
const textClasses = useMemo(
() =>
`${sizeConfig.text} font-bold ${themeClasses.textPrimary} transition-colors duration-200`,
[sizeConfig.text, themeClasses.textPrimary]
);
const content = (
<>
<div className="relative">
<div
className={`absolute inset-0 ${themeClasses.bgGradient} ${sizeConfig.iconRadius} opacity-0 group-hover:opacity-100 blur-xl transition-opacity duration-300`}
></div>
<div className={iconContainerClasses}>
<Icon className={`${sizeConfig.icon} text-white`} />
</div>
</div>
{showText && <span className={textClasses}>MapleFile</span>}
</>
);
if (to) {
return (
<Link to={to} className={containerClasses}>
{content}
</Link>
);
}
return <div className={containerClasses}>{content}</div>;
});
Logo.displayName = "Logo";
export default Logo;

View file

@ -0,0 +1,80 @@
// File: src/components/UIX/NavBar/NavBar.jsx
// NavBar Component - Navigation bar for authentication pages
import React, { memo, useMemo } from "react";
import { Link } from "react-router";
import { useUIXTheme } from "../themes/useUIXTheme.jsx";
import Logo from "../Logo/Logo.jsx";
/**
* NavBar Component - Performance Optimized
* Navigation bar with logo and optional nav links
*
* @param {Array} links - Array of link objects { to, label, variant }
* @param {string} logoSize - Logo size: 'sm', 'md', 'lg' (default: 'md')
* @param {string} className - Additional CSS classes
* @param {boolean} transparent - Use transparent background (default: false)
* @param {React.ReactNode} children - Custom content to render on the right side
*/
const NavBar = memo(function NavBar({
links = [],
logoSize = "md",
className = "",
transparent = false,
children,
}) {
const { getThemeClasses } = useUIXTheme();
const themeClasses = useMemo(
() => ({
bgCard: getThemeClasses("bg-card"),
borderSecondary: getThemeClasses("border-secondary"),
linkSecondary: getThemeClasses("link-secondary"),
}),
[getThemeClasses]
);
const navClasses = useMemo(() => {
const baseClasses = transparent
? "backdrop-blur-sm"
: `${themeClasses.bgCard}/95 backdrop-blur-sm border-b ${themeClasses.borderSecondary}`;
return `${baseClasses} ${className}`.trim();
}, [transparent, themeClasses, className]);
const linkClasses = useMemo(
() =>
`text-base font-medium ${themeClasses.linkSecondary} transition-colors duration-200`,
[themeClasses.linkSecondary]
);
const linksSection = useMemo(() => {
if (children) return children;
if (links.length === 0) return null;
return (
<div className="flex items-center space-x-6">
{links.map((link, index) => (
<Link key={index} to={link.to} className={linkClasses}>
{link.label}
</Link>
))}
</div>
);
}, [links, linkClasses, children]);
return (
<nav className={navClasses}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center py-4 sm:py-6">
<Logo size={logoSize} />
{linksSection}
</div>
</div>
</nav>
);
});
NavBar.displayName = "NavBar";
export default NavBar;

View file

@ -14,6 +14,9 @@
export { default as ActionCard } from "./ActionCard/ActionCard"; export { default as ActionCard } from "./ActionCard/ActionCard";
export { default as DeleteActionCard } from "./ActionCard/DeleteActionCard"; export { default as DeleteActionCard } from "./ActionCard/DeleteActionCard";
// AuthPageHeader Component (New - centered icon with title for auth pages)
export { default as AuthPageHeader } from "./AuthPageHeader/AuthPageHeader";
// Alert Components (Enhanced with modern styling) // Alert Components (Enhanced with modern styling)
export { default as Alert, Notification, Message } from "./Alert/Alert"; export { default as Alert, Notification, Message } from "./Alert/Alert";
@ -175,6 +178,9 @@ export {
export { default as LoadingOverlay } from "./Loading/LoadingOverlay"; export { default as LoadingOverlay } from "./Loading/LoadingOverlay";
export { default as Spinner, LoadingSpinner } from "./Loading/Spinner"; export { default as Spinner, LoadingSpinner } from "./Loading/Spinner";
// Logo Component (New - MapleFile branding with icon)
export { default as Logo } from "./Logo/Logo";
// Modal Component // Modal Component
export { default as Modal } from "./Modal/Modal"; export { default as Modal } from "./Modal/Modal";
@ -184,6 +190,9 @@ export { default as MultiSelect } from "./MultiSelect/MultiSelect";
// Navigation Component (New - for reusable navigation bars) // Navigation Component (New - for reusable navigation bars)
export { default as Navigation } from "./Navigation/Navigation"; export { default as Navigation } from "./Navigation/Navigation";
// NavBar Component (New - navigation bar for authentication pages)
export { default as NavBar } from "./NavBar/NavBar";
// PageContainer Component (New - for full-page layouts with decorative elements) // PageContainer Component (New - for full-page layouts with decorative elements)
export { default as PageContainer } from "./PageContainer/PageContainer"; export { default as PageContainer } from "./PageContainer/PageContainer";

View file

@ -3,11 +3,10 @@
import { useState, useEffect, useRef, useCallback } from "react"; import { useState, useEffect, useRef, useCallback } from "react";
import { useNavigate, Link } from "react-router"; import { useNavigate, Link } from "react-router";
import { useServices } from "../../../services/Services"; import { useServices } from "../../../services/Services";
import { Card, Input, Button, Alert, GDPRFooter, ProgressIndicator, Divider } from "../../../components/UIX"; import { Card, Input, Button, Alert, GDPRFooter, ProgressIndicator, Divider, NavBar, AuthPageHeader } from "../../../components/UIX";
import { useUIXTheme } from "../../../components/UIX/themes/useUIXTheme"; import { useUIXTheme } from "../../../components/UIX/themes/useUIXTheme";
import { import {
ArrowRightIcon, ArrowRightIcon,
LockClosedIcon,
ShieldCheckIcon, ShieldCheckIcon,
SparklesIcon, SparklesIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
@ -172,33 +171,13 @@ const RequestOTT = () => {
{/* Header */} {/* Header */}
<div className="relative z-10 flex-shrink-0"> <div className="relative z-10 flex-shrink-0">
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 flex justify-between items-center"> <NavBar
<Link to="/" className="inline-flex items-center space-x-3 group"> links={[
<div className="relative"> { to: "/register", label: "Need an account?" },
<div className={`absolute inset-0 ${getThemeClasses("bg-gradient-secondary")} rounded-xl opacity-0 group-hover:opacity-100 blur-xl transition-opacity duration-300`}></div> { to: "/recovery", label: "Forgot password?" }
<div className={`relative flex items-center justify-center h-10 w-10 ${getThemeClasses("bg-gradient-secondary")} rounded-xl shadow-md group-hover:shadow-lg transform group-hover:scale-105 transition-all duration-200`}> ]}
<LockClosedIcon className="h-5 w-5 text-white" /> transparent
</div> />
</div>
<span className={`text-xl font-bold ${getThemeClasses("text-primary")} transition-colors duration-200`}>
MapleFile
</span>
</Link>
<div className="flex items-center space-x-6">
<Link
to="/register"
className={`text-base font-medium ${getThemeClasses("link-secondary")} transition-colors duration-200`}
>
Need an account?
</Link>
<Link
to="/recovery"
className={`text-base font-medium ${getThemeClasses("link-secondary")} transition-colors duration-200`}
>
Forgot password?
</Link>
</div>
</nav>
</div> </div>
{/* Main Content */} {/* Main Content */}
@ -216,17 +195,10 @@ const RequestOTT = () => {
/> />
{/* Welcome Message */} {/* Welcome Message */}
<div className="text-center"> <AuthPageHeader
<div className="flex justify-center mb-6"> title="Secure Sign In"
<div className="relative"> icon={ShieldCheckIcon}
<div className={`absolute inset-0 ${getThemeClasses("bg-gradient-secondary")} rounded-2xl blur-2xl opacity-20`}></div> />
<div className={`relative h-16 w-16 ${getThemeClasses("bg-gradient-secondary")} rounded-2xl flex items-center justify-center shadow-xl`}>
<ShieldCheckIcon className="h-8 w-8 text-white" />
</div>
</div>
</div>
<h1 className={`text-3xl font-bold ${getThemeClasses("text-primary")}`}>Secure Sign In</h1>
</div>
{/* Login Form */} {/* Login Form */}
<Card <Card

View file

@ -3,27 +3,20 @@
import React, { useState, useCallback, useRef, useEffect } from "react"; import React, { useState, useCallback, useRef, useEffect } from "react";
import { useNavigate, Link } from "react-router"; import { useNavigate, Link } from "react-router";
import { useServices } from "../../../services/Services"; import { useServices } from "../../../services/Services";
import { Button, Input, Alert, Card, GDPRFooter, Badge } from "../../../components/UIX"; import { Button, Input, Alert, Card, GDPRFooter, ProgressIndicator, NavBar, AuthPageHeader } from "../../../components/UIX";
import { useUIXTheme } from "../../../components/UIX/themes/useUIXTheme";
import { import {
ArrowRightIcon, ArrowRightIcon,
ArrowLeftIcon, ArrowLeftIcon,
LockClosedIcon,
ShieldCheckIcon,
CheckIcon, CheckIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
EnvelopeIcon, EnvelopeIcon,
KeyIcon, KeyIcon,
ClockIcon,
ExclamationCircleIcon,
DocumentTextIcon,
ServerIcon,
EyeSlashIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
const InitiateRecovery = () => { const InitiateRecovery = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { recoveryManager } = useServices(); const { recoveryManager } = useServices();
const { getThemeClasses } = useUIXTheme();
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(""); const [error, setError] = useState("");
@ -98,78 +91,34 @@ const InitiateRecovery = () => {
}, [navigate]); }, [navigate]);
return ( return (
<div className="min-h-screen bg-gradient-to-br from-gray-50 via-white to-red-50 flex flex-col"> <div className={`min-h-screen flex flex-col ${getThemeClasses("bg-gradient-primary")}`}>
{/* Navigation */} {/* Navigation */}
<nav className="bg-white/95 backdrop-blur-sm border-b border-gray-100"> <NavBar
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> links={[{ to: "/register", label: "Need an account?" }]}
<div className="flex justify-between items-center py-4"> />
<Link to="/" className="flex items-center group">
<div className="flex items-center justify-center h-10 w-10 bg-gradient-to-br from-red-800 to-red-900 rounded-lg mr-3 group-hover:scale-105 transition-transform duration-200">
<LockClosedIcon className="h-6 w-6 text-white" />
</div>
<span className="text-2xl font-bold bg-gradient-to-r from-gray-900 to-red-800 bg-clip-text text-transparent">
MapleFile
</span>
</Link>
<div className="flex items-center space-x-6">
<Link to="/register">
<Button variant="ghost" size="md">
Need an account?
</Button>
</Link>
</div>
</div>
</div>
</nav>
{/* Main Content */} {/* Main Content */}
<div className="flex-1 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8"> <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"> <div className="max-w-md w-full space-y-8">
{/* Progress Indicator */} {/* Progress Indicator */}
<div className="flex items-center justify-center mb-8"> <ProgressIndicator
<div className="flex items-center space-x-4"> steps={[
<div className="flex items-center"> { label: "Email" },
<div className="flex items-center justify-center w-8 h-8 bg-gradient-to-r from-red-800 to-red-900 rounded-full text-white text-sm font-bold"> { label: "Verify" },
1 { label: "Reset" }
</div> ]}
<span className="ml-2 text-sm font-semibold text-gray-900"> currentStep={1}
Email className="mb-0"
</span> />
</div>
<div className="w-12 h-0.5 bg-gray-300"></div>
<div className="flex items-center">
<div className="flex items-center justify-center w-8 h-8 bg-gray-300 rounded-full text-gray-500 text-sm font-bold">
2
</div>
<span className="ml-2 text-sm text-gray-500">Verify</span>
</div>
<div className="w-12 h-0.5 bg-gray-300"></div>
<div className="flex items-center">
<div className="flex items-center justify-center w-8 h-8 bg-gray-300 rounded-full text-gray-500 text-sm font-bold">
3
</div>
<span className="ml-2 text-sm text-gray-500">Reset</span>
</div>
</div>
</div>
{/* Header */} {/* Header */}
<div className="text-center animate-fade-in-up"> <AuthPageHeader
<div className="flex justify-center mb-6"> title="Account Recovery"
<div className="relative"> icon={KeyIcon}
<div className="flex items-center justify-center h-16 w-16 bg-gradient-to-br from-red-800 to-red-900 rounded-2xl shadow-lg animate-pulse"> />
<KeyIcon className="h-8 w-8 text-white" />
</div>
<div className="absolute -inset-1 bg-gradient-to-r from-red-800 to-red-900 rounded-2xl blur opacity-20 animate-pulse"></div>
</div>
</div>
<h2 className="text-3xl font-black text-gray-900">
Account Recovery
</h2>
</div>
{/* Important Notice */} {/* Important Notice */}
<Alert type="warning" className="animate-fade-in-up-delay"> <Alert type="warning">
<div> <div>
<h3 className="text-sm font-semibold mb-1"> <h3 className="text-sm font-semibold mb-1">
Before You Begin Before You Begin
@ -183,11 +132,11 @@ const InitiateRecovery = () => {
</Alert> </Alert>
{/* Form Card */} {/* Form Card */}
<Card className="shadow-2xl animate-fade-in-up-delay"> <Card className={`shadow-2xl animate-fade-in-up ${getThemeClasses("bg-card")}`}>
<div className="p-8"> <div className="px-8 pt-4 pb-8">
{/* Error Message */} {/* Error Message */}
{error && ( {error && (
<Alert type="error" className="mb-6 animate-fade-in"> <Alert type="error" className="mb-6">
<div> <div>
<h3 className="text-sm font-semibold mb-1"> <h3 className="text-sm font-semibold mb-1">
Recovery Error Recovery Error
@ -199,29 +148,19 @@ const InitiateRecovery = () => {
{/* Form */} {/* Form */}
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
<div> <Input
<label type="email"
htmlFor="email" id="email"
className="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2" label="Email Address"
> value={email}
Email Address onChange={setEmail}
</label> placeholder="Enter your email address"
<Input required
type="email" disabled={loading}
id="email" autoComplete="email"
value={email} icon={email && email.includes("@") && email.includes(".") ? CheckIcon : EnvelopeIcon}
onChange={setEmail} helperText="We'll use this to verify your identity and guide you through recovery"
placeholder="Enter your email address" />
required
disabled={loading}
autoComplete="email"
icon={email && email.includes("@") && email.includes(".") ? CheckIcon : EnvelopeIcon}
/>
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
We'll use this to verify your identity and guide you through
recovery
</p>
</div>
<Button <Button
type="submit" type="submit"
@ -231,13 +170,26 @@ const InitiateRecovery = () => {
fullWidth fullWidth
loading={loading} loading={loading}
loadingText="Starting Recovery..." loadingText="Starting Recovery..."
className="group shadow-lg hover:shadow-xl transform hover:scale-105"
> >
<span className="flex items-center"> <span className="flex items-center">
Start Account Recovery Start Account Recovery
<ArrowRightIcon className="ml-2 h-4 w-4 group-hover:translate-x-1 transition-transform duration-200" /> <ArrowRightIcon className="ml-2 h-4 w-4" />
</span> </span>
</Button> </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 identitysee our{" "}
<Link to="/privacy" className={`${getThemeClasses("link-primary")} underline`}>
Privacy Policy
</Link>{" "}
for full details.
</p>
</div>
</Alert>
</form> </form>
{/* Back to Login Button */} {/* Back to Login Button */}
@ -262,52 +214,7 @@ const InitiateRecovery = () => {
</div> </div>
{/* Footer */} {/* Footer */}
<GDPRFooter containerClassName="bg-white/75" /> <GDPRFooter containerClassName={`${getThemeClasses("bg-card")}/75`} />
{/* CSS Animations */}
<style>{`
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fade-in 0.4s ease-out;
}
.animate-fade-in-up {
animation: fade-in-up 0.6s ease-out;
}
.animate-fade-in-up-delay {
animation: fade-in-up 0.6s ease-out 0.2s both;
}
.animate-fade-in-up-delay-2 {
animation: fade-in-up 0.6s ease-out 0.4s both;
}
.animate-fade-in-up-delay-3 {
animation: fade-in-up 0.6s ease-out 0.6s both;
}
`}</style>
</div> </div>
); );
}; };

View file

@ -3,15 +3,13 @@
import { useState, useEffect, useRef, useCallback, useMemo } from "react"; import { useState, useEffect, useRef, useCallback, useMemo } from "react";
import { useNavigate, Link } from "react-router"; import { useNavigate, Link } from "react-router";
import { useServices } from "../../../services/Services"; import { useServices } from "../../../services/Services";
import { Card, Input, Select, Checkbox, Button, Alert, GDPRFooter } from "../../../components/UIX"; import { Card, Input, Select, Checkbox, Button, Alert, GDPRFooter, NavBar, AuthPageHeader } from "../../../components/UIX";
import { useUIXTheme } from "../../../components/UIX/themes/useUIXTheme"; import { useUIXTheme } from "../../../components/UIX/themes/useUIXTheme";
import { import {
ArrowRightIcon, ArrowRightIcon,
LockClosedIcon, LockClosedIcon,
ShieldCheckIcon, ShieldCheckIcon,
CheckIcon, CheckIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
UserIcon, UserIcon,
EnvelopeIcon, EnvelopeIcon,
PhoneIcon, PhoneIcon,
@ -287,45 +285,19 @@ const Register = () => {
return ( return (
<div className={`min-h-screen ${getThemeClasses("bg-gradient-primary")} flex flex-col`}> <div className={`min-h-screen ${getThemeClasses("bg-gradient-primary")} flex flex-col`}>
{/* Navigation */} {/* Navigation */}
<nav className="bg-white/95 backdrop-blur-sm border-b border-gray-100"> <NavBar
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> links={[{ to: "/login", label: "Already have an account?" }]}
<div className="flex justify-between items-center py-4"> />
<Link to="/" className="flex items-center group">
<div className={`flex items-center justify-center h-10 w-10 ${getThemeClasses("bg-gradient-secondary")} rounded-lg mr-3 group-hover:scale-105 transition-transform duration-200`}>
<LockClosedIcon className="h-6 w-6 text-white" />
</div>
<span className={`text-2xl font-bold ${getThemeClasses("text-primary")}`}>
MapleFile
</span>
</Link>
<div className="flex items-center space-x-6">
<Link
to="/login"
className={`text-base font-medium ${getThemeClasses("link-secondary")} transition-colors duration-200`}
>
Already have an account?
</Link>
</div>
</div>
</div>
</nav>
{/* Main Content */} {/* Main Content */}
<div className="flex-1 py-12 px-4 sm:px-6 lg:px-8"> <div className="flex-1 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-2xl mx-auto"> <div className="max-w-2xl mx-auto">
{/* Header */} {/* Header */}
<div className="text-center mb-8 animate-fade-in-up"> <AuthPageHeader
<div className="flex justify-center mb-6"> title="Create Your Secure Account"
<div className="relative"> icon={UserIcon}
<div className={`flex items-center justify-center h-16 w-16 ${getThemeClasses("bg-gradient-secondary")} rounded-2xl shadow-xl`}> />
<UserIcon className="h-8 w-8 text-white" /> <div className="text-center mb-8">
</div>
<div className={`absolute -inset-1 ${getThemeClasses("bg-gradient-secondary")} rounded-2xl blur opacity-20`}></div>
</div>
</div>
<h2 className={`text-3xl font-black ${getThemeClasses("text-primary")} mb-2`}>
Create Your Secure Account
</h2>
<p className={`${getThemeClasses("text-secondary")} mb-2`}> <p className={`${getThemeClasses("text-secondary")} mb-2`}>
Join MapleFile and protect your files with end-to-end encryption Join MapleFile and protect your files with end-to-end encryption
</p> </p>
@ -521,7 +493,7 @@ const Register = () => {
disabled={loading} disabled={loading}
/> />
{errors.agree_terms_of_service && ( {errors.agree_terms_of_service && (
<p className="ml-7 text-xs text-red-600"> <p className={`ml-7 text-xs ${getThemeClasses("text-danger")}`}>
{errors.agree_terms_of_service} {errors.agree_terms_of_service}
</p> </p>
)} )}
@ -534,7 +506,7 @@ const Register = () => {
onChange={(checked) => handleInputChange("agree_promotions", checked)} onChange={(checked) => handleInputChange("agree_promotions", checked)}
disabled={loading} disabled={loading}
/> />
<p className="ml-7 mt-1 text-xs text-gray-600"> <p className={`ml-7 mt-1 text-xs ${getThemeClasses("text-secondary")}`}>
We'll use your email address to send you important updates. You can unsubscribe anytime. Data retention: Until you unsubscribe or close your account. We'll use your email address to send you important updates. You can unsubscribe anytime. Data retention: Until you unsubscribe or close your account.
</p> </p>
</div> </div>
@ -552,7 +524,7 @@ const Register = () => {
} }
disabled={loading} disabled={loading}
/> />
<p className="ml-7 mt-1 text-xs text-gray-600"> <p className={`ml-7 mt-1 text-xs ${getThemeClasses("text-secondary")}`}>
We collect anonymized usage data (page views, feature usage) to improve our service. No personal files or content are tracked. Data retention: 24 months. You can opt-out anytime in settings. We collect anonymized usage data (page views, feature usage) to improve our service. No personal files or content are tracked. Data retention: 24 months. You can opt-out anytime in settings.
</p> </p>
</div> </div>
@ -629,7 +601,7 @@ const Register = () => {
Your files will be encrypted before leaving your device Your files will be encrypted before leaving your device
</li> </li>
</ul> </ul>
<div className="mt-4 pt-4 border-t border-gray-200"> <div className={`mt-4 pt-4 border-t ${getThemeClasses("border-secondary")}`}>
<p className="text-xs font-semibold mb-2">Data We Collect & Why (GDPR Transparency):</p> <p className="text-xs font-semibold mb-2">Data We Collect & Why (GDPR Transparency):</p>
<ul className="text-xs space-y-1"> <ul className="text-xs space-y-1">
<li><strong> Name & Email:</strong> Account management, authentication, communication (Legal basis: Contract)</li> <li><strong> Name & Email:</strong> Account management, authentication, communication (Legal basis: Contract)</li>
@ -658,27 +630,6 @@ const Register = () => {
{/* Footer */} {/* Footer */}
<GDPRFooter /> <GDPRFooter />
<style>{`
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fade-in-up 0.6s ease-out;
}
.animate-fade-in-up-delay {
animation: fade-in-up 0.6s ease-out 0.2s both;
}
`}</style>
</div> </div>
); );
}; };