diff --git a/package-lock.json b/package-lock.json index 627ff3f..80c48f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@tanstack/router-devtools": "^1.143.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.23.26", "lucide-react": "^0.562.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -3012,6 +3013,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.23.26", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz", + "integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3614,6 +3642,21 @@ "node": "*" } }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4147,6 +4190,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index ee622ea..e113be4 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@tanstack/router-devtools": "^1.143.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.23.26", "lucide-react": "^0.562.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/assets/images/README.md b/src/assets/images/README.md new file mode 100644 index 0000000..a004014 --- /dev/null +++ b/src/assets/images/README.md @@ -0,0 +1,46 @@ +# Images Directory + +This directory contains all image assets for the AgenticIQ application, organized by category. + +## Folder Structure + +``` +images/ +├── logo/ # Brand logos and identity assets +├── icons/ # UI icons and symbols +├── auth/ # Authentication-related images (social login icons, etc.) +├── backgrounds/ # Background images and decorative elements +└── ui/ # General UI component images +``` + +## Usage Guidelines + +### Importing Images in Components + +```typescript +// Example: Importing a logo +import agenticiqLogo from '@/assets/images/logo/agenticiq-logo.png'; + +// Example: Importing an icon +import googleIcon from '@/assets/images/auth/google-icon.svg'; +``` + +### File Naming Convention + +- Use **kebab-case** for all filenames (e.g., `agenticiq-logo.png`, `google-icon.svg`) +- Be descriptive and avoid abbreviations +- Include size/resolution suffix if multiple versions exist (e.g., `logo-256x256.png`) + +### Image Formats + +- **Logos**: PNG (with transparency) or SVG +- **Icons**: SVG (preferred) or PNG +- **Photos**: WebP (preferred) or JPG +- **Backgrounds**: WebP or PNG + +### Optimization + +- Optimize images before adding them to this directory +- Use appropriate formats for the use case +- Consider responsive images for different screen densities + diff --git a/src/assets/images/auth/README.md b/src/assets/images/auth/README.md new file mode 100644 index 0000000..5202b35 --- /dev/null +++ b/src/assets/images/auth/README.md @@ -0,0 +1,35 @@ +# Authentication Assets + +This directory contains images related to authentication and social login. + +## Files to Add + +### Social Login Icons +- `google-icon.svg` - Google sign-in button icon +- `azure-icon.svg` - Microsoft Azure sign-in button icon +- `github-icon.svg` - GitHub sign-in icon (if needed) +- `facebook-icon.svg` - Facebook sign-in icon (if needed) + +### Authentication UI +- `auth-background.svg` - Background pattern for auth pages (if needed) +- `lock-icon.svg` - Security/lock icon +- `user-icon.svg` - User profile icon + +## Usage Example + +```typescript +import googleIcon from '@/assets/images/auth/google-icon.svg'; +import azureIcon from '@/assets/images/auth/azure-icon.svg'; + + +``` + +## Icon Specifications + +- **Size**: 24x24px or 32x32px for social login buttons +- **Format**: SVG preferred for scalability +- **Style**: Match brand guidelines for each provider + diff --git a/src/assets/images/backgrounds/README.md b/src/assets/images/backgrounds/README.md new file mode 100644 index 0000000..d0ac55e --- /dev/null +++ b/src/assets/images/backgrounds/README.md @@ -0,0 +1,32 @@ +# Background Assets + +This directory contains background images and decorative elements. + +## Files to Add + +### Decorative Elements +- `gradient-blob-1.svg` - Decorative gradient shape (if needed as image) +- `gradient-blob-2.svg` - Additional decorative element +- `pattern-overlay.svg` - Pattern overlay (if needed) + +### Background Images +- `auth-background.jpg` or `.webp` - Full background image for auth pages (if needed) +- `dashboard-background.svg` - Dashboard background pattern + +## Usage Example + +```typescript +import backgroundPattern from '@/assets/images/backgrounds/pattern-overlay.svg'; + +
+``` + +## Notes + +- Most decorative backgrounds are created with CSS gradients (as in login-page.tsx) +- Only add image files if CSS gradients cannot achieve the desired effect +- Optimize all background images for performance + diff --git a/src/assets/images/backgrounds/bottom-left-wave1 .svg b/src/assets/images/backgrounds/bottom-left-wave1 .svg new file mode 100644 index 0000000..e97c83a --- /dev/null +++ b/src/assets/images/backgrounds/bottom-left-wave1 .svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/images/backgrounds/bottom-left-wave2.svg b/src/assets/images/backgrounds/bottom-left-wave2.svg new file mode 100644 index 0000000..f0f20ed --- /dev/null +++ b/src/assets/images/backgrounds/bottom-left-wave2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/backgrounds/logo-glow-bg.svg b/src/assets/images/backgrounds/logo-glow-bg.svg new file mode 100644 index 0000000..41b1a9d --- /dev/null +++ b/src/assets/images/backgrounds/logo-glow-bg.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/images/backgrounds/top-right-glow.svg b/src/assets/images/backgrounds/top-right-glow.svg new file mode 100644 index 0000000..eeda5f1 --- /dev/null +++ b/src/assets/images/backgrounds/top-right-glow.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/images/icons/README.md b/src/assets/images/icons/README.md new file mode 100644 index 0000000..d3fc77f --- /dev/null +++ b/src/assets/images/icons/README.md @@ -0,0 +1,33 @@ +# Icon Assets + +This directory contains UI icons and symbols used throughout the application. + +## Files to Add + +### Navigation Icons +- `home-icon.svg` +- `dashboard-icon.svg` +- `settings-icon.svg` + +### Action Icons +- `eye-icon.svg` - Show/hide password +- `eye-close-icon.svg` - Hide password +- `search-icon.svg` +- `plus-icon.svg` +- `edit-icon.svg` +- `delete-icon.svg` + +### Status Icons +- `success-icon.svg` +- `error-icon.svg` +- `warning-icon.svg` +- `info-icon.svg` + +## Usage Example + +```typescript +import eyeIcon from '@/assets/images/icons/eye-icon.svg'; + +Show password +``` + diff --git a/src/assets/images/logo/AgenticIQLogo.svg b/src/assets/images/logo/AgenticIQLogo.svg new file mode 100644 index 0000000..0199d43 --- /dev/null +++ b/src/assets/images/logo/AgenticIQLogo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/images/logo/README.md b/src/assets/images/logo/README.md new file mode 100644 index 0000000..c00701d --- /dev/null +++ b/src/assets/images/logo/README.md @@ -0,0 +1,27 @@ +# Logo Assets + +This directory contains brand logos and identity assets for AgenticIQ. + +## Files to Add + +### Primary Logo +- `agenticiq-logo.png` or `agenticiq-logo.svg` - Main AgenticIQ logo +- `agenticiq-logo-white.png` - White variant for dark backgrounds +- `agenticiq-logo-dark.png` - Dark variant for light backgrounds + +### Logo Variants +- `agenticiq-logo-horizontal.svg` - Horizontal layout variant +- `agenticiq-logo-vertical.svg` - Vertical/stacked layout variant +- `agenticiq-icon.svg` - Icon-only version (favicon, app icon) + +### Trademark Badge +- `trademark-badge.svg` - TM badge component (if separate from logo) + +## Usage Example + +```typescript +import agenticiqLogo from '@/assets/images/logo/agenticiq-logo.svg'; + +AgenticIQ Logo +``` + diff --git a/src/assets/images/ui/README.md b/src/assets/images/ui/README.md new file mode 100644 index 0000000..fab2f2d --- /dev/null +++ b/src/assets/images/ui/README.md @@ -0,0 +1,31 @@ +# UI Component Assets + +This directory contains images used in UI components throughout the application. + +## Files to Add + +### Profile & User +- `default-avatar.png` - Default user avatar placeholder +- `profile-placeholder.svg` - Profile image placeholder + +### Dashboard Components +- `metric-card-icon.svg` - Icon for metric cards +- `empty-state-illustration.svg` - Empty state illustrations + +### General UI +- `loading-spinner.svg` - Loading animation (if not CSS-based) +- `error-illustration.svg` - Error state illustration +- `success-illustration.svg` - Success state illustration + +## Usage Example + +```typescript +import defaultAvatar from '@/assets/images/ui/default-avatar.png'; + +User avatar +``` + diff --git a/src/components/auth/auth-button.tsx b/src/components/auth/auth-button.tsx new file mode 100644 index 0000000..e1dc3c7 --- /dev/null +++ b/src/components/auth/auth-button.tsx @@ -0,0 +1,219 @@ +/** + * AuthButton Component + * @description Reusable button components for authentication forms. + * Includes primary action button and social login buttons. + * Follows AgenticIQ Enterprise Coding Guidelines v1.0 + */ + +import { type ButtonHTMLAttributes, type ReactNode } from 'react'; + +/** + * Props for AuthButton component + */ +interface AuthButtonProps extends ButtonHTMLAttributes { + /** Button content */ + children: ReactNode; + /** Loading state */ + isLoading?: boolean; + /** Full width button */ + fullWidth?: boolean; +} + +/** + * Props for SocialButton component + */ +interface SocialButtonProps extends ButtonHTMLAttributes { + /** Button text */ + children: ReactNode; + /** Icon element to show before text */ + icon: ReactNode; +} + +/** + * Props for TextButton component + */ +interface TextButtonProps extends ButtonHTMLAttributes { + /** Button text */ + children: ReactNode; +} + +/** + * Google icon for social login + * @returns {JSX.Element} Google SVG icon + */ +export function GoogleIcon(): JSX.Element { + return ( + + + + + + + ); +} + +/** + * Azure icon for social login + * @returns {JSX.Element} Azure SVG icon + */ +export function AzureIcon(): JSX.Element { + return ( + + + + + + + ); +} + +/** + * AuthButton component + * @description Primary action button with cyan/teal background. + * Used for main form submissions (Sign In, Sign Up, etc.). + * @param {AuthButtonProps} props - Component props + * @returns {JSX.Element} AuthButton element + */ +export function AuthButton({ + children, + isLoading = false, + fullWidth = true, + disabled, + className = '', + ...buttonProps +}: AuthButtonProps): JSX.Element { + return ( + + ); +} + +/** + * SocialButton component + * @description Social login button with white background. + * Used for Google, Azure, and other OAuth providers. + * @param {SocialButtonProps} props - Component props + * @returns {JSX.Element} SocialButton element + */ +export function SocialButton({ + children, + icon, + className = '', + ...buttonProps +}: SocialButtonProps): JSX.Element { + return ( + + ); +} + +/** + * TextButton component + * @description Text-only button for secondary actions (e.g., "Forgot password?"). + * @param {TextButtonProps} props - Component props + * @returns {JSX.Element} TextButton element + */ +export function TextButton({ + children, + className = '', + ...buttonProps +}: TextButtonProps): JSX.Element { + return ( + + ); +} + diff --git a/src/components/auth/auth-card.tsx b/src/components/auth/auth-card.tsx new file mode 100644 index 0000000..3b67f77 --- /dev/null +++ b/src/components/auth/auth-card.tsx @@ -0,0 +1,108 @@ +/** + * AuthCard Component + * @description Reusable authentication card with gradient background. + * Can be used for Sign In, Sign Up, Forgot Password, and other auth screens. + * Follows AgenticIQ Enterprise Coding Guidelines v1.0 + */ + +import { type ReactNode } from 'react'; + +/** + * Props for AuthCard component + */ +interface AuthCardProps { + /** Child elements to render inside the card */ + children: ReactNode; + /** Additional CSS classes for customization */ + className?: string; + /** Card variant - 'signin' has standard padding, 'register' has compact padding */ + variant?: 'signin' | 'register'; +} + +/** + * Props for AuthCardHeader component + */ +interface AuthCardHeaderProps { + /** Main title text */ + title: string; + /** Subtitle/description text */ + subtitle?: string; +} + +/** + * Props for AuthCardContent component + */ +interface AuthCardContentProps { + /** Child elements (form fields, etc.) */ + children: ReactNode; + /** Additional CSS classes */ + className?: string; +} + +/** + * AuthCard component + * @description Main container with gradient background (teal to dark blue). + * Provides the card structure for authentication screens. + * No border to avoid dark line artifacts. + * @param {AuthCardProps} props - Component props + * @returns {JSX.Element} AuthCard element + */ +export function AuthCard({ children, className = '', variant = 'signin' }: AuthCardProps): JSX.Element { + const isRegister = variant === 'register'; + + return ( +
+ {children} +
+ ); +} + +/** + * AuthCardHeader component + * @description Header section with title and optional subtitle. + * @param {AuthCardHeaderProps} props - Component props + * @returns {JSX.Element} AuthCardHeader element + */ +export function AuthCardHeader({ title, subtitle }: AuthCardHeaderProps): JSX.Element { + return ( +
+
+

+ {title} +

+ {subtitle && ( +

+ {subtitle} +

+ )} +
+
+ ); +} + +/** + * AuthCardContent component + * @description Content section for form fields and actions. + * @param {AuthCardContentProps} props - Component props + * @returns {JSX.Element} AuthCardContent element + */ +export function AuthCardContent({ children, className = '' }: AuthCardContentProps): JSX.Element { + return ( +
+ {children} +
+ ); +} + diff --git a/src/components/auth/auth-form-card.tsx b/src/components/auth/auth-form-card.tsx new file mode 100644 index 0000000..97e14d0 --- /dev/null +++ b/src/components/auth/auth-form-card.tsx @@ -0,0 +1,311 @@ +/** + * AuthFormCard Component + * @description Unified authentication form with animated transitions between Sign In and Register. + * Uses framer-motion for smooth expand/collapse animations. + * Follows AgenticIQ Enterprise Coding Guidelines v1.0 + */ + +import { useState, type FormEvent } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + AuthCard, + AuthCardHeader, + AuthCardContent, + AuthInput, + AuthButton, + SocialButton, + TextButton, + GoogleIcon, + AzureIcon, +} from './index'; + +/** + * Auth form mode type + */ +type AuthMode = 'signin' | 'register'; + +/** + * Props for AuthFormCard component + */ +interface AuthFormCardProps { + /** Initial mode (signin or register) */ + initialMode?: AuthMode; + /** Callback when sign in is submitted */ + onSignIn?: (email: string, password: string) => void; + /** Callback when register is submitted */ + onRegister?: (data: RegisterData) => void; + /** Callback for Google sign in */ + onGoogleAuth?: () => void; + /** Callback for Azure sign in */ + onAzureAuth?: () => void; + /** Callback for forgot password */ + onForgotPassword?: () => void; +} + +/** + * Register form data interface + */ +interface RegisterData { + firstName: string; + lastName: string; + email: string; + password: string; +} + +/** + * Spring transition for smooth physics-based animations + * Using 'as const' to ensure literal types for framer-motion + */ +const springTransition = { + type: 'spring' as const, + stiffness: 500, + damping: 30, + mass: 1, +}; + +/** + * Animation variants for the collapsible name fields row + */ +const nameFieldsVariants = { + hidden: { + height: 0, + opacity: 0, + marginBottom: 0, + transition: { + height: { ...springTransition, duration: 0.2 }, + opacity: { duration: 0.1 }, + marginBottom: { ...springTransition, duration: 0.2 }, + }, + }, + visible: { + height: 'auto', + opacity: 1, + marginBottom: 16, + transition: { + height: { ...springTransition, duration: 0.2 }, + opacity: { duration: 0.15, delay: 0.05 }, + marginBottom: { ...springTransition, duration: 0.2 }, + }, + }, +}; + +/** + * Animation variants for forgot password link + */ +const forgotPasswordVariants = { + hidden: { + height: 0, + opacity: 0, + marginTop: 0, + transition: { + height: { ...springTransition, duration: 0.15 }, + opacity: { duration: 0.1 }, + marginTop: { ...springTransition, duration: 0.15 }, + }, + }, + visible: { + height: 'auto', + opacity: 1, + marginTop: 8, + transition: { + height: { ...springTransition, duration: 0.15 }, + opacity: { duration: 0.15, delay: 0.05 }, + marginTop: { ...springTransition, duration: 0.15 }, + }, + }, +}; + +/** + * AuthFormCard component + * @description Unified authentication card with animated transitions between Sign In and Register modes. + * @param {AuthFormCardProps} props - Component props + * @returns {JSX.Element} AuthFormCard element + */ +export function AuthFormCard({ + initialMode = 'signin', + onSignIn, + onRegister, + onGoogleAuth, + onAzureAuth, + onForgotPassword, +}: AuthFormCardProps): JSX.Element { + const [mode, setMode] = useState(initialMode); + const isRegister = mode === 'register'; + + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + /** + * Handle form submission based on current mode + * @param {FormEvent} e - Form event + */ + const handleSubmit = (e: FormEvent): void => { + e.preventDefault(); + setIsLoading(true); + + if (isRegister) { + onRegister?.({ firstName, lastName, email, password }); + } else { + onSignIn?.(email, password); + } + + setTimeout(() => setIsLoading(false), 1000); + }; + + /** + * Toggle between Sign In and Register modes + */ + const toggleMode = (): void => { + setMode((prev) => (prev === 'signin' ? 'register' : 'signin')); + setFirstName(''); + setLastName(''); + setEmail(''); + setPassword(''); + }; + + return ( + + + {/* Header Section */} + + + {/* Form Content */} + +
+ {/* Collapsible Name Fields Row */} + + {isRegister && ( + +
+
+ setFirstName(e.target.value)} + isRequired + autoComplete="given-name" + /> +
+
+ setLastName(e.target.value)} + autoComplete="family-name" + /> +
+
+
+ )} +
+ + {/* Email Input */} + + setEmail(e.target.value)} + isRequired + autoComplete={isRegister ? 'email' : 'username'} + /> + + + {/* Password Input */} + + setPassword(e.target.value)} + isRequired + autoComplete={isRegister ? 'new-password' : 'current-password'} + /> + + + {/* Forgot Password Link - Collapses in Register mode */} + + {!isRegister && ( + + + Forgot your password? + + + )} + + + {/* Submit Button */} + + {isRegister ? 'Create Account' : 'Sign In'} + +
+ + {/* Social Login Buttons */} +
+ } + onClick={onGoogleAuth} + className="flex-1" + > + {isRegister ? 'Sign Up with Google' : 'Sign In with Google'} + + } + onClick={onAzureAuth} + className="flex-1" + > + {isRegister ? 'Sign Up with Azure' : 'Sign In with Azure'} + +
+
+ + {/* Footer Section */} +
+ + {isRegister ? 'Already have an account?' : "Don't have an account?"} + + + {isRegister ? 'Sign In' : 'Register here'} + +
+
+
+ ); +} diff --git a/src/components/auth/auth-input.tsx b/src/components/auth/auth-input.tsx new file mode 100644 index 0000000..a1b4c24 --- /dev/null +++ b/src/components/auth/auth-input.tsx @@ -0,0 +1,168 @@ +/** + * AuthInput Component + * @description Reusable input field for authentication forms. + * Features semi-transparent background, white text, and password visibility toggle. + * Follows AgenticIQ Enterprise Coding Guidelines v1.0 + */ + +import { forwardRef, useState, type InputHTMLAttributes } from 'react'; + +/** + * Props for AuthInput component + */ +interface AuthInputProps extends InputHTMLAttributes { + /** Label text for the input */ + label: string; + /** Whether the field is required */ + isRequired?: boolean; + /** Error message to display */ + error?: string; + /** Additional container classes */ + containerClassName?: string; +} + +/** + * Eye closed icon for password visibility toggle + * @returns {JSX.Element} Eye closed SVG icon + */ +function EyeClosedIcon(): JSX.Element { + return ( + + + + ); +} + +/** + * Eye open icon for password visibility toggle + * @returns {JSX.Element} Eye open SVG icon + */ +function EyeOpenIcon(): JSX.Element { + return ( + + + + + ); +} + +/** + * AuthInput component + * @description Styled input field with label, required indicator, and password toggle. + * Semi-transparent background with white text for dark gradient cards. + * @param {AuthInputProps} props - Component props + * @returns {JSX.Element} AuthInput element + */ +export const AuthInput = forwardRef( + function AuthInput( + { + label, + isRequired = false, + error, + containerClassName = '', + type = 'text', + placeholder, + ...inputProps + }, + ref + ): JSX.Element { + const [showPassword, setShowPassword] = useState(false); + const isPasswordType = type === 'password'; + const inputType = isPasswordType && showPassword ? 'text' : type; + + const togglePasswordVisibility = (): void => { + setShowPassword((prev) => !prev); + }; + + return ( +
+ {/* Label */} + + + {/* Input Container */} +
+ + + {/* Password Toggle Icon */} + {isPasswordType && ( + + )} +
+ + {/* Error Message */} + {error && ( +

{error}

+ )} +
+ ); + } +); + diff --git a/src/components/auth/forgot-password-modal.tsx b/src/components/auth/forgot-password-modal.tsx new file mode 100644 index 0000000..f5c9aaf --- /dev/null +++ b/src/components/auth/forgot-password-modal.tsx @@ -0,0 +1,391 @@ +/** + * ForgotPasswordModal Component + * @description Modal dialog for password recovery with two steps: + * 1. Email input (forgot password) + * 2. OTP verification (verify your email) + * Features smooth animated transitions between steps. + * Follows AgenticIQ Enterprise Coding Guidelines v1.0 + */ + +import { useState, useRef, useEffect, type FormEvent, type KeyboardEvent, type ClipboardEvent } from 'react'; +import { motion, AnimatePresence, type Easing } from 'framer-motion'; +import { AuthInput, AuthButton } from './index'; + +/** + * Modal step type - determines which view to show + */ +type ModalStep = 'email' | 'verify'; + +/** + * Props for ForgotPasswordModal component + */ +interface ForgotPasswordModalProps { + /** Whether the modal is visible */ + isOpen: boolean; + /** Callback when modal should close */ + onClose: () => void; + /** Callback when email is submitted */ + onSubmit?: (email: string) => void; + /** Callback when OTP is verified */ + onVerify?: (otp: string) => void; +} + +/** + * Close icon component + */ +function CloseIcon(): JSX.Element { + return ( + + + + ); +} + +/** + * Left arrow icon component + */ +function ArrowLeftIcon(): JSX.Element { + return ( + + + + ); +} + +/** + * Smooth easing curve for animations (cubic-bezier) + */ +const smoothEasing: Easing = [0.4, 0, 0.2, 1]; + +/** + * OTP Input component for 6-digit verification code + */ +function OTPInput({ + otp, + setOtp, +}: { + otp: string[]; + setOtp: React.Dispatch>; +}): JSX.Element { + const inputRefs = useRef<(HTMLInputElement | null)[]>([]); + + // Auto-focus first input when component mounts + useEffect(() => { + const timer = setTimeout(() => { + inputRefs.current[0]?.focus(); + }, 300); + return () => clearTimeout(timer); + }, []); + + const handleChange = (index: number, value: string): void => { + if (value.length > 1) value = value.slice(-1); + if (value && !/^\d$/.test(value)) return; + + const newOtp = [...otp]; + newOtp[index] = value; + setOtp(newOtp); + + if (value && index < 5) { + inputRefs.current[index + 1]?.focus(); + } + }; + + const handleKeyDown = (e: KeyboardEvent, index: number): void => { + if (e.key === 'Backspace' && !otp[index] && index > 0) { + inputRefs.current[index - 1]?.focus(); + } + }; + + const handlePaste = (e: ClipboardEvent): void => { + e.preventDefault(); + const pastedData = e.clipboardData.getData('text').replace(/\D/g, '').slice(0, 6); + const newOtp = [...otp]; + for (let i = 0; i < pastedData.length; i++) { + newOtp[i] = pastedData[i]; + } + setOtp(newOtp); + const nextIndex = Math.min(pastedData.length, 5); + inputRefs.current[nextIndex]?.focus(); + }; + + return ( +
+ {otp.map((digit, index) => ( + { inputRefs.current[index] = el; }} + type="text" + inputMode="numeric" + maxLength={1} + value={digit} + onChange={(e) => handleChange(index, e.target.value)} + onKeyDown={(e) => handleKeyDown(e, index)} + onPaste={handlePaste} + className=" + w-[44px] h-[44px] + bg-[rgba(255,255,255,0.15)] + border border-[rgba(255,255,255,0.1)] + rounded-[8px] + text-center text-white text-[18px] font-medium + outline-none + focus:border-[rgba(255,255,255,0.4)] + focus:bg-[rgba(255,255,255,0.2)] + transition-all duration-200 + " + placeholder="•" + aria-label={`Digit ${index + 1}`} + /> + ))} +
+ ); +} + +/** + * ForgotPasswordModal component + * @description Modal with smooth transitions between email input and OTP verification. + * @param {ForgotPasswordModalProps} props - Component props + * @returns {JSX.Element} ForgotPasswordModal element + */ +export function ForgotPasswordModal({ + isOpen, + onClose, + onSubmit, + onVerify, +}: ForgotPasswordModalProps): JSX.Element { + const [step, setStep] = useState('email'); + const [email, setEmail] = useState(''); + const [otp, setOtp] = useState(['', '', '', '', '', '']); + const [isLoading, setIsLoading] = useState(false); + + const handleClose = (): void => { + onClose(); + setTimeout(() => { + setStep('email'); + setEmail(''); + setOtp(['', '', '', '', '', '']); + setIsLoading(false); + }, 250); + }; + + const handleEmailSubmit = (e: FormEvent): void => { + e.preventDefault(); + setIsLoading(true); + onSubmit?.(email); + setTimeout(() => { + setIsLoading(false); + setStep('verify'); + }, 600); + }; + + const handleVerifySubmit = (e: FormEvent): void => { + e.preventDefault(); + const otpString = otp.join(''); + if (otpString.length !== 6) return; + + setIsLoading(true); + onVerify?.(otpString); + setTimeout(() => { + setIsLoading(false); + handleClose(); + }, 800); + }; + + const handleResendCode = (): void => { + console.log('Resend code to:', email); + }; + + const handleBackdropClick = (): void => handleClose(); + const handleModalClick = (e: React.MouseEvent): void => e.stopPropagation(); + + const isEmailStep = step === 'email'; + + return ( + + {isOpen && ( + <> + {/* Backdrop */} + + + {/* Modal Container */} + + {/* Modal Card */} + + {/* Header with Close Button */} +
+ {/* Title Section */} +
+ + +

+ {isEmailStep ? 'Forgot your password' : 'Verify Your Email'} +

+

+ {isEmailStep ? 'and continue' : 'Enter the 6-Digit Verification Code'} +

+
+
+
+ + {/* Close Button */} + +
+ + {/* Form Content */} +
+ + {isEmailStep ? ( + + setEmail(e.target.value)} + isRequired + autoComplete="email" + /> + + Submit + + + ) : ( + + + + Continue + + + )} + +
+ + {/* Footer */} + + {isEmailStep ? ( + + + Back to Sign In + + ) : ( + + + Didn't you receive any code? + + + + )} + +
+
+ + )} +
+ ); +} diff --git a/src/components/auth/index.ts b/src/components/auth/index.ts new file mode 100644 index 0000000..0bda060 --- /dev/null +++ b/src/components/auth/index.ts @@ -0,0 +1,12 @@ +/** + * Auth Components Barrel Export + * @description Exports all authentication-related components. + * Follows AgenticIQ Enterprise Coding Guidelines v1.0 + */ + +export { AuthCard, AuthCardHeader, AuthCardContent } from './auth-card'; +export { AuthInput } from './auth-input'; +export { AuthButton, SocialButton, TextButton, GoogleIcon, AzureIcon } from './auth-button'; +export { AuthFormCard } from './auth-form-card'; +export { ForgotPasswordModal } from './forgot-password-modal'; + diff --git a/src/pages/login-page.tsx b/src/pages/login-page.tsx new file mode 100644 index 0000000..14e024e --- /dev/null +++ b/src/pages/login-page.tsx @@ -0,0 +1,190 @@ +/** + * Login Page Component + * @description Main authentication page with 2-column responsive layout. + * Left column displays branding/marketing content, right column contains auth form. + * Supports both Sign In and Register modes with animated transitions. + * Includes Forgot Password modal with OTP verification. + * Follows AgenticIQ Enterprise Coding Guidelines v1.0 + */ + +import { useState } from 'react'; +import agenticiqLogo from '@/assets/images/logo/AgenticIQLogo.svg'; +import topRightGlow from '@/assets/images/backgrounds/top-right-glow.svg'; +import bottomLeftWave1 from '@/assets/images/backgrounds/bottom-left-wave1 .svg'; +import bottomLeftWave2 from '@/assets/images/backgrounds/bottom-left-wave2.svg'; +import logoGlowBg from '@/assets/images/backgrounds/logo-glow-bg.svg'; +import { AuthFormCard, ForgotPasswordModal } from '@/components/auth'; + +/** + * LoginPage component + * @description Full-page login layout with responsive 2-column grid structure. + * Mobile-first design that stacks columns on smaller screens. + * @returns {JSX.Element} LoginPage element + */ +export function LoginPage(): JSX.Element { + const [showForgotPwd, setShowForgotPwd] = useState(false); + + const handleSignIn = (email: string, password: string): void => { + console.log('Sign in:', { email, password }); + }; + + const handleRegister = (data: { + firstName: string; + lastName: string; + email: string; + password: string; + }): void => { + console.log('Register:', data); + }; + + const handleGoogleAuth = (): void => { + console.log('Google auth clicked'); + }; + + const handleAzureAuth = (): void => { + console.log('Azure auth clicked'); + }; + + const handleForgotPasswordClick = (): void => { + setShowForgotPwd(true); + }; + + const handleForgotPasswordClose = (): void => { + setShowForgotPwd(false); + }; + + const handleForgotPasswordSubmit = (email: string): void => { + console.log('Password recovery requested for:', email); + }; + + const handleOtpVerify = (otp: string): void => { + console.log('OTP verification:', otp); + }; + + return ( +
+
+ {/* Decorative Background Elements */} + + + {/* AgenticIQ Logo */} +
+ AgenticIQ Logo +
+ + {/* Left Side Text Content */} + + + {/* Main Content Grid */} +
+ {/* Left Column - Branding/Marketing Content */} + + + {/* Right Column - Auth Form Card */} +
+ +
+
+
+ + {/* Forgot Password Modal */} + +
+ ); +} + +/** + * BackgroundDecorations component + * @description Renders decorative background images matching Figma design. + * @returns {JSX.Element} Background decoration elements + */ +function BackgroundDecorations(): JSX.Element { + return ( + + ); +} + +/** + * LeftColumnText component + * @description Left side text content (heading and description). + * @returns {JSX.Element} Text elements positioned as per Figma + */ +function LeftColumnText(): JSX.Element { + return ( + <> +

+ Engineering the Future with Intelligent Agents +

+

+ Deploy intelligent agents that automate complex workflows, enhance decision-making, and scale your operations. Built for enterprise reliability with seamless integration capabilities. +

+ + ); +} + +/** + * LeftColumn component + * @description Empty container for left side content. + * @returns {JSX.Element} Left column container + */ +function LeftColumn(): JSX.Element { + return ( +
+ {/* Text content is positioned absolutely on page level */} +
+ ); +} diff --git a/src/pages/sign-in.tsx b/src/pages/sign-in.tsx deleted file mode 100644 index 86eb94d..0000000 --- a/src/pages/sign-in.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Sign In Page - * @description Basic sign-in form for existing users. - */ - -import { Link } from '@tanstack/react-router'; - -export function SignInPage() { - return ( -
-
-
- AgenticIQ Logo -

Welcome back

-

Sign in to continue to your dashboard.

-
- -
-
- - -
-
- - -
- -
- -

- New here?{' '} - - Create an account - -

-
-
- ); -} - diff --git a/src/routes.tsx b/src/routes.tsx index a03eb0d..2bf20b3 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -8,8 +8,8 @@ import { RootLayout } from '@/components/layout'; import { AgentPage } from '@/pages/agent'; import { AgentCreatePage } from '@/pages/agent-create'; import { Dashboard } from '@/pages/dashboard'; -import { SignInPage } from '@/pages/sign-in'; import { SignUpPage } from '@/pages/sign-up'; +import { LoginPage } from '@/pages/login-page'; export const APP_PATHS = { signIn: '/', @@ -39,10 +39,10 @@ const appRoute = createRoute({ ), }); -const signInRoute = createRoute({ +const loginRoute = createRoute({ getParentRoute: () => publicRoute, - path: APP_PATHS.signIn, - component: SignInPage, + path: '/', + component: LoginPage, }); const signUpRoute = createRoute({ @@ -83,9 +83,8 @@ const notFoundRoute = createRoute({ }); const routeTree = rootRoute.addChildren([ - publicRoute.addChildren([signInRoute, signUpRoute]), - appRoute.addChildren([dashboardRoute, agentRoute, agentCreateRoute]), - notFoundRoute, + publicRoute.addChildren([loginRoute, signUpRoute]), + appRoute.addChildren([dashboardRoute,agentRoute,agentCreateRoute,notFoundRoute]), ]); export const router = createRouter({ diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..8d6a9c5 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,14 @@ +/// + +/** + * SVG Module Declaration + * @description Allows importing SVG files as modules in TypeScript. + * Supports both default export (URL string) and ReactComponent export. + */ +declare module '*.svg' { + import React = require('react'); + export const ReactComponent: React.FC>; + const src: string; + export default src; +} + diff --git a/tsconfig.app.json b/tsconfig.app.json index 9a5e0a3..1f88941 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "incremental": true, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2022", "useDefineForClassFields": true, @@ -24,9 +25,7 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, /* Path Aliases */ "baseUrl": ".", "paths": {