diff --git a/web/maplefile-frontend/src/components/UIX/AuthPageHeader/AuthPageHeader.jsx b/web/maplefile-frontend/src/components/UIX/AuthPageHeader/AuthPageHeader.jsx
new file mode 100644
index 0000000..0a32324
--- /dev/null
+++ b/web/maplefile-frontend/src/components/UIX/AuthPageHeader/AuthPageHeader.jsx
@@ -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 (
+
+ {Icon && (
+
+ )}
+ {title &&
{title} }
+
+ );
+});
+
+AuthPageHeader.displayName = "AuthPageHeader";
+
+export default AuthPageHeader;
diff --git a/web/maplefile-frontend/src/components/UIX/Input/Input.jsx b/web/maplefile-frontend/src/components/UIX/Input/Input.jsx
index 9b0ff21..c42b0f8 100644
--- a/web/maplefile-frontend/src/components/UIX/Input/Input.jsx
+++ b/web/maplefile-frontend/src/components/UIX/Input/Input.jsx
@@ -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 (
{label && (
{label}
{required && (
- *
+ *
)}
)}
@@ -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}
`}>
{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 && (
-
+
)}
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({
{currentLength}/{maxLength} characters
@@ -192,7 +220,7 @@ function Input({
{/* Helper text */}
{helperText && !error && (
-
+
{helperText}
)}
@@ -200,16 +228,42 @@ function Input({
{/* Error message */}
{error && (
{error}
)}
);
-}
+},
+// 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;
diff --git a/web/maplefile-frontend/src/components/UIX/Logo/Logo.jsx b/web/maplefile-frontend/src/components/UIX/Logo/Logo.jsx
new file mode 100644
index 0000000..1de7187
--- /dev/null
+++ b/web/maplefile-frontend/src/components/UIX/Logo/Logo.jsx
@@ -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 = (
+ <>
+
+ {showText && MapleFile }
+ >
+ );
+
+ if (to) {
+ return (
+
+ {content}
+
+ );
+ }
+
+ return {content}
;
+});
+
+Logo.displayName = "Logo";
+
+export default Logo;
diff --git a/web/maplefile-frontend/src/components/UIX/NavBar/NavBar.jsx b/web/maplefile-frontend/src/components/UIX/NavBar/NavBar.jsx
new file mode 100644
index 0000000..8148a12
--- /dev/null
+++ b/web/maplefile-frontend/src/components/UIX/NavBar/NavBar.jsx
@@ -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 (
+
+ {links.map((link, index) => (
+
+ {link.label}
+
+ ))}
+
+ );
+ }, [links, linkClasses, children]);
+
+ return (
+
+
+
+ );
+});
+
+NavBar.displayName = "NavBar";
+
+export default NavBar;
diff --git a/web/maplefile-frontend/src/components/UIX/index.jsx b/web/maplefile-frontend/src/components/UIX/index.jsx
index 3ab92f4..a6632c8 100644
--- a/web/maplefile-frontend/src/components/UIX/index.jsx
+++ b/web/maplefile-frontend/src/components/UIX/index.jsx
@@ -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";
diff --git a/web/maplefile-frontend/src/pages/Anonymous/Login/RequestOTT.jsx b/web/maplefile-frontend/src/pages/Anonymous/Login/RequestOTT.jsx
index 627581e..c2ff07a 100644
--- a/web/maplefile-frontend/src/pages/Anonymous/Login/RequestOTT.jsx
+++ b/web/maplefile-frontend/src/pages/Anonymous/Login/RequestOTT.jsx
@@ -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 */}
-
-
-
-
- MapleFile
-
-
-
-
- Need an account?
-
-
- Forgot password?
-
-
-
+
{/* Main Content */}
@@ -216,17 +195,10 @@ const RequestOTT = () => {
/>
{/* Welcome Message */}
-
+
{/* Login Form */}
{
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 (
-
+
{/* Navigation */}
-
-
-
-
-
-
-
-
- MapleFile
-
-
-
-
-
- Need an account?
-
-
-
-
-
-
+
{/* Main Content */}
{/* Progress Indicator */}
-
+
{/* Header */}
-
-
-
- Account Recovery
-
-
+
{/* Important Notice */}
-
+
Before You Begin
@@ -183,11 +132,11 @@ const InitiateRecovery = () => {
{/* Form Card */}
-
-
+
+
{/* Error Message */}
{error && (
-
+
Recovery Error
@@ -199,29 +148,19 @@ const InitiateRecovery = () => {
{/* Form */}
{/* Back to Login Button */}
@@ -262,52 +214,7 @@ const InitiateRecovery = () => {
{/* Footer */}
-
-
- {/* CSS Animations */}
-
+
);
};
diff --git a/web/maplefile-frontend/src/pages/Anonymous/Register/Register.jsx b/web/maplefile-frontend/src/pages/Anonymous/Register/Register.jsx
index 9a069f8..5290df4 100644
--- a/web/maplefile-frontend/src/pages/Anonymous/Register/Register.jsx
+++ b/web/maplefile-frontend/src/pages/Anonymous/Register/Register.jsx
@@ -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 (
{/* Navigation */}
-
-
-
-
-
-
-
-
- MapleFile
-
-
-
-
- Already have an account?
-
-
-
-
-
+
{/* Main Content */}
{/* Header */}
-
-
-
- Create Your Secure Account
-
+
+
Join MapleFile and protect your files with end-to-end encryption
@@ -521,7 +493,7 @@ const Register = () => {
disabled={loading}
/>
{errors.agree_terms_of_service && (
-
+
{errors.agree_terms_of_service}
)}
@@ -534,7 +506,7 @@ const Register = () => {
onChange={(checked) => handleInputChange("agree_promotions", checked)}
disabled={loading}
/>
-
+
We'll use your email address to send you important updates. You can unsubscribe anytime. Data retention: Until you unsubscribe or close your account.
@@ -552,7 +524,7 @@ const Register = () => {
}
disabled={loading}
/>
-
+
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.
@@ -629,7 +601,7 @@ const Register = () => {
Your files will be encrypted before leaving your device
-
+
Data We Collect & Why (GDPR Transparency):
• Name & Email: Account management, authentication, communication (Legal basis: Contract)
@@ -658,27 +630,6 @@ const Register = () => {
{/* Footer */}
-
-
);
};