additional GUI work
This commit is contained in:
parent
3c6899ace5
commit
81f60acd06
8 changed files with 467 additions and 285 deletions
|
|
@ -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;
|
||||
|
|
@ -1,11 +1,25 @@
|
|||
// 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 { 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
|
||||
*
|
||||
* @param {string} label - Input label
|
||||
|
|
@ -30,7 +44,7 @@ import { useUIXTheme } from "../themes/useUIXTheme.jsx";
|
|||
* Correct usage: onChange={(value) => setMyValue(value)}
|
||||
* Incorrect usage: onChange={(e) => setMyValue(e.target.value)} // This will cause errors!
|
||||
*/
|
||||
function Input({
|
||||
const Input = memo(function Input({
|
||||
label,
|
||||
type = "text",
|
||||
placeholder,
|
||||
|
|
@ -65,33 +79,51 @@ function Input({
|
|||
);
|
||||
}, [props.id, props.name]);
|
||||
|
||||
const sizeClasses = {
|
||||
sm: "px-3 py-2 text-sm",
|
||||
md: "px-4 py-3 text-base",
|
||||
lg: "px-5 py-4 text-base sm:text-lg",
|
||||
};
|
||||
// Memoize theme classes to prevent multiple getThemeClasses calls on each render
|
||||
const themeClasses = useMemo(
|
||||
() => ({
|
||||
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 = {
|
||||
sm: "text-sm",
|
||||
md: "text-base",
|
||||
lg: "text-base sm:text-lg",
|
||||
};
|
||||
// Memoize the onChange handler to prevent recreation on each render
|
||||
const handleChange = useCallback(
|
||||
(e) => {
|
||||
if (onChange) {
|
||||
onChange(e.target.value);
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
// Memoize computed values
|
||||
const currentLength = value ? value.length : 0;
|
||||
const warningThreshold = characterCountWarning || characterWarningThreshold;
|
||||
const isNearLimit =
|
||||
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 (
|
||||
<div className={className}>
|
||||
{label && (
|
||||
<label
|
||||
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}
|
||||
{required && (
|
||||
<span className={`ml-1 ${getThemeClasses("text-danger")}`}>*</span>
|
||||
<span className={`ml-1 ${themeClasses.textDanger}`}>*</span>
|
||||
)}
|
||||
</label>
|
||||
)}
|
||||
|
|
@ -102,14 +134,14 @@ function Input({
|
|||
flex items-center w-full
|
||||
border-2 rounded-xl shadow-sm
|
||||
transition-all duration-200
|
||||
${error ? getThemeClasses("input-border-error") : getThemeClasses("input-border")}
|
||||
${disabled ? `${getThemeClasses("bg-disabled")} cursor-not-allowed` : "bg-white"}
|
||||
focus-within:outline-none focus-within:${getThemeClasses("input-focus-ring")}
|
||||
${error ? themeClasses.inputBorderError : themeClasses.inputBorder}
|
||||
${disabled ? `${themeClasses.bgDisabled} cursor-not-allowed` : "bg-white"}
|
||||
focus-within:outline-none focus-within:${themeClasses.inputFocusRing}
|
||||
`}>
|
||||
<span className={`
|
||||
${sizeClasses[size]}
|
||||
${sizeClass}
|
||||
pr-0 font-medium
|
||||
${disabled ? getThemeClasses("text-muted") : getThemeClasses("text-secondary")}
|
||||
${disabled ? themeClasses.textMuted : themeClasses.textSecondary}
|
||||
select-none whitespace-nowrap
|
||||
`}>
|
||||
{prefix}
|
||||
|
|
@ -120,14 +152,14 @@ function Input({
|
|||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
readOnly={readOnly}
|
||||
maxLength={maxLength}
|
||||
className={`
|
||||
flex-1
|
||||
${sizeClasses[size]}
|
||||
${sizeClass}
|
||||
pl-0
|
||||
bg-transparent
|
||||
border-0
|
||||
|
|
@ -142,7 +174,7 @@ function Input({
|
|||
<>
|
||||
{Icon && (
|
||||
<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>
|
||||
)}
|
||||
<input
|
||||
|
|
@ -151,25 +183,21 @@ function Input({
|
|||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
readOnly={readOnly}
|
||||
maxLength={maxLength}
|
||||
className={`
|
||||
w-full
|
||||
${sizeClasses[size]}
|
||||
${sizeClass}
|
||||
${Icon ? "pl-10" : "pl-5"}
|
||||
border-2 rounded-xl shadow-sm
|
||||
transition-all duration-200
|
||||
placeholder-gray-400
|
||||
focus:outline-none ${getThemeClasses("input-focus-ring")} ${getThemeClasses("input-border")}
|
||||
${
|
||||
error
|
||||
? getThemeClasses("input-border-error")
|
||||
: getThemeClasses("input-border")
|
||||
}
|
||||
${disabled ? `${getThemeClasses("bg-disabled")} cursor-not-allowed` : "bg-white"}
|
||||
focus:outline-none ${themeClasses.inputFocusRing} ${themeClasses.inputBorder}
|
||||
${error ? themeClasses.inputBorderError : themeClasses.inputBorder}
|
||||
${disabled ? `${themeClasses.bgDisabled} cursor-not-allowed` : "bg-white"}
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
|
|
@ -182,7 +210,7 @@ function Input({
|
|||
<div className="mt-1 text-right">
|
||||
<span
|
||||
className={`text-xs ${
|
||||
isNearLimit ? "text-amber-600" : getThemeClasses("text-muted")
|
||||
isNearLimit ? "text-amber-600" : themeClasses.textMuted
|
||||
}`}
|
||||
>
|
||||
{currentLength}/{maxLength} characters
|
||||
|
|
@ -192,7 +220,7 @@ function Input({
|
|||
|
||||
{/* Helper text */}
|
||||
{helperText && !error && (
|
||||
<p className={`mt-1 text-xs ${getThemeClasses("text-muted")}`}>
|
||||
<p className={`mt-1 text-xs ${themeClasses.textMuted}`}>
|
||||
{helperText}
|
||||
</p>
|
||||
)}
|
||||
|
|
@ -200,16 +228,42 @@ function Input({
|
|||
{/* Error message */}
|
||||
{error && (
|
||||
<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
|
||||
className={`h-4 w-4 mr-1 ${getThemeClasses("text-danger")}`}
|
||||
className={`h-4 w-4 mr-1 ${themeClasses.textDanger}`}
|
||||
/>
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</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;
|
||||
|
|
|
|||
104
web/maplefile-frontend/src/components/UIX/Logo/Logo.jsx
Normal file
104
web/maplefile-frontend/src/components/UIX/Logo/Logo.jsx
Normal 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;
|
||||
80
web/maplefile-frontend/src/components/UIX/NavBar/NavBar.jsx
Normal file
80
web/maplefile-frontend/src/components/UIX/NavBar/NavBar.jsx
Normal 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;
|
||||
|
|
@ -14,6 +14,9 @@
|
|||
export { default as ActionCard } from "./ActionCard/ActionCard";
|
||||
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)
|
||||
export { default as Alert, Notification, Message } from "./Alert/Alert";
|
||||
|
||||
|
|
@ -175,6 +178,9 @@ export {
|
|||
export { default as LoadingOverlay } from "./Loading/LoadingOverlay";
|
||||
export { default as Spinner, LoadingSpinner } from "./Loading/Spinner";
|
||||
|
||||
// Logo Component (New - MapleFile branding with icon)
|
||||
export { default as Logo } from "./Logo/Logo";
|
||||
|
||||
// Modal Component
|
||||
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)
|
||||
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)
|
||||
export { default as PageContainer } from "./PageContainer/PageContainer";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@
|
|||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { useNavigate, Link } from "react-router";
|
||||
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 {
|
||||
ArrowRightIcon,
|
||||
LockClosedIcon,
|
||||
ShieldCheckIcon,
|
||||
SparklesIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
|
@ -172,33 +171,13 @@ const RequestOTT = () => {
|
|||
|
||||
{/* Header */}
|
||||
<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">
|
||||
<Link to="/" className="inline-flex items-center space-x-3 group">
|
||||
<div className="relative">
|
||||
<div className={`absolute inset-0 ${getThemeClasses("bg-gradient-secondary")} rounded-xl opacity-0 group-hover:opacity-100 blur-xl transition-opacity duration-300`}></div>
|
||||
<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" />
|
||||
</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>
|
||||
<NavBar
|
||||
links={[
|
||||
{ to: "/register", label: "Need an account?" },
|
||||
{ to: "/recovery", label: "Forgot password?" }
|
||||
]}
|
||||
transparent
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
|
|
@ -216,17 +195,10 @@ const RequestOTT = () => {
|
|||
/>
|
||||
|
||||
{/* Welcome Message */}
|
||||
<div className="text-center">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="relative">
|
||||
<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>
|
||||
<AuthPageHeader
|
||||
title="Secure Sign In"
|
||||
icon={ShieldCheckIcon}
|
||||
/>
|
||||
|
||||
{/* Login Form */}
|
||||
<Card
|
||||
|
|
|
|||
|
|
@ -3,27 +3,20 @@
|
|||
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, Badge } from "../../../components/UIX";
|
||||
import { Button, Input, Alert, Card, GDPRFooter, ProgressIndicator, NavBar, AuthPageHeader } from "../../../components/UIX";
|
||||
import { useUIXTheme } from "../../../components/UIX/themes/useUIXTheme";
|
||||
import {
|
||||
ArrowRightIcon,
|
||||
ArrowLeftIcon,
|
||||
LockClosedIcon,
|
||||
ShieldCheckIcon,
|
||||
CheckIcon,
|
||||
ExclamationTriangleIcon,
|
||||
InformationCircleIcon,
|
||||
EnvelopeIcon,
|
||||
KeyIcon,
|
||||
ClockIcon,
|
||||
ExclamationCircleIcon,
|
||||
DocumentTextIcon,
|
||||
ServerIcon,
|
||||
EyeSlashIcon,
|
||||
} 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("");
|
||||
|
|
@ -98,78 +91,34 @@ const InitiateRecovery = () => {
|
|||
}, [navigate]);
|
||||
|
||||
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 */}
|
||||
<nav className="bg-white/95 backdrop-blur-sm border-b border-gray-100">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<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>
|
||||
<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 */}
|
||||
<div className="flex items-center justify-center mb-8">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center">
|
||||
<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">
|
||||
1
|
||||
</div>
|
||||
<span className="ml-2 text-sm font-semibold text-gray-900">
|
||||
Email
|
||||
</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>
|
||||
<ProgressIndicator
|
||||
steps={[
|
||||
{ label: "Email" },
|
||||
{ label: "Verify" },
|
||||
{ label: "Reset" }
|
||||
]}
|
||||
currentStep={1}
|
||||
className="mb-0"
|
||||
/>
|
||||
|
||||
{/* Header */}
|
||||
<div className="text-center animate-fade-in-up">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="relative">
|
||||
<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>
|
||||
<AuthPageHeader
|
||||
title="Account Recovery"
|
||||
icon={KeyIcon}
|
||||
/>
|
||||
|
||||
{/* Important Notice */}
|
||||
<Alert type="warning" className="animate-fade-in-up-delay">
|
||||
<Alert type="warning">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-1">
|
||||
Before You Begin
|
||||
|
|
@ -183,11 +132,11 @@ const InitiateRecovery = () => {
|
|||
</Alert>
|
||||
|
||||
{/* Form Card */}
|
||||
<Card className="shadow-2xl animate-fade-in-up-delay">
|
||||
<div className="p-8">
|
||||
<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 animate-fade-in">
|
||||
<Alert type="error" className="mb-6">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-1">
|
||||
Recovery Error
|
||||
|
|
@ -199,29 +148,19 @@ const InitiateRecovery = () => {
|
|||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
<Input
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
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>
|
||||
<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"
|
||||
|
|
@ -231,13 +170,26 @@ const InitiateRecovery = () => {
|
|||
fullWidth
|
||||
loading={loading}
|
||||
loadingText="Starting Recovery..."
|
||||
className="group shadow-lg hover:shadow-xl transform hover:scale-105"
|
||||
>
|
||||
<span className="flex items-center">
|
||||
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>
|
||||
</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 */}
|
||||
|
|
@ -262,52 +214,7 @@ const InitiateRecovery = () => {
|
|||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<GDPRFooter containerClassName="bg-white/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>
|
||||
<GDPRFooter containerClassName={`${getThemeClasses("bg-card")}/75`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,15 +3,13 @@
|
|||
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
||||
import { useNavigate, Link } from "react-router";
|
||||
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 {
|
||||
ArrowRightIcon,
|
||||
LockClosedIcon,
|
||||
ShieldCheckIcon,
|
||||
CheckIcon,
|
||||
ExclamationTriangleIcon,
|
||||
InformationCircleIcon,
|
||||
UserIcon,
|
||||
EnvelopeIcon,
|
||||
PhoneIcon,
|
||||
|
|
@ -287,45 +285,19 @@ const Register = () => {
|
|||
return (
|
||||
<div className={`min-h-screen ${getThemeClasses("bg-gradient-primary")} flex flex-col`}>
|
||||
{/* Navigation */}
|
||||
<nav className="bg-white/95 backdrop-blur-sm border-b border-gray-100">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<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>
|
||||
<NavBar
|
||||
links={[{ to: "/login", label: "Already have an account?" }]}
|
||||
/>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8 animate-fade-in-up">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="relative">
|
||||
<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>
|
||||
<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>
|
||||
<AuthPageHeader
|
||||
title="Create Your Secure Account"
|
||||
icon={UserIcon}
|
||||
/>
|
||||
<div className="text-center mb-8">
|
||||
<p className={`${getThemeClasses("text-secondary")} mb-2`}>
|
||||
Join MapleFile and protect your files with end-to-end encryption
|
||||
</p>
|
||||
|
|
@ -521,7 +493,7 @@ const Register = () => {
|
|||
disabled={loading}
|
||||
/>
|
||||
{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}
|
||||
</p>
|
||||
)}
|
||||
|
|
@ -534,7 +506,7 @@ const Register = () => {
|
|||
onChange={(checked) => handleInputChange("agree_promotions", checked)}
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -552,7 +524,7 @@ const Register = () => {
|
|||
}
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -629,7 +601,7 @@ const Register = () => {
|
|||
Your files will be encrypted before leaving your device
|
||||
</li>
|
||||
</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>
|
||||
<ul className="text-xs space-y-1">
|
||||
<li><strong>• Name & Email:</strong> Account management, authentication, communication (Legal basis: Contract)</li>
|
||||
|
|
@ -658,27 +630,6 @@ const Register = () => {
|
|||
|
||||
{/* Footer */}
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue