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
|
// 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;
|
||||||
|
|
|
||||||
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 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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 identity—see 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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue