Complete UI overhaul with luxury design: 3D agent carousel, credits system, enhanced sidebar, profile features, and logo updates
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700;800;900&family=Montserrat:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700;800;900&family=Montserrat:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Pennyzo Web App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
1
src/assets/Dark-LineLogo-cropped.svg
Normal file
1
src/assets/Dark-LineLogo-cropped.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 3.2 MiB |
153
src/components/AgentPanel.tsx
Normal file
153
src/components/AgentPanel.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
export type Agent = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
color: string; // tailwind gradient colors
|
||||||
|
emoji: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AgentPanelProps {
|
||||||
|
agents: Agent[];
|
||||||
|
activeAgentId: string;
|
||||||
|
onChangeAgent: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AgentPanel: React.FC<AgentPanelProps> = ({ agents, activeAgentId, onChangeAgent }) => {
|
||||||
|
const active = useMemo(() => agents.find(a => a.id === activeAgentId) || agents[0], [agents, activeAgentId]);
|
||||||
|
|
||||||
|
// 8 diverse agents for the 3D carousel
|
||||||
|
const carouselAgents = useMemo(() => [
|
||||||
|
{ id: 'hr', name: 'HR Command', emoji: '👥', color: 'from-luxury-gold/30 to-brass/30' },
|
||||||
|
{ id: 'finance', name: 'Finance Sage', emoji: '💰', color: 'from-bronze/30 to-luxury-gold/30' },
|
||||||
|
{ id: 'project', name: 'Project Sensei', emoji: '📊', color: 'from-brass/30 to-bronze/30' },
|
||||||
|
{ id: 'marketing', name: 'Marketing Genius', emoji: '🎯', color: 'from-luxury-gold/30 to-cream/30' },
|
||||||
|
{ id: 'analytics', name: 'Data Wizard', emoji: '📈', color: 'from-bronze/30 to-brass/30' },
|
||||||
|
{ id: 'security', name: 'Security Guard', emoji: '🛡️', color: 'from-deep-forest/40 to-luxury-gold/30' },
|
||||||
|
{ id: 'support', name: 'Support Hero', emoji: '🎧', color: 'from-brass/30 to-rich-brown/30' },
|
||||||
|
{ id: 'research', name: 'Research Bot', emoji: '🔍', color: 'from-luxury-gold/30 to-bronze/30' }
|
||||||
|
], []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full border border-luxury-gold/25 rounded-2xl p-4 md:p-6 bg-deep-forest/50 backdrop-blur-xl overflow-hidden flex flex-col shadow-luxury-inner">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h3 className="font-playfair text-xl text-cream">Personal Assistant</h3>
|
||||||
|
<div className="text-cream/80 text-sm font-montserrat">
|
||||||
|
{active.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative rounded-xl bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 border border-luxury-gold/25 overflow-hidden flex-1">
|
||||||
|
<div className={`absolute inset-0 opacity-40 bg-gradient-to-br ${active.color}`} />
|
||||||
|
|
||||||
|
{/* Creative Orbital Agent Display */}
|
||||||
|
<div className="absolute inset-0 grid place-items-center">
|
||||||
|
{/* Central Hub */}
|
||||||
|
<div className="relative">
|
||||||
|
{/* Outer orbital ring */}
|
||||||
|
<div className="w-72 h-72 rounded-full border border-luxury-gold/20 animate-spin-ultra-slow relative">
|
||||||
|
{/* Orbital path indicator */}
|
||||||
|
<div className="absolute inset-4 rounded-full border border-luxury-gold/10"></div>
|
||||||
|
<div className="absolute inset-8 rounded-full border border-luxury-gold/5"></div>
|
||||||
|
|
||||||
|
{/* Rotating agents on the orbit */}
|
||||||
|
{carouselAgents.map((agent, index) => {
|
||||||
|
const angle = (index * 360) / carouselAgents.length;
|
||||||
|
const isActive = agent.id === activeAgentId;
|
||||||
|
const radian = (angle * Math.PI) / 180;
|
||||||
|
const radius = 130;
|
||||||
|
const x = Math.cos(radian) * radius;
|
||||||
|
const y = Math.sin(radian) * radius;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={agent.id} className="contents">
|
||||||
|
<button
|
||||||
|
onClick={() => onChangeAgent(agent.id)}
|
||||||
|
className={`absolute w-14 h-14 rounded-2xl transition-all duration-500 hover:scale-125 focus:outline-none group ${
|
||||||
|
isActive
|
||||||
|
? 'bg-gradient-to-br from-luxury-gold/60 to-brass/60 border-2 border-luxury-gold shadow-[0_0_25px_rgba(197,157,89,0.8)] scale-110 z-10'
|
||||||
|
: 'bg-deep-forest/70 border border-luxury-gold/30 hover:border-luxury-gold/60 hover:bg-deep-forest/80'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
left: '50%',
|
||||||
|
top: '50%',
|
||||||
|
transform: `translate(${x - 28}px, ${y - 28}px)`,
|
||||||
|
}}
|
||||||
|
title={agent.name}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center justify-center h-full">
|
||||||
|
<span className={`text-lg transition-all duration-300 ${isActive ? 'text-deep-forest' : 'text-cream'}`}>
|
||||||
|
{agent.emoji}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Connection line to center for active agent */}
|
||||||
|
{isActive && (
|
||||||
|
<div
|
||||||
|
className="absolute w-0.5 bg-gradient-to-r from-luxury-gold to-transparent opacity-60"
|
||||||
|
style={{
|
||||||
|
height: `${radius - 20}px`,
|
||||||
|
left: '50%',
|
||||||
|
top: '50%',
|
||||||
|
transformOrigin: 'top center',
|
||||||
|
transform: `translateX(-50%) rotate(${180 + angle}deg)`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Tooltip positioned outside rotating container */}
|
||||||
|
<div
|
||||||
|
className="absolute opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none z-50"
|
||||||
|
style={{
|
||||||
|
left: '50%',
|
||||||
|
top: '50%',
|
||||||
|
transform: `translate(${x - 28}px, ${y + 40}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="bg-deep-forest/95 text-cream text-sm px-3 py-2 rounded-xl border border-luxury-gold/40 whitespace-nowrap font-montserrat font-medium shadow-[0_4px_12px_rgba(0,0,0,0.4)] -translate-x-1/2">
|
||||||
|
{agent.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Central Control Hub */}
|
||||||
|
<div className="absolute top-1/2 left-1/2 w-20 h-20 -translate-x-1/2 -translate-y-1/2 z-20">
|
||||||
|
<div className="w-full h-full rounded-full bg-gradient-to-br from-deep-forest/80 to-rich-brown/60 border-2 border-luxury-gold/40 shadow-[0_0_20px_rgba(197,157,89,0.3)] flex items-center justify-center group cursor-pointer">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-2xl mb-1">🤖</div>
|
||||||
|
<div className="text-xs text-cream/80 font-montserrat font-medium">HUB</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pulsing energy rings */}
|
||||||
|
<div className="absolute inset-0 rounded-full border border-luxury-gold/20 animate-ping"></div>
|
||||||
|
<div className="absolute inset-2 rounded-full border border-luxury-gold/10 animate-ping animation-delay-1000"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Floating particles for ambiance */}
|
||||||
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
|
{[...Array(6)].map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="absolute w-1 h-1 bg-luxury-gold rounded-full animate-ping"
|
||||||
|
style={{
|
||||||
|
left: `${20 + Math.random() * 60}%`,
|
||||||
|
top: `${20 + Math.random() * 60}%`,
|
||||||
|
animationDelay: `${Math.random() * 2}s`,
|
||||||
|
animationDuration: `${1.5 + Math.random() * 1}s`
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AgentPanel;
|
||||||
67
src/components/ChatDock.tsx
Normal file
67
src/components/ChatDock.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
|
||||||
|
export type ChatMessage = {
|
||||||
|
id: string;
|
||||||
|
role: 'user' | 'assistant' | 'system';
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ChatDockProps {
|
||||||
|
messages: ChatMessage[];
|
||||||
|
onSend: (text: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
className?: string;
|
||||||
|
leftOffsetClass?: string; // e.g., 'left-20 md:left-64'
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChatDock: React.FC<ChatDockProps> = ({ messages, onSend, placeholder = 'Ask anything…', className = '', leftOffsetClass = 'left-0' }) => {
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
const listRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (listRef.current) listRef.current.scrollTop = listRef.current.scrollHeight;
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const handleSend = () => {
|
||||||
|
const t = text.trim();
|
||||||
|
if (!t) return;
|
||||||
|
onSend(t);
|
||||||
|
setText('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`border-t border-luxury-gold/25 bg-deep-forest/80 backdrop-blur-2xl fixed bottom-0 right-0 ${leftOffsetClass} z-40 h-[140px] md:h-[160px] ${className}`}>
|
||||||
|
<div className="w-full h-full p-3 md:p-4 flex flex-col">
|
||||||
|
<div ref={listRef} className="flex-1 min-h-0 overflow-y-auto pr-1 space-y-3 custom-thin-scroll">
|
||||||
|
{messages.map((m) => (
|
||||||
|
<div key={m.id} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||||
|
<div className={`max-w-[80%] px-3 py-2 rounded-2xl border text-sm font-montserrat ${
|
||||||
|
m.role === 'user'
|
||||||
|
? 'bg-luxury-gold text-deep-forest border-luxury-gold/50'
|
||||||
|
: 'bg-deep-forest/60 text-cream border-luxury-gold/30'
|
||||||
|
}`}>
|
||||||
|
{m.content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1 flex items-center gap-2">
|
||||||
|
<textarea
|
||||||
|
value={text}
|
||||||
|
onChange={(e) => setText(e.target.value)}
|
||||||
|
onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }}
|
||||||
|
rows={2}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className="flex-1 resize-none px-4 py-3 h-12 bg-deep-forest/70 border border-luxury-gold/40 rounded-xl text-cream focus:outline-none focus:ring-2 focus:ring-luxury-gold/40"
|
||||||
|
/>
|
||||||
|
<button onClick={handleSend} className="px-6 py-3 h-12 rounded-xl bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-semibold shadow-luxury flex items-center justify-center">
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatDock;
|
||||||
196
src/components/CreditsModal.tsx
Normal file
196
src/components/CreditsModal.tsx
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface CreditsScreenProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreditsScreen: React.FC<CreditsScreenProps> = ({ isOpen, onClose }) => {
|
||||||
|
const [selectedYear, setSelectedYear] = useState('2025');
|
||||||
|
const [selectedMonth, setSelectedMonth] = useState('09');
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
// Mock data - will be replaced with backend integration
|
||||||
|
const creditHistory = [
|
||||||
|
{ id: 1, date: '2025-09-27', type: 'Usage', description: 'HR Command - Employee Analysis', amount: -5, balance: 123 },
|
||||||
|
{ id: 2, date: '2025-09-26', type: 'Purchase', description: 'Credit Package - Premium', amount: +100, balance: 128 },
|
||||||
|
{ id: 3, date: '2025-09-25', type: 'Usage', description: 'Finance Sage - Budget Report', amount: -8, balance: 28 },
|
||||||
|
{ id: 4, date: '2025-09-24', type: 'Usage', description: 'Project Sensei - Task Analysis', amount: -3, balance: 36 },
|
||||||
|
{ id: 5, date: '2025-09-23', type: 'Bonus', description: 'Daily Login Reward', amount: +2, balance: 39 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentBalance = 128;
|
||||||
|
const monthlyUsage = 16;
|
||||||
|
const monthlyPurchases = 100;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-16 md:top-20 left-20 md:left-64 right-0 bottom-0 z-40 bg-gradient-to-br from-deep-forest/95 to-rich-brown/90 backdrop-blur-sm">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="h-16 px-6 border-b border-luxury-gold/25 bg-deep-forest/90 backdrop-blur-xl flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="w-10 h-10 rounded-xl bg-deep-forest/60 border border-luxury-gold/30 hover:bg-luxury-gold/10 hover:border-luxury-gold/50 transition-all flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" className="text-cream fill-current">
|
||||||
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<h1 className="font-playfair text-xl md:text-2xl font-bold text-cream">Credits & Usage History</h1>
|
||||||
|
<p className="text-cream/60 text-sm">Manage your account balance and track usage patterns</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="bg-gradient-to-r from-luxury-gold/20 to-brass/20 border border-luxury-gold/30 px-4 py-2 rounded-xl">
|
||||||
|
<span className="text-luxury-gold font-bold text-lg">{currentBalance} Credits</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="flex-1 p-4 md:p-6 overflow-auto h-[calc(100vh-12rem)] md:h-[calc(100vh-14rem)]">
|
||||||
|
{/* Stats Cards */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
|
<div className="bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 border border-luxury-gold/25 rounded-xl p-4">
|
||||||
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-green-500/30 to-emerald-500/30 flex items-center justify-center">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" className="text-green-400 fill-current">
|
||||||
|
<path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M11,16.5L6.5,12L7.91,10.59L11,13.67L16.59,8.09L18,9.5L11,16.5Z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-playfair text-lg font-bold text-cream">Current Balance</h3>
|
||||||
|
<p className="text-cream/60 text-xs">Available credits</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-2xl font-bold text-green-400">{currentBalance}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 border border-luxury-gold/25 rounded-xl p-4">
|
||||||
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-red-500/30 to-pink-500/30 flex items-center justify-center">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" className="text-red-400 fill-current">
|
||||||
|
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-playfair text-lg font-bold text-cream">Monthly Usage</h3>
|
||||||
|
<p className="text-cream/60 text-xs">Credits spent this month</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-2xl font-bold text-red-400">{monthlyUsage}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 border border-luxury-gold/25 rounded-xl p-4">
|
||||||
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-luxury-gold/30 to-brass/30 flex items-center justify-center">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" className="text-luxury-gold fill-current">
|
||||||
|
<path d="M7,15H9C9,16.08 10.37,17 12,17C13.63,17 15,16.08 15,15C15,13.9 13.96,13.5 11.76,12.97C9.64,12.44 7,11.78 7,9C7,7.21 8.47,5.69 10.5,5.18V3H13.5V5.18C15.53,5.69 17,7.21 17,9H15C15,7.92 13.63,7 12,7C10.37,7 9,7.92 9,9C9,10.1 10.04,10.5 12.24,11.03C14.36,11.56 17,12.22 17,15C17,16.79 15.53,18.31 13.5,18.82V21H10.5V18.82C8.47,18.31 7,16.79 7,15Z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-playfair text-lg font-bold text-cream">Monthly Purchases</h3>
|
||||||
|
<p className="text-cream/60 text-xs">Credits purchased this month</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-2xl font-bold text-luxury-gold">{monthlyPurchases}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filters */}
|
||||||
|
<div className="bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 border border-luxury-gold/25 rounded-xl p-4 mb-4">
|
||||||
|
<div className="flex flex-wrap items-end gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-cream/80 text-sm mb-1">Filter by Year</label>
|
||||||
|
<select
|
||||||
|
value={selectedYear}
|
||||||
|
onChange={(e) => setSelectedYear(e.target.value)}
|
||||||
|
className="px-3 py-2 bg-deep-forest/70 border border-luxury-gold/40 rounded-lg text-cream focus:outline-none focus:ring-2 focus:ring-luxury-gold/40 text-sm"
|
||||||
|
>
|
||||||
|
<option value="2025">2025</option>
|
||||||
|
<option value="2024">2024</option>
|
||||||
|
<option value="2023">2023</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-cream/80 text-sm mb-1">Filter by Month</label>
|
||||||
|
<select
|
||||||
|
value={selectedMonth}
|
||||||
|
onChange={(e) => setSelectedMonth(e.target.value)}
|
||||||
|
className="px-3 py-2 bg-deep-forest/70 border border-luxury-gold/40 rounded-lg text-cream focus:outline-none focus:ring-2 focus:ring-luxury-gold/40 text-sm"
|
||||||
|
>
|
||||||
|
<option value="01">January</option>
|
||||||
|
<option value="02">February</option>
|
||||||
|
<option value="03">March</option>
|
||||||
|
<option value="04">April</option>
|
||||||
|
<option value="05">May</option>
|
||||||
|
<option value="06">June</option>
|
||||||
|
<option value="07">July</option>
|
||||||
|
<option value="08">August</option>
|
||||||
|
<option value="09">September</option>
|
||||||
|
<option value="10">October</option>
|
||||||
|
<option value="11">November</option>
|
||||||
|
<option value="12">December</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button className="px-4 py-2 bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-semibold rounded-lg hover:scale-105 transition-transform text-sm">
|
||||||
|
Show Details
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* History Table */}
|
||||||
|
<div className="bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 border border-luxury-gold/25 rounded-xl overflow-hidden">
|
||||||
|
<div className="px-4 py-3 border-b border-luxury-gold/20 bg-gradient-to-r from-luxury-gold/10 to-transparent">
|
||||||
|
<h3 className="font-playfair text-lg font-bold text-cream">Transaction History</h3>
|
||||||
|
<p className="text-cream/60 text-sm">Detailed record of all credit transactions</p>
|
||||||
|
</div>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead className="bg-deep-forest/60">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left text-cream/80 font-montserrat font-semibold text-sm">Date</th>
|
||||||
|
<th className="px-4 py-3 text-left text-cream/80 font-montserrat font-semibold text-sm">Type</th>
|
||||||
|
<th className="px-4 py-3 text-left text-cream/80 font-montserrat font-semibold text-sm">Description</th>
|
||||||
|
<th className="px-4 py-3 text-right text-cream/80 font-montserrat font-semibold text-sm">Amount</th>
|
||||||
|
<th className="px-4 py-3 text-right text-cream/80 font-montserrat font-semibold text-sm">Balance</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-luxury-gold/10">
|
||||||
|
{creditHistory.map((transaction) => (
|
||||||
|
<tr key={transaction.id} className="hover:bg-luxury-gold/5 transition-colors">
|
||||||
|
<td className="px-4 py-3 text-cream/90 font-montserrat text-sm">{transaction.date}</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
|
||||||
|
transaction.type === 'Usage' ? 'bg-red-500/20 text-red-400' :
|
||||||
|
transaction.type === 'Purchase' ? 'bg-green-500/20 text-green-400' :
|
||||||
|
'bg-blue-500/20 text-blue-400'
|
||||||
|
}`}>
|
||||||
|
{transaction.type}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-cream/90 font-montserrat text-sm">{transaction.description}</td>
|
||||||
|
<td className={`px-4 py-3 text-right font-montserrat font-semibold text-sm ${
|
||||||
|
transaction.amount > 0 ? 'text-green-400' : 'text-red-400'
|
||||||
|
}`}>
|
||||||
|
{transaction.amount > 0 ? '+' : ''}{transaction.amount}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-right text-cream/90 font-montserrat font-semibold text-sm">
|
||||||
|
{transaction.balance}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreditsScreen;
|
||||||
84
src/components/DisplayPanel.tsx
Normal file
84
src/components/DisplayPanel.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface DisplayPanelProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DisplayPanel: React.FC<DisplayPanelProps> = ({ children }) => {
|
||||||
|
const [activeTab, setActiveTab] = useState<'instructions' | 'playground'>('instructions');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full border border-luxury-gold/25 rounded-2xl p-4 md:p-6 bg-deep-forest/50 backdrop-blur-xl overflow-hidden flex flex-col shadow-luxury-inner">
|
||||||
|
{/* Tab header */}
|
||||||
|
<div className="mb-3 flex items-center justify-between">
|
||||||
|
<div role="tablist" aria-label="Display panel tabs" className="inline-flex items-center rounded-xl bg-deep-forest/60 border border-luxury-gold/30 p-1 shadow-inner">
|
||||||
|
{[
|
||||||
|
{ id: 'instructions', label: 'Instructions' },
|
||||||
|
{ id: 'playground', label: 'Playground' },
|
||||||
|
].map((t) => {
|
||||||
|
const active = activeTab === (t.id as 'instructions' | 'playground');
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={t.id}
|
||||||
|
role="tab"
|
||||||
|
aria-selected={active}
|
||||||
|
onClick={() => setActiveTab(t.id as 'instructions' | 'playground')}
|
||||||
|
className={`px-4 py-1.5 md:px-5 md:py-2 rounded-lg text-sm font-montserrat transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-luxury-gold/40 ${
|
||||||
|
active
|
||||||
|
? 'bg-gradient-to-r from-luxury-gold/25 to-brass/25 text-cream border border-luxury-gold/40 shadow-[0_2px_14px_rgba(197,157,89,0.25)]'
|
||||||
|
: 'text-cream/80 hover:text-cream hover:bg-luxury-gold/10 border border-transparent'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Panel body */}
|
||||||
|
<div className="relative rounded-xl bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 border border-luxury-gold/25 overflow-hidden flex-1">
|
||||||
|
{/* soft decorative gradient */}
|
||||||
|
<div className="pointer-events-none absolute inset-0 opacity-30 bg-gradient-to-tr from-luxury-gold/10 via-transparent to-bronze/10" />
|
||||||
|
|
||||||
|
<div className="absolute inset-0 p-4 md:p-5 overflow-auto custom-thin-scroll">
|
||||||
|
{activeTab === 'instructions' ? (
|
||||||
|
<div className="text-cream/85 font-montserrat text-sm leading-relaxed space-y-3">
|
||||||
|
<p className="text-cream/90">Welcome. This panel guides you on how to drive agents and view results.</p>
|
||||||
|
<ol className="list-decimal list-inside space-y-2">
|
||||||
|
<li>Pick an agent from the left panel that matches your task.</li>
|
||||||
|
<li>Use the chat dock to issue a request. Press Enter to send.</li>
|
||||||
|
<li>Watch this panel for summaries, charts, or generated assets.</li>
|
||||||
|
<li>Refine with follow-ups. Use concise prompts for best results.</li>
|
||||||
|
</ol>
|
||||||
|
<div className="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
<div className="rounded-lg border border-luxury-gold/30 bg-deep-forest/60 p-3">
|
||||||
|
<div className="text-cream/70 text-xs uppercase tracking-wide mb-1">Tip</div>
|
||||||
|
<div className="text-cream/90">Start prompts with a verb: “Summarize…”, “Compare…”, “Draft…”.</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-lg border border-luxury-gold/30 bg-deep-forest/60 p-3">
|
||||||
|
<div className="text-cream/70 text-xs uppercase tracking-wide mb-1">Format</div>
|
||||||
|
<div className="text-cream/90">Ask for tables, bullet points, or JSON if you need structured output.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-cream/85 font-montserrat text-sm">
|
||||||
|
{children || (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="text-cream/80">This is your playground canvas. Render previews, charts, or content here.</div>
|
||||||
|
<div className="rounded-xl border border-luxury-gold/30 bg-deep-forest/60 p-4">
|
||||||
|
<div className="text-cream/70 text-xs uppercase tracking-wide mb-2">Empty Canvas</div>
|
||||||
|
<div className="text-cream/90">Hook this area to your agent outputs to visualize results.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DisplayPanel;
|
||||||
0
src/components/ProtectedRoute.tsx
Normal file
0
src/components/ProtectedRoute.tsx
Normal file
107
src/components/SidebarNav.tsx
Normal file
107
src/components/SidebarNav.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Item = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SidebarNavProps {
|
||||||
|
items: Item[];
|
||||||
|
activeId: string;
|
||||||
|
onSelect: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarNav: React.FC<SidebarNavProps> = ({ items, activeId, onSelect }) => {
|
||||||
|
return (
|
||||||
|
<aside className="w-20 md:w-64 border-r border-luxury-gold/25 bg-gradient-to-b from-deep-forest/90 via-deep-forest/80 to-rich-brown/70 backdrop-blur-xl sticky top-16 md:top-20 h-[calc(100vh-4rem)] md:h-[calc(100vh-5rem)] overflow-hidden">
|
||||||
|
{/* Decorative background elements */}
|
||||||
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
|
<div className="absolute -top-20 -left-10 w-40 h-40 bg-luxury-gold/5 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-20 -right-10 w-32 h-32 bg-bronze/5 rounded-full blur-2xl"></div>
|
||||||
|
<div className="absolute top-1/2 left-0 w-0.5 h-32 bg-gradient-to-b from-transparent via-luxury-gold/20 to-transparent"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Premium border accent */}
|
||||||
|
<div className="absolute top-0 left-0 w-full h-0.5 bg-gradient-to-r from-transparent via-luxury-gold/40 to-transparent"></div>
|
||||||
|
|
||||||
|
<nav className="relative p-3 md:p-6 space-y-2">
|
||||||
|
{/* Navigation title */}
|
||||||
|
<div className="hidden md:block mb-6">
|
||||||
|
<h3 className="text-luxury-gold/80 text-xs font-montserrat font-semibold uppercase tracking-wider mb-2">Navigation</h3>
|
||||||
|
<div className="w-8 h-0.5 bg-gradient-to-r from-luxury-gold to-transparent rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{items.map((it, index) => {
|
||||||
|
const isActive = activeId === it.id;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={it.id}
|
||||||
|
onClick={() => onSelect(it.id)}
|
||||||
|
className={`group relative w-full flex items-center gap-3 md:gap-4 px-4 py-4 rounded-2xl transition-all duration-300 transform hover:scale-[1.02] ${
|
||||||
|
isActive
|
||||||
|
? 'bg-gradient-to-r from-luxury-gold/25 to-brass/20 border border-luxury-gold/50 shadow-[0_8px_32px_rgba(197,157,89,0.15)] text-cream'
|
||||||
|
: 'border border-transparent hover:border-luxury-gold/30 hover:bg-gradient-to-r hover:from-luxury-gold/10 hover:to-transparent text-cream/80 hover:text-cream'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
animationDelay: `${index * 100}ms`
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Icon container with enhanced styling */}
|
||||||
|
<div className={`relative w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300 ${
|
||||||
|
isActive
|
||||||
|
? 'bg-gradient-to-br from-luxury-gold/30 to-brass/30 shadow-lg'
|
||||||
|
: 'bg-deep-forest/50 group-hover:bg-luxury-gold/20'
|
||||||
|
}`}>
|
||||||
|
<span className={`text-lg transition-all duration-300 ${
|
||||||
|
isActive ? 'scale-110' : 'group-hover:scale-105'
|
||||||
|
}`}>
|
||||||
|
{it.icon}
|
||||||
|
</span>
|
||||||
|
{/* Active indicator dot */}
|
||||||
|
{isActive && (
|
||||||
|
<div className="absolute -right-1 -top-1 w-3 h-3 bg-luxury-gold rounded-full shadow-[0_0_8px_rgba(197,157,89,0.6)] animate-pulse"></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Label with enhanced typography */}
|
||||||
|
<div className="hidden md:block flex-1">
|
||||||
|
<span className={`text-sm font-montserrat font-medium transition-all duration-300 ${
|
||||||
|
isActive ? 'font-semibold' : 'group-hover:font-medium'
|
||||||
|
}`}>
|
||||||
|
{it.label}
|
||||||
|
</span>
|
||||||
|
{/* Subtle underline for active item */}
|
||||||
|
{isActive && (
|
||||||
|
<div className="w-full h-0.5 bg-gradient-to-r from-luxury-gold/60 to-transparent rounded-full mt-1"></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Hover effect overlay */}
|
||||||
|
<div className="absolute inset-0 rounded-2xl bg-gradient-to-r from-luxury-gold/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"></div>
|
||||||
|
|
||||||
|
{/* Active selection indicator */}
|
||||||
|
{isActive && (
|
||||||
|
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 bg-gradient-to-b from-luxury-gold to-brass rounded-r-full shadow-[0_0_8px_rgba(197,157,89,0.4)]"></div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Bottom decorative element */}
|
||||||
|
<div className="hidden md:block mt-8 pt-6">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-luxury-gold/10 to-bronze/5 border border-luxury-gold/20 flex items-center justify-center">
|
||||||
|
<div className="w-6 h-6 rounded-lg bg-luxury-gold/20 animate-pulse"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Bottom gradient fade */}
|
||||||
|
<div className="absolute bottom-0 left-0 w-full h-16 bg-gradient-to-t from-deep-forest/50 to-transparent pointer-events-none"></div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SidebarNav;
|
||||||
308
src/components/TopBar.tsx
Normal file
308
src/components/TopBar.tsx
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
|
||||||
|
type Notification = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
time: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TopBarProps {
|
||||||
|
companies: string[];
|
||||||
|
selectedCompany: string;
|
||||||
|
onCompanyChange: (value: string) => void;
|
||||||
|
coins?: number;
|
||||||
|
notifications?: Notification[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TopBar: React.FC<TopBarProps> = ({
|
||||||
|
companies,
|
||||||
|
selectedCompany,
|
||||||
|
onCompanyChange,
|
||||||
|
coins = 0,
|
||||||
|
notifications = []
|
||||||
|
}) => {
|
||||||
|
const [showNotif, setShowNotif] = useState(false);
|
||||||
|
const [showProfile, setShowProfile] = useState(false);
|
||||||
|
const [profileImage, setProfileImage] = useState<string | null>(null);
|
||||||
|
const notifRef = useRef<HTMLDivElement>(null);
|
||||||
|
const profileRef = useRef<HTMLDivElement>(null);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleProfileImageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const result = e.target?.result as string;
|
||||||
|
setProfileImage(result);
|
||||||
|
// Save to localStorage for persistence
|
||||||
|
localStorage.setItem('profileImage', result);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load profile image from localStorage on component mount
|
||||||
|
React.useEffect(() => {
|
||||||
|
const savedImage = localStorage.getItem('profileImage');
|
||||||
|
if (savedImage) {
|
||||||
|
setProfileImage(savedImage);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = (e: MouseEvent) => {
|
||||||
|
if (notifRef.current && !notifRef.current.contains(e.target as Node)) setShowNotif(false);
|
||||||
|
if (profileRef.current && !profileRef.current.contains(e.target as Node)) setShowProfile(false);
|
||||||
|
};
|
||||||
|
document.addEventListener('click', handler);
|
||||||
|
return () => document.removeEventListener('click', handler);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-16 md:h-20 px-4 md:px-6 border-b border-luxury-gold/20 bg-deep-forest/80 backdrop-blur-xl flex items-center justify-between sticky top-0 z-40">
|
||||||
|
{/* Left: Logo */}
|
||||||
|
<a href="/home" className="flex items-center space-x-3 group">
|
||||||
|
<div className="h-10 md:h-12 flex items-center">
|
||||||
|
<img
|
||||||
|
src="/src/assets/Dark-LineLogo-cropped.svg"
|
||||||
|
alt="Pennyzo Logo"
|
||||||
|
className="h-full w-auto object-contain group-hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Center: Company dropdown */}
|
||||||
|
<div className="flex-1 flex justify-center">
|
||||||
|
<select
|
||||||
|
value={selectedCompany}
|
||||||
|
onChange={(e) => onCompanyChange(e.target.value)}
|
||||||
|
className="w-full max-w-md md:max-w-xl px-4 py-2 md:py-3 rounded-xl bg-deep-forest/70 border border-luxury-gold/40 text-cream focus:outline-none focus:ring-2 focus:ring-luxury-gold/40 shadow-luxury"
|
||||||
|
>
|
||||||
|
{companies.map((c) => (
|
||||||
|
<option key={c} value={c} className="bg-deep-forest">{c}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: Coins, Notifications, Profile */}
|
||||||
|
<div className="flex items-center gap-2 md:gap-4">
|
||||||
|
{/* Coins */}
|
||||||
|
<div className="flex items-center gap-2 bg-deep-forest/60 border border-luxury-gold/30 px-3 py-1.5 rounded-full">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" className="text-luxury-gold fill-current">
|
||||||
|
<circle cx="12" cy="12" r="10"/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-cream text-sm font-montserrat">{coins}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Notifications */}
|
||||||
|
<div className="relative" ref={notifRef}>
|
||||||
|
<button
|
||||||
|
aria-label="Notifications"
|
||||||
|
onClick={() => setShowNotif((s) => !s)}
|
||||||
|
className="relative w-11 h-11 rounded-full bg-gradient-to-br from-deep-forest/80 to-rich-brown/60 border border-luxury-gold/30 hover:border-luxury-gold/60 hover:bg-gradient-to-br hover:from-luxury-gold/10 hover:to-brass/10 transition-all duration-300 shadow-[0_4px_12px_rgba(0,0,0,0.15)] hover:shadow-[0_6px_20px_rgba(197,157,89,0.2)] group"
|
||||||
|
>
|
||||||
|
{/* Bell icon with luxury styling */}
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" className="text-luxury-gold/80 group-hover:text-luxury-gold transition-colors duration-300 fill-current">
|
||||||
|
<path d="M12,2A2,2 0 0,1 14,4V5.5A6.5,6.5 0 0,1 20.5,12V16.5L22,18V19H2V18L3.5,16.5V12A6.5,6.5 0 0,1 9.5,5.5V4A2,2 0 0,1 12,2M12,22A2,2 0 0,0 14,20H10A2,2 0 0,0 12,22Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Notification badge */}
|
||||||
|
{notifications.length > 0 && (
|
||||||
|
<div className="absolute -top-1.5 -right-1.5 min-w-[20px] h-5 bg-gradient-to-r from-red-500 to-red-600 rounded-full flex items-center justify-center border-2 border-deep-forest shadow-[0_0_12px_rgba(239,68,68,0.4)]">
|
||||||
|
<span className="text-white text-xs font-bold px-1">
|
||||||
|
{notifications.length > 99 ? '99+' : notifications.length}
|
||||||
|
</span>
|
||||||
|
<div className="absolute inset-0 rounded-full bg-red-500 animate-ping opacity-30"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Subtle inner glow effect */}
|
||||||
|
<div className="absolute inset-0.5 rounded-full bg-gradient-to-br from-luxury-gold/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showNotif && (
|
||||||
|
<div className="absolute right-0 mt-3 w-80 bg-gradient-to-br from-deep-forest/95 to-rich-brown/90 backdrop-blur-xl border border-luxury-gold/30 rounded-2xl shadow-[0_20px_40px_rgba(0,0,0,0.4)] animate-fadeInUp overflow-hidden">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="px-4 py-3 border-b border-luxury-gold/20 bg-gradient-to-r from-luxury-gold/10 to-transparent">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-cream font-montserrat font-semibold">Notifications</h3>
|
||||||
|
{notifications.length > 0 && (
|
||||||
|
<span className="text-luxury-gold/80 text-sm font-medium">{notifications.length} new</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="max-h-80 overflow-auto custom-thin-scroll">
|
||||||
|
{notifications.length === 0 ? (
|
||||||
|
<div className="px-4 py-8 text-center">
|
||||||
|
<div className="w-16 h-16 mx-auto mb-3 rounded-full bg-gradient-to-br from-luxury-gold/20 to-brass/20 flex items-center justify-center">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" className="text-luxury-gold/60 fill-current">
|
||||||
|
<path d="M12,2A2,2 0 0,1 14,4V5.5A6.5,6.5 0 0,1 20.5,12V16.5L22,18V19H2V18L3.5,16.5V12A6.5,6.5 0 0,1 9.5,5.5V4A2,2 0 0,1 12,2Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p className="text-cream/60 font-montserrat">No new notifications</p>
|
||||||
|
<p className="text-cream/40 text-sm mt-1">We'll notify you when something arrives</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="divide-y divide-luxury-gold/10">
|
||||||
|
{notifications.map((n, index) => (
|
||||||
|
<div key={n.id} className="px-4 py-4 hover:bg-gradient-to-r hover:from-luxury-gold/5 hover:to-transparent transition-all duration-200 group cursor-pointer">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
{/* Notification icon */}
|
||||||
|
<div className="w-2 h-2 mt-2 rounded-full bg-gradient-to-r from-luxury-gold to-brass shadow-[0_0_8px_rgba(197,157,89,0.6)] flex-shrink-0"></div>
|
||||||
|
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-cream text-sm font-medium group-hover:text-luxury-gold/90 transition-colors">
|
||||||
|
{n.title}
|
||||||
|
</p>
|
||||||
|
<p className="text-cream/50 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" className="fill-current">
|
||||||
|
<path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.7L16.2,16.2Z"/>
|
||||||
|
</svg>
|
||||||
|
{n.time}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* New indicator for first few notifications */}
|
||||||
|
{index < 2 && (
|
||||||
|
<div className="px-2 py-1 rounded-full bg-gradient-to-r from-luxury-gold/20 to-brass/20 border border-luxury-gold/30">
|
||||||
|
<span className="text-luxury-gold text-xs font-medium">New</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
{notifications.length > 0 && (
|
||||||
|
<div className="px-4 py-3 border-t border-luxury-gold/20 bg-gradient-to-r from-transparent to-luxury-gold/5">
|
||||||
|
<button className="w-full text-luxury-gold/80 hover:text-luxury-gold text-sm font-medium transition-colors">
|
||||||
|
View all notifications
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Profile */}
|
||||||
|
<div className="relative" ref={profileRef}>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowProfile((s) => !s)}
|
||||||
|
className="flex items-center gap-2 bg-deep-forest/60 border border-luxury-gold/30 pl-2 pr-3 py-2 rounded-full hover:bg-luxury-gold/10 transition-all duration-300 hover:border-luxury-gold/50 hover:shadow-[0_4px_12px_rgba(197,157,89,0.15)]"
|
||||||
|
>
|
||||||
|
{/* Enhanced Profile Picture Circle */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-9 h-9 rounded-full overflow-hidden border-2 border-luxury-gold/40 shadow-[0_0_12px_rgba(197,157,89,0.3)] hover:border-luxury-gold transition-all duration-300">
|
||||||
|
{profileImage ? (
|
||||||
|
<img
|
||||||
|
src={profileImage}
|
||||||
|
alt="Profile"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-full bg-gradient-to-br from-luxury-gold to-brass flex items-center justify-center text-deep-forest font-bold text-sm">
|
||||||
|
U
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* Online status indicator */}
|
||||||
|
<div className="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-green-400 rounded-full border-2 border-deep-forest shadow-sm"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" className="text-cream/80 fill-current transition-transform duration-200 group-hover:rotate-180">
|
||||||
|
<path d="M7 10l5 5 5-5z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showProfile && (
|
||||||
|
<div className="absolute right-0 mt-3 w-64 bg-deep-forest/95 backdrop-blur-xl border border-luxury-gold/30 rounded-2xl shadow-[0_20px_40px_rgba(0,0,0,0.4)] p-3 animate-fadeInUp">
|
||||||
|
{/* Profile Header */}
|
||||||
|
<div className="flex items-center gap-3 p-3 border-b border-luxury-gold/20 mb-2">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-12 h-12 rounded-full overflow-hidden border-2 border-luxury-gold/40">
|
||||||
|
{profileImage ? (
|
||||||
|
<img src={profileImage} alt="Profile" className="w-full h-full object-cover" />
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-full bg-gradient-to-br from-luxury-gold to-brass flex items-center justify-center text-deep-forest font-bold">
|
||||||
|
U
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
className="absolute -bottom-1 -right-1 w-6 h-6 bg-luxury-gold rounded-full flex items-center justify-center text-deep-forest hover:bg-brass transition-colors shadow-lg"
|
||||||
|
title="Change profile picture"
|
||||||
|
>
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-cream font-montserrat font-medium">You</div>
|
||||||
|
<div className="text-cream/60 text-sm">Premium User</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Menu Items */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<a href="#profile" className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-cream/90 hover:bg-luxury-gold/10 hover:text-cream transition-all group">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" className="text-luxury-gold/70 group-hover:text-luxury-gold fill-current">
|
||||||
|
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
||||||
|
</svg>
|
||||||
|
<span className="font-montserrat">My Profile</span>
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setShowProfile(false);
|
||||||
|
// We'll add the credits modal trigger here
|
||||||
|
const event = new CustomEvent('openCredits');
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
}}
|
||||||
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-cream/90 hover:bg-luxury-gold/10 hover:text-cream transition-all group"
|
||||||
|
>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" className="text-luxury-gold/70 group-hover:text-luxury-gold fill-current">
|
||||||
|
<path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M11,16.5L6.5,12L7.91,10.59L11,13.67L16.59,8.09L18,9.5L11,16.5Z"/>
|
||||||
|
</svg>
|
||||||
|
<span className="font-montserrat">Credits</span>
|
||||||
|
</button>
|
||||||
|
<a href="#settings" className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-cream/90 hover:bg-luxury-gold/10 hover:text-cream transition-all group">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" className="text-luxury-gold/70 group-hover:text-luxury-gold fill-current">
|
||||||
|
<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.82,11.69,4.82,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/>
|
||||||
|
</svg>
|
||||||
|
<span className="font-montserrat">Settings</span>
|
||||||
|
</a>
|
||||||
|
<div className="h-px bg-luxury-gold/20 my-2"></div>
|
||||||
|
<a href="/login" className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-cream/90 hover:bg-red-500/10 hover:text-red-400 transition-all group">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" className="text-red-400/70 group-hover:text-red-400 fill-current">
|
||||||
|
<path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/>
|
||||||
|
</svg>
|
||||||
|
<span className="font-montserrat">Sign out</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Hidden file input for profile picture */}
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleProfileImageChange}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TopBar;
|
||||||
0
src/config.ts
Normal file
0
src/config.ts
Normal file
0
src/context/AuthContext.tsx
Normal file
0
src/context/AuthContext.tsx
Normal file
@@ -58,6 +58,16 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
background: #D4AF37;
|
background: #D4AF37;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Thin variant for internal panels */
|
||||||
|
.custom-thin-scroll::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
.custom-thin-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(197, 157, 89, 0.35);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Custom animations */
|
/* Custom animations */
|
||||||
@keyframes fadeInUp {
|
@keyframes fadeInUp {
|
||||||
from {
|
from {
|
||||||
@@ -171,3 +181,47 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 3D Carousel Utilities */
|
||||||
|
.perspective-1000 {
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preserve-3d {
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin-slow {
|
||||||
|
animation: spin-slow 20s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin-ultra-slow {
|
||||||
|
animation: spin-slow 40s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin-ultra-slow-reverse {
|
||||||
|
animation: spin-slow-reverse 40s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin-slow {
|
||||||
|
from {
|
||||||
|
transform: rotateY(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotateY(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin-slow-reverse {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(-360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation delay utilities */
|
||||||
|
.animation-delay-1000 {
|
||||||
|
animation-delay: 1s;
|
||||||
|
}
|
||||||
|
|||||||
0
src/lib/http.ts
Normal file
0
src/lib/http.ts
Normal file
@@ -1,19 +1,10 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const ForgotPasswordPage: React.FC = () => {
|
const ForgotPasswordPage: React.FC = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isSuccess, setIsSuccess] = useState(false);
|
const [isSuccess, setIsSuccess] = useState(false);
|
||||||
const [magicEffect, setMagicEffect] = useState(0);
|
|
||||||
|
|
||||||
// Animate magic effects
|
|
||||||
useEffect(() => {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setMagicEffect(prev => (prev + 1) % 8);
|
|
||||||
}, 600);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleResetPassword = (e: React.FormEvent) => {
|
const handleResetPassword = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -25,129 +16,60 @@ const ForgotPasswordPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center px-4 relative overflow-hidden">
|
<div className="min-h-screen flex items-center justify-center px-4 relative overflow-hidden bg-gradient-to-br from-deep-forest/90 via-deep-forest/80 to-rich-brown/90">
|
||||||
{/* Mystical Background */}
|
{/* Floating Particles */}
|
||||||
<div className="fixed inset-0 opacity-15">
|
|
||||||
<div className="absolute inset-0" style={{
|
|
||||||
backgroundImage: `
|
|
||||||
radial-gradient(circle at 25% 25%, rgba(197, 157, 89, 0.2) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 75% 75%, rgba(205, 127, 50, 0.2) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 50% 50%, rgba(212, 175, 55, 0.1) 0%, transparent 70%)
|
|
||||||
`
|
|
||||||
}}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Floating Magic Particles */}
|
|
||||||
<div className="fixed inset-0 pointer-events-none">
|
<div className="fixed inset-0 pointer-events-none">
|
||||||
{[...Array(20)].map((_, i) => (
|
{[...Array(18)].map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="absolute animate-bounce"
|
className="absolute w-1 h-1 bg-luxury-gold rounded-full animate-pulse"
|
||||||
style={{
|
style={{
|
||||||
left: `${Math.random() * 100}%`,
|
left: `${Math.random() * 100}%`,
|
||||||
top: `${Math.random() * 100}%`,
|
top: `${Math.random() * 100}%`,
|
||||||
animationDelay: `${Math.random() * 4}s`,
|
animationDelay: `${Math.random() * 3}s`,
|
||||||
animationDuration: `${3 + Math.random() * 2}s`
|
animationDuration: `${2 + Math.random() * 3}s`
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="w-2 h-2 bg-luxury-gold rounded-full opacity-60"></div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-w-6xl w-full grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
|
||||||
{/* Wizard Character Side */}
|
|
||||||
<div className="flex flex-col items-center space-y-8 order-2 lg:order-1">
|
|
||||||
{/* Animated Wizard */}
|
|
||||||
<div className="relative">
|
|
||||||
<div className="w-80 h-80 bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 rounded-3xl border-4 border-luxury-gold/30 backdrop-blur-xl flex items-center justify-center relative overflow-hidden">
|
|
||||||
{/* Wizard Container */}
|
|
||||||
<div className="relative w-64 h-64">
|
|
||||||
{/* Wizard Base */}
|
|
||||||
<div className={`absolute inset-0 transition-all duration-300 ${magicEffect % 2 === 0 ? 'scale-100' : 'scale-105'}`}>
|
|
||||||
{/* Robes */}
|
|
||||||
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 w-28 h-36 bg-gradient-to-b from-bronze to-deep-forest rounded-t-full border-2 border-luxury-gold/50"></div>
|
|
||||||
|
|
||||||
{/* Head */}
|
|
||||||
<div className="absolute bottom-32 left-1/2 transform -translate-x-1/2 w-16 h-16 bg-gradient-to-b from-champagne to-pearl rounded-full border-2 border-luxury-gold/50"></div>
|
|
||||||
|
|
||||||
{/* Wizard Hat */}
|
|
||||||
<div className="absolute bottom-40 left-1/2 transform -translate-x-1/2 w-18 h-16 bg-gradient-to-b from-deep-forest to-rich-brown rounded-t-full border-2 border-luxury-gold/50 relative">
|
|
||||||
<div className="absolute top-2 left-1/2 transform -translate-x-1/2 w-8 h-2 bg-luxury-gold rounded-full"></div>
|
|
||||||
{/* Hat tip */}
|
|
||||||
<div className="absolute -top-8 left-1/2 transform -translate-x-1/2 w-6 h-8 bg-gradient-to-t from-deep-forest to-rich-brown rounded-t-full border border-luxury-gold/30"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Glowing Eyes */}
|
|
||||||
<div className="absolute bottom-36 left-1/2 transform -translate-x-1/2">
|
|
||||||
<div className={`w-3 h-3 bg-luxury-gold rounded-full absolute -left-2 transition-all duration-500 ${magicEffect % 4 === 0 ? 'animate-pulse shadow-lg shadow-luxury-gold' : ''}`}></div>
|
|
||||||
<div className={`w-3 h-3 bg-luxury-gold rounded-full absolute left-2 transition-all duration-500 ${magicEffect % 4 === 0 ? 'animate-pulse shadow-lg shadow-luxury-gold' : ''}`}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Arms with Staff */}
|
|
||||||
<div className="absolute bottom-16 left-4 w-6 h-20 bg-gradient-to-b from-rich-brown to-deep-forest rounded-full border border-luxury-gold/30"></div>
|
|
||||||
<div className={`absolute bottom-16 right-2 w-6 h-20 bg-gradient-to-b from-rich-brown to-deep-forest rounded-full border border-luxury-gold/30 transform transition-transform duration-300 ${magicEffect % 2 === 0 ? 'rotate-12' : '-rotate-6'}`}></div>
|
|
||||||
|
|
||||||
{/* Magic Staff */}
|
|
||||||
<div className={`absolute bottom-8 right-0 w-2 h-32 bg-gradient-to-b from-bronze to-rich-brown transform transition-all duration-300 ${magicEffect % 2 === 0 ? 'rotate-12 scale-110' : '-rotate-6'}`}>
|
|
||||||
{/* Staff Crystal */}
|
|
||||||
<div className={`absolute -top-4 left-1/2 transform -translate-x-1/2 w-6 h-6 bg-luxury-gold rounded-full transition-all duration-300 ${magicEffect % 4 === 0 ? 'animate-pulse scale-125 shadow-lg shadow-luxury-gold' : ''}`}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Beard */}
|
|
||||||
<div className="absolute bottom-28 left-1/2 transform -translate-x-1/2 w-12 h-8 bg-gradient-to-b from-platinum to-cream rounded-b-full border border-luxury-gold/30"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Magic Sparkles */}
|
|
||||||
{magicEffect % 3 === 0 && (
|
|
||||||
<div className="absolute inset-0">
|
|
||||||
{[...Array(8)].map((_, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="absolute w-1 h-1 bg-luxury-gold rounded-full animate-ping"
|
|
||||||
style={{
|
|
||||||
left: `${20 + Math.random() * 60}%`,
|
|
||||||
top: `${20 + Math.random() * 60}%`,
|
|
||||||
animationDelay: `${i * 0.2}s`
|
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
{/* Ambient Gradient Glows */}
|
||||||
|
<div className="pointer-events-none absolute inset-0 -z-10 overflow-hidden">
|
||||||
|
<div className="absolute -top-24 -right-24 w-96 h-96 rounded-full bg-gradient-to-br from-luxury-gold/20 to-bronze/20 blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-16 -left-20 w-[28rem] h-[28rem] rounded-full bg-gradient-to-tr from-cream/10 to-luxury-gold/10 blur-3xl"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Magic Aura */}
|
{/* Centered Recovery Card */}
|
||||||
<div className={`absolute inset-0 bg-gradient-to-r from-luxury-gold/20 to-bronze/20 rounded-3xl transition-opacity duration-500 ${magicEffect % 4 === 0 ? 'opacity-100 animate-pulse' : 'opacity-40'}`}></div>
|
<div className="w-full max-w-md">
|
||||||
</div>
|
<div className="relative overflow-hidden backdrop-blur-2xl backdrop-saturate-150 bg-white/12 border border-white/25 rounded-3xl p-8 shadow-[0_10px_30px_rgba(0,0,0,0.35)] ring-1 ring-white/10">
|
||||||
</div>
|
{/* outer soft glow */}
|
||||||
|
<div className="pointer-events-none absolute -inset-0.5 rounded-3xl bg-gradient-to-br from-luxury-gold/10 to-bronze/10 blur-2xl"></div>
|
||||||
{/* Wizard Speech Bubble */}
|
{/* inner highlight border */}
|
||||||
<div className="bg-gradient-to-br from-deep-forest/80 to-rich-brown/60 backdrop-blur-xl rounded-2xl p-6 border-2 border-luxury-gold/30 relative max-w-sm">
|
<div className="pointer-events-none absolute inset-[1px] rounded-3xl border border-white/20"></div>
|
||||||
<div className="absolute -top-2 left-8 w-4 h-4 bg-gradient-to-br from-deep-forest/80 to-rich-brown/60 rotate-45 border-t-2 border-l-2 border-luxury-gold/30"></div>
|
{/* top-left sheen */}
|
||||||
<p className="font-montserrat text-cream text-center italic">
|
<div className="pointer-events-none absolute -top-8 -left-8 w-[140%] h-1/2 rounded-[3rem] bg-gradient-to-b from-white/20 to-transparent opacity-60 rotate-[-6deg]"></div>
|
||||||
{isSuccess
|
{/* subtle texture */}
|
||||||
? "✨ The spell has been cast! Check your email for the magic recovery link!"
|
<div
|
||||||
: "🔮 Fear not, traveler! I shall help you recover your lost credentials with ancient magic!"
|
className="pointer-events-none absolute inset-0 rounded-3xl opacity-10 mix-blend-soft-light"
|
||||||
}
|
style={{ backgroundImage: 'radial-gradient(rgba(255,255,255,0.10) 1px, transparent 1px)', backgroundSize: '6px 6px' }}
|
||||||
</p>
|
></div>
|
||||||
</div>
|
<div className="relative">
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Reset Password Form Side */}
|
|
||||||
<div className="order-1 lg:order-2">
|
|
||||||
<div className="bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 backdrop-blur-2xl rounded-3xl border-2 border-luxury-gold/30 p-8 shadow-luxury-xl">
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
<div className="flex items-center justify-center space-x-3 mb-4">
|
<div className="flex items-center justify-center mb-4">
|
||||||
<div className="w-12 h-12 bg-gradient-to-br from-luxury-gold to-bronze rounded-xl flex items-center justify-center">
|
<div className="h-16 flex items-center">
|
||||||
<span className="font-playfair text-2xl font-bold text-deep-forest">🔮</span>
|
<img
|
||||||
|
src="/src/assets/Dark-LineLogo-cropped.svg"
|
||||||
|
alt="Pennyzo Logo"
|
||||||
|
className="h-full w-auto object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-playfair text-4xl font-bold text-cream">Password Recovery</h1>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h1 className="font-playfair text-3xl font-bold text-cream mb-2">Password Recovery</h1>
|
||||||
<p className="font-montserrat text-cream/80 text-lg">
|
<p className="font-montserrat text-cream/80 text-lg">
|
||||||
Pennyzo Web - Multiagent Business Solutions
|
Multiagent Business Solutions
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 inline-block px-4 py-2 bg-gradient-to-r from-luxury-gold/20 to-brass/20 rounded-full border border-luxury-gold/30">
|
<div className="mt-4 inline-block px-4 py-2 bg-gradient-to-r from-luxury-gold/15 to-brass/15 rounded-full border border-luxury-gold/30">
|
||||||
<span className="font-montserrat text-luxury-gold text-sm font-medium">Recovery Mode</span>
|
<span className="font-montserrat text-luxury-gold text-sm font-medium">Recovery Mode</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,7 +89,7 @@ const ForgotPasswordPage: React.FC = () => {
|
|||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className="w-full px-4 py-3 bg-deep-forest/60 border-2 border-luxury-gold/30 rounded-xl text-cream placeholder-cream/50 focus:border-luxury-gold focus:outline-none focus:ring-2 focus:ring-luxury-gold/20 transition-all duration-300"
|
className="w-full px-4 py-3 rounded-2xl bg-white/15 border border-white/25 text-cream placeholder-cream/60 focus:outline-none focus:ring-4 focus:ring-luxury-gold/25 focus:border-luxury-gold/50 focus:ring-offset-2 focus:ring-offset-white/5 backdrop-blur-md shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
|
||||||
placeholder="Enter your email address"
|
placeholder="Enter your email address"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@@ -177,7 +99,7 @@ const ForgotPasswordPage: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="w-full bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold py-4 rounded-xl hover:scale-105 hover:shadow-luxury-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold py-4 rounded-2xl hover:scale-[1.015] hover:shadow-[0_18px_40px_rgba(0,0,0,0.35)] transition-all duration-300 focus:outline-none focus:ring-4 focus:ring-luxury-gold/30 focus:ring-offset-2 focus:ring-offset-white/5 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center space-x-2">
|
<div className="flex items-center justify-center space-x-2">
|
||||||
@@ -199,7 +121,7 @@ const ForgotPasswordPage: React.FC = () => {
|
|||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m0 7h18" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m0 7h18" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>⚔️ Return to Login Portal</span>
|
<span>Return to Login</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -230,9 +152,9 @@ const ForgotPasswordPage: React.FC = () => {
|
|||||||
|
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
to="/login"
|
||||||
className="inline-block w-full bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold py-4 rounded-xl hover:scale-105 hover:shadow-luxury-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50"
|
className="inline-block w-full bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold py-4 rounded-2xl hover:scale-[1.015] hover:shadow-[0_18px_40px_rgba(0,0,0,0.35)] transition-all duration-300 focus:outline-none focus:ring-4 focus:ring-luxury-gold/30 focus:ring-offset-2 focus:ring-offset-white/5"
|
||||||
>
|
>
|
||||||
⚡ Return to Login Portal
|
Return to Login
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -250,7 +172,7 @@ const ForgotPasswordPage: React.FC = () => {
|
|||||||
to="/home"
|
to="/home"
|
||||||
className="inline-block mt-3 text-luxury-gold hover:text-brass text-sm underline transition-colors duration-300"
|
className="inline-block mt-3 text-luxury-gold hover:text-brass text-sm underline transition-colors duration-300"
|
||||||
>
|
>
|
||||||
🏠 Return to Pennyzo Web Homepage
|
Return to Pennyzo Web Homepage
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,263 +1,99 @@
|
|||||||
import React from 'react';
|
import React, { useMemo, useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import TopBar from '../components/TopBar';
|
||||||
import Header from '../components/Header';
|
import SidebarNav from '../components/SidebarNav';
|
||||||
import Footer from '../components/Footer';
|
import AgentPanel from '../components/AgentPanel';
|
||||||
import LiveCounter from '../components/LiveCounter';
|
import type { Agent } from '../components/AgentPanel';
|
||||||
|
import DisplayPanel from '../components/DisplayPanel';
|
||||||
|
import ChatDock from '../components/ChatDock';
|
||||||
|
import type { ChatMessage } from '../components/ChatDock';
|
||||||
|
import CreditsScreen from '../components/CreditsModal';
|
||||||
|
|
||||||
const LandingPage: React.FC = () => {
|
const LandingPage: React.FC = () => {
|
||||||
const agents = [
|
// Companies for top dropdown
|
||||||
{
|
const companies = ['Pennyzo Labs', 'Pennyzo Retail', 'Pennyzo Finance'];
|
||||||
id: 'hr',
|
const [company, setCompany] = useState(companies[0]);
|
||||||
name: 'HR Command',
|
|
||||||
icon: '👥',
|
|
||||||
description: 'Intelligent Human Resources management with automated recruitment, performance tracking, and employee analytics.',
|
|
||||||
features: ['Smart Recruitment', 'Performance Analytics', 'Payroll Automation', 'Employee Engagement'],
|
|
||||||
color: 'from-luxury-gold to-brass'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'finance',
|
|
||||||
name: 'Finance Sage',
|
|
||||||
icon: '💰',
|
|
||||||
description: 'Advanced financial management with real-time reporting, budget optimization, and predictive analytics.',
|
|
||||||
features: ['Real-time Reports', 'Budget Planning', 'Risk Analysis', 'Automated Invoicing'],
|
|
||||||
color: 'from-bronze to-luxury-gold'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'project',
|
|
||||||
name: 'Project Sensei',
|
|
||||||
icon: '📊',
|
|
||||||
description: 'Comprehensive project management with intelligent task allocation and deadline optimization.',
|
|
||||||
features: ['Smart Planning', 'Resource Allocation', 'Timeline Optimization', 'Team Collaboration'],
|
|
||||||
color: 'from-brass to-bronze'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'crm',
|
|
||||||
name: 'CRM Ninja',
|
|
||||||
icon: '🤝',
|
|
||||||
description: 'Customer relationship mastery with automated lead scoring and personalized engagement strategies.',
|
|
||||||
features: ['Lead Scoring', 'Sales Pipeline', 'Customer Analytics', 'Automated Outreach'],
|
|
||||||
color: 'from-luxury-gold to-champagne'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'ops',
|
|
||||||
name: 'Operations Dojo',
|
|
||||||
icon: '⚙️',
|
|
||||||
description: 'Operational excellence through intelligent workflow automation and process optimization.',
|
|
||||||
features: ['Workflow Automation', 'Process Mining', 'Quality Control', 'Supply Chain'],
|
|
||||||
color: 'from-bronze to-rich-brown'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'analytics',
|
|
||||||
name: 'Analytics Oracle',
|
|
||||||
icon: '📈',
|
|
||||||
description: 'Business intelligence powered by AI-driven insights and predictive modeling capabilities.',
|
|
||||||
features: ['Predictive Models', 'Data Visualization', 'Custom Dashboards', 'Trend Analysis'],
|
|
||||||
color: 'from-brass to-luxury-gold'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const stats = [
|
// Sidebar
|
||||||
{ value: '500K+', label: 'Active Users' },
|
const sidebarItems = [
|
||||||
{ value: '2.5M+', label: 'Tasks Automated' },
|
{ id: 'dashboard', label: 'Dashboard', icon: <span>🏠</span> },
|
||||||
{ value: '150+', label: 'Countries' },
|
{ id: 'agents', label: 'Agents', icon: <span>🤖</span> },
|
||||||
{ value: '99.9%', label: 'Uptime' }
|
{ id: 'projects', label: 'Projects', icon: <span>📁</span> },
|
||||||
|
{ id: 'reports', label: 'Reports', icon: <span>📈</span> },
|
||||||
|
{ id: 'settings', label: 'Settings', icon: <span>⚙️</span> },
|
||||||
];
|
];
|
||||||
|
const [activeNav, setActiveNav] = useState('dashboard');
|
||||||
|
|
||||||
|
// Agents
|
||||||
|
const agents: Agent[] = useMemo(() => ([
|
||||||
|
{ id: 'hr', name: 'HR Command', color: 'from-luxury-gold/30 to-brass/30', emoji: '👥' },
|
||||||
|
{ id: 'finance', name: 'Finance Sage', color: 'from-bronze/30 to-luxury-gold/30', emoji: '💰' },
|
||||||
|
{ id: 'project', name: 'Project Sensei', color: 'from-brass/30 to-bronze/30', emoji: '📊' },
|
||||||
|
{ id: 'marketing', name: 'Marketing Genius', color: 'from-luxury-gold/30 to-cream/30', emoji: '🎯' },
|
||||||
|
{ id: 'analytics', name: 'Data Wizard', color: 'from-bronze/30 to-brass/30', emoji: '📈' },
|
||||||
|
{ id: 'security', name: 'Security Guard', color: 'from-deep-forest/40 to-luxury-gold/30', emoji: '🛡️' },
|
||||||
|
{ id: 'support', name: 'Support Hero', color: 'from-brass/30 to-rich-brown/30', emoji: '🎧' },
|
||||||
|
{ id: 'research', name: 'Research Bot', color: 'from-luxury-gold/30 to-bronze/30', emoji: '🔍' }
|
||||||
|
]), []);
|
||||||
|
const [activeAgent, setActiveAgent] = useState(agents[0].id);
|
||||||
|
|
||||||
|
// Chat
|
||||||
|
const [messages, setMessages] = useState<ChatMessage[]>([
|
||||||
|
{ id: 'm1', role: 'assistant', content: 'Hello! Choose an agent and tell me what to do.' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Credits Modal
|
||||||
|
const [showCredits, setShowCredits] = useState(false);
|
||||||
|
|
||||||
|
// Listen for credits modal trigger from TopBar
|
||||||
|
useEffect(() => {
|
||||||
|
const handleOpenCredits = () => {
|
||||||
|
setShowCredits(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('openCredits', handleOpenCredits);
|
||||||
|
return () => window.removeEventListener('openCredits', handleOpenCredits);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSend = (text: string) => {
|
||||||
|
const userMsg: ChatMessage = { id: crypto.randomUUID(), role: 'user', content: text };
|
||||||
|
const assistantMsg: ChatMessage = { id: crypto.randomUUID(), role: 'assistant', content: `Got it! The ${agents.find(a=>a.id===activeAgent)?.name} will process: "${text}"` };
|
||||||
|
setMessages((prev) => [...prev, userMsg, assistantMsg]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="min-h-screen flex flex-col overflow-hidden">
|
||||||
<Header />
|
{/* Top bar */}
|
||||||
|
<TopBar
|
||||||
|
companies={companies}
|
||||||
|
selectedCompany={company}
|
||||||
|
onCompanyChange={setCompany}
|
||||||
|
coins={128}
|
||||||
|
notifications={[{ id: 'n1', title: 'Welcome to Pennyzo!', time: 'Just now' }]}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Hero Section */}
|
{/* Main area with sidebar + two panels */}
|
||||||
<section className="relative min-h-screen flex items-center justify-center pt-20 overflow-hidden">
|
<div className="flex-1 min-h-0 grid grid-cols-[auto,1fr] gap-0 overflow-hidden">
|
||||||
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
<SidebarNav items={sidebarItems} activeId={activeNav} onSelect={setActiveNav} />
|
||||||
<div className="mb-8">
|
<div className="p-3 md:p-6 grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6 min-h-0 overflow-hidden h-[calc(100vh-13rem)] md:h-[calc(100vh-15rem)]">
|
||||||
<div className="inline-block px-8 py-3 bg-gradient-to-r from-luxury-gold/20 to-brass/20 border-2 border-luxury-gold/30 rounded-full text-luxury-gold font-montserrat font-medium text-lg backdrop-blur-sm mb-8">
|
<AgentPanel agents={agents} activeAgentId={activeAgent} onChangeAgent={setActiveAgent} />
|
||||||
🚀 Pennyzo Web - Next-Gen Business Solutions
|
<DisplayPanel />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="font-playfair text-5xl sm:text-6xl lg:text-8xl xl:text-9xl font-bold text-cream tracking-tight mb-8 leading-[0.95]">
|
{/* Chat dock at bottom (aligned to right of sidebar) */}
|
||||||
<span className="block">Multiagent</span>
|
<ChatDock
|
||||||
<span className="block">Mastery</span>
|
messages={messages}
|
||||||
</h1>
|
onSend={handleSend}
|
||||||
|
leftOffsetClass="left-20 md:left-64"
|
||||||
|
className="w-[calc(100%-5rem)] md:w-[calc(100%-16rem)]"
|
||||||
|
placeholder="Ask anything..."
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="font-montserrat text-xl sm:text-2xl lg:text-3xl text-cream/80 max-w-5xl mx-auto mb-12 leading-relaxed">
|
{/* Credits Screen */}
|
||||||
Harness the power of specialized AI agents to transform your business operations.
|
<CreditsScreen
|
||||||
From HR management to financial analytics - unlock unprecedented efficiency and growth.
|
isOpen={showCredits}
|
||||||
</p>
|
onClose={() => setShowCredits(false)}
|
||||||
|
/>
|
||||||
<div className="flex flex-col sm:flex-row gap-6 justify-center items-center mb-16">
|
|
||||||
<Link
|
|
||||||
to="/login"
|
|
||||||
className="min-w-[250px] bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold px-10 py-5 rounded-full hover:scale-105 hover:shadow-luxury-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 text-lg"
|
|
||||||
>
|
|
||||||
⚡ Enter the Pennyzo Realm
|
|
||||||
</Link>
|
|
||||||
<button className="min-w-[250px] border-2 border-luxury-gold/50 text-luxury-gold font-montserrat font-semibold px-10 py-5 rounded-full hover:bg-luxury-gold/10 hover:border-luxury-gold hover:scale-105 transition-all duration-300 backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 text-lg">
|
|
||||||
🎯 Watch Demo
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats */}
|
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8 max-w-4xl mx-auto">
|
|
||||||
{stats.map((stat) => (
|
|
||||||
<div
|
|
||||||
key={stat.label}
|
|
||||||
className="bg-gradient-to-br from-deep-forest/40 to-rich-brown/20 backdrop-blur-2xl border border-luxury-gold/20 rounded-2xl p-6 hover:scale-105 transition-all duration-300"
|
|
||||||
>
|
|
||||||
<div className="text-3xl sm:text-4xl font-playfair font-bold text-luxury-gold mb-2">
|
|
||||||
{stat.value}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm font-montserrat text-cream/70">
|
|
||||||
{stat.label}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Agents Showcase */}
|
|
||||||
<section className="relative py-24 lg:py-32">
|
|
||||||
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
||||||
<div className="text-center mb-20">
|
|
||||||
<div className="inline-block px-6 py-2 bg-gradient-to-r from-luxury-gold/20 to-brass/20 border border-luxury-gold/30 rounded-full text-luxury-gold font-montserrat font-medium text-sm backdrop-blur-sm mb-6">
|
|
||||||
🤖 AI-Powered Agent Arsenal
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 className="font-playfair text-4xl sm:text-5xl lg:text-6xl font-bold text-cream tracking-tight mb-6">
|
|
||||||
Your Business
|
|
||||||
<span className="block text-transparent bg-clip-text bg-gradient-to-r from-luxury-gold via-brass to-bronze">
|
|
||||||
Dream Team
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p className="font-montserrat text-lg sm:text-xl text-cream/80 max-w-3xl mx-auto leading-relaxed">
|
|
||||||
Meet your specialized AI agents, each trained to excel in specific business domains.
|
|
||||||
Together, they form an unstoppable force for digital transformation.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
||||||
{agents.map((agent) => (
|
|
||||||
<div
|
|
||||||
key={agent.id}
|
|
||||||
className="group bg-gradient-to-br from-deep-forest/40 to-rich-brown/20 backdrop-blur-2xl border border-luxury-gold/20 rounded-3xl p-8 hover:scale-105 hover:shadow-luxury-lg hover:border-luxury-gold/40 transition-all duration-500"
|
|
||||||
>
|
|
||||||
{/* Agent Icon */}
|
|
||||||
<div className={`w-20 h-20 bg-gradient-to-br ${agent.color} rounded-2xl flex items-center justify-center text-4xl mb-6 group-hover:scale-110 transition-transform duration-300`}>
|
|
||||||
{agent.icon}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 className="font-playfair text-2xl font-bold text-cream mb-4">
|
|
||||||
{agent.name}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p className="font-montserrat text-cream/80 leading-relaxed mb-6">
|
|
||||||
{agent.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Features */}
|
|
||||||
<div className="space-y-3 mb-6">
|
|
||||||
{agent.features.map((feature, featureIndex) => (
|
|
||||||
<div key={featureIndex} className="flex items-center space-x-3">
|
|
||||||
<div className="w-2 h-2 bg-luxury-gold rounded-full flex-shrink-0"></div>
|
|
||||||
<span className="font-montserrat text-sm text-cream/90">
|
|
||||||
{feature}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Action Button */}
|
|
||||||
<button className={`w-full bg-gradient-to-r ${agent.color} text-deep-forest font-montserrat font-semibold px-6 py-3 rounded-xl hover:scale-105 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50`}>
|
|
||||||
Deploy {agent.name}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Why Choose Pennyzo */}
|
|
||||||
<section className="relative py-24 lg:py-32 bg-gradient-to-b from-deep-forest/20 to-rich-brown/10">
|
|
||||||
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
||||||
<div className="text-center mb-20">
|
|
||||||
<h2 className="font-playfair text-4xl sm:text-5xl lg:text-6xl font-bold text-cream tracking-tight mb-6">
|
|
||||||
Why Choose
|
|
||||||
<span className="block text-transparent bg-clip-text bg-gradient-to-r from-luxury-gold via-brass to-bronze">
|
|
||||||
Pennyzo Web?
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
|
||||||
<div className="space-y-8">
|
|
||||||
{[
|
|
||||||
{
|
|
||||||
title: 'Unified Intelligence',
|
|
||||||
description: 'All agents work together seamlessly, sharing insights and optimizing cross-functional operations.',
|
|
||||||
icon: '🧠'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Enterprise-Grade Security',
|
|
||||||
description: 'Bank-level encryption and compliance standards protect your sensitive business data.',
|
|
||||||
icon: '🔒'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Scalable Architecture',
|
|
||||||
description: 'From startups to enterprises, our platform grows with your business needs.',
|
|
||||||
icon: '📈'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '24/7 AI Support',
|
|
||||||
description: 'Our agents never sleep, providing round-the-clock optimization and monitoring.',
|
|
||||||
icon: '🌟'
|
|
||||||
}
|
|
||||||
].map((benefit, index) => (
|
|
||||||
<div key={index} className="flex items-start space-x-4">
|
|
||||||
<div className="w-12 h-12 bg-gradient-to-br from-luxury-gold/20 to-brass/20 rounded-xl flex items-center justify-center text-2xl flex-shrink-0">
|
|
||||||
{benefit.icon}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-playfair text-xl font-bold text-cream mb-2">
|
|
||||||
{benefit.title}
|
|
||||||
</h3>
|
|
||||||
<p className="font-montserrat text-cream/80">
|
|
||||||
{benefit.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 backdrop-blur-2xl rounded-3xl border border-luxury-gold/30 p-12">
|
|
||||||
<h3 className="font-playfair text-3xl font-bold text-cream mb-6 text-center">
|
|
||||||
Ready to Transform Your Business?
|
|
||||||
</h3>
|
|
||||||
<p className="font-montserrat text-cream/80 text-lg mb-8 text-center">
|
|
||||||
Join thousands of businesses already leveraging the power of multiagent AI solutions.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<Link
|
|
||||||
to="/login"
|
|
||||||
className="block w-full bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold py-4 rounded-xl hover:scale-105 hover:shadow-luxury-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 text-center"
|
|
||||||
>
|
|
||||||
🚀 Start Free Trial
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<button className="w-full border-2 border-luxury-gold/50 text-luxury-gold font-montserrat font-semibold py-4 rounded-xl hover:bg-luxury-gold/10 hover:border-luxury-gold transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50">
|
|
||||||
📞 Schedule Demo
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<Footer />
|
|
||||||
<LiveCounter initialUsers={2847} initialRevenue={156789} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const LoginPage: React.FC = () => {
|
const LoginPage: React.FC = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [characterFrame, setCharacterFrame] = useState(0);
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Animate character sprite
|
|
||||||
useEffect(() => {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setCharacterFrame(prev => (prev + 1) % 4);
|
|
||||||
}, 800);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDemoLogin = () => {
|
const handleDemoLogin = () => {
|
||||||
setEmail('demo@pennyzo.com');
|
setEmail('demo@pennyzo.com');
|
||||||
setPassword('demo123');
|
setPassword('demo123');
|
||||||
@@ -64,78 +55,39 @@ const LoginPage: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-6xl w-full grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
{/* Ambient Gradient Glows */}
|
||||||
{/* Character Side */}
|
<div className="pointer-events-none absolute inset-0 -z-10 overflow-hidden">
|
||||||
<div className="flex flex-col items-center space-y-8 order-2 lg:order-1">
|
<div className="absolute -top-24 -right-24 w-96 h-96 rounded-full bg-gradient-to-br from-luxury-gold/20 to-bronze/20 blur-3xl"></div>
|
||||||
{/* Animated Character */}
|
<div className="absolute -bottom-16 -left-20 w-[28rem] h-[28rem] rounded-full bg-gradient-to-tr from-cream/10 to-luxury-gold/10 blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<div className="relative overflow-hidden backdrop-blur-2xl backdrop-saturate-150 bg-white/12 border border-white/25 rounded-3xl p-8 shadow-[0_10px_30px_rgba(0,0,0,0.35)] ring-1 ring-white/10">
|
||||||
|
{/* outer soft glow */}
|
||||||
|
<div className="pointer-events-none absolute -inset-0.5 rounded-3xl bg-gradient-to-br from-luxury-gold/10 to-bronze/10 blur-2xl"></div>
|
||||||
|
{/* inner highlight border */}
|
||||||
|
<div className="pointer-events-none absolute inset-[1px] rounded-3xl border border-white/20"></div>
|
||||||
|
{/* top-left sheen */}
|
||||||
|
<div className="pointer-events-none absolute -top-8 -left-8 w-[140%] h-1/2 rounded-[3rem] bg-gradient-to-b from-white/20 to-transparent opacity-60 rotate-[-6deg]"></div>
|
||||||
|
{/* subtle texture (dot pattern) */}
|
||||||
|
<div
|
||||||
|
className="pointer-events-none absolute inset-0 rounded-3xl opacity-10 mix-blend-soft-light"
|
||||||
|
style={{
|
||||||
|
backgroundImage: 'radial-gradient(rgba(255,255,255,0.10) 1px, transparent 1px)',
|
||||||
|
backgroundSize: '6px 6px'
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="w-80 h-80 bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 rounded-3xl border-4 border-luxury-gold/30 backdrop-blur-xl flex items-center justify-center relative overflow-hidden">
|
|
||||||
{/* Character Container */}
|
|
||||||
<div className="relative w-64 h-64">
|
|
||||||
{/* Character Base (Samurai/Ninja style) */}
|
|
||||||
<div className={`absolute inset-0 transition-all duration-200 ${characterFrame % 2 === 0 ? 'scale-100' : 'scale-105'}`}>
|
|
||||||
{/* Body */}
|
|
||||||
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 w-24 h-32 bg-gradient-to-b from-rich-brown to-deep-forest rounded-t-3xl border-2 border-luxury-gold/50"></div>
|
|
||||||
|
|
||||||
{/* Head */}
|
|
||||||
<div className="absolute bottom-32 left-1/2 transform -translate-x-1/2 w-16 h-16 bg-gradient-to-b from-champagne to-pearl rounded-full border-2 border-luxury-gold/50"></div>
|
|
||||||
|
|
||||||
{/* Hat/Helmet */}
|
|
||||||
<div className="absolute bottom-40 left-1/2 transform -translate-x-1/2 w-20 h-12 bg-gradient-to-b from-deep-forest to-rich-brown rounded-t-full border-2 border-luxury-gold/50"></div>
|
|
||||||
|
|
||||||
{/* Eyes */}
|
|
||||||
<div className="absolute bottom-36 left-1/2 transform -translate-x-1/2">
|
|
||||||
<div className={`w-2 h-2 bg-luxury-gold rounded-full absolute -left-2 transition-all duration-300 ${characterFrame % 4 === 0 ? 'animate-pulse' : ''}`}></div>
|
|
||||||
<div className={`w-2 h-2 bg-luxury-gold rounded-full absolute left-2 transition-all duration-300 ${characterFrame % 4 === 0 ? 'animate-pulse' : ''}`}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Arms */}
|
|
||||||
<div className={`absolute bottom-20 left-3 w-6 h-16 bg-gradient-to-b from-rich-brown to-deep-forest rounded-full border border-luxury-gold/30 transform transition-transform duration-200 ${characterFrame % 2 === 0 ? 'rotate-12' : 'rotate-0'}`}></div>
|
|
||||||
<div className={`absolute bottom-20 right-3 w-6 h-16 bg-gradient-to-b from-rich-brown to-deep-forest rounded-full border border-luxury-gold/30 transform transition-transform duration-200 ${characterFrame % 2 === 0 ? '-rotate-12' : 'rotate-0'}`}></div>
|
|
||||||
|
|
||||||
{/* Sword */}
|
|
||||||
<div className={`absolute bottom-12 right-1 w-1 h-24 bg-gradient-to-b from-platinum to-luxury-gold transform transition-all duration-200 ${characterFrame % 2 === 0 ? 'rotate-45 scale-110' : 'rotate-30'}`}></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Action Lines */}
|
|
||||||
{characterFrame === 1 && (
|
|
||||||
<div className="absolute inset-0">
|
|
||||||
<div className="absolute top-8 left-4 w-12 h-0.5 bg-luxury-gold opacity-70 animate-pulse"></div>
|
|
||||||
<div className="absolute top-16 right-6 w-8 h-0.5 bg-luxury-gold opacity-50 animate-pulse"></div>
|
|
||||||
<div className="absolute bottom-24 left-8 w-10 h-0.5 bg-luxury-gold opacity-60 animate-pulse"></div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Power Aura */}
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-luxury-gold/10 to-bronze/10 rounded-3xl animate-pulse"></div>
|
|
||||||
|
|
||||||
{/* Floating Orbs */}
|
|
||||||
<div className="absolute top-4 right-4 w-3 h-3 bg-luxury-gold rounded-full animate-bounce" style={{ animationDelay: '0s' }}></div>
|
|
||||||
<div className="absolute top-8 left-6 w-2 h-2 bg-bronze rounded-full animate-bounce" style={{ animationDelay: '0.5s' }}></div>
|
|
||||||
<div className="absolute bottom-8 right-8 w-2 h-2 bg-brass rounded-full animate-bounce" style={{ animationDelay: '1s' }}></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Character Speech Bubble */}
|
|
||||||
<div className="bg-gradient-to-br from-deep-forest/80 to-rich-brown/60 backdrop-blur-xl rounded-2xl p-6 border-2 border-luxury-gold/30 relative max-w-sm">
|
|
||||||
<div className="absolute -top-2 left-8 w-4 h-4 bg-gradient-to-br from-deep-forest/80 to-rich-brown/60 rotate-45 border-t-2 border-l-2 border-luxury-gold/30"></div>
|
|
||||||
<p className="font-montserrat text-cream text-center italic">
|
|
||||||
"Welcome. Sign in with your credentials to continue."
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Login Form Side */}
|
|
||||||
<div className="order-1 lg:order-2">
|
|
||||||
<div className="bg-gradient-to-br from-deep-forest/60 to-rich-brown/40 backdrop-blur-2xl rounded-3xl border-2 border-luxury-gold/30 p-8 shadow-luxury-xl">
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
<div className="flex items-center justify-center space-x-3 mb-4">
|
<div className="flex items-center justify-center mb-4">
|
||||||
<div className="w-12 h-12 bg-gradient-to-br from-luxury-gold to-bronze rounded-xl flex items-center justify-center">
|
<div className="h-16 md:h-20 flex items-center">
|
||||||
<span className="font-playfair text-2xl font-bold text-deep-forest">P</span>
|
<img
|
||||||
|
src="/src/assets/Dark-LineLogo-cropped.svg"
|
||||||
|
alt="Pennyzo Logo"
|
||||||
|
className="h-full w-auto object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-playfair text-4xl font-bold text-cream">Pennyzo Web</h1>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="font-montserrat text-cream/80 text-lg">Multiagent Business Solutions</p>
|
<p className="font-montserrat text-cream/80 text-lg">Multiagent Business Solutions</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -150,7 +102,7 @@ const LoginPage: React.FC = () => {
|
|||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className="w-full px-4 py-3 bg-deep-forest/60 border-2 border-luxury-gold/30 rounded-xl text-cream placeholder-cream/50 focus:border-luxury-gold focus:outline-none focus:ring-2 focus:ring-luxury-gold/20 transition-all duration-300"
|
className="w-full px-4 py-3 rounded-2xl bg-white/15 border border-white/25 text-cream placeholder-cream/60 focus:outline-none focus:ring-4 focus:ring-luxury-gold/25 focus:border-luxury-gold/50 focus:ring-offset-2 focus:ring-offset-white/5 backdrop-blur-md shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
|
||||||
placeholder="Enter your email"
|
placeholder="Enter your email"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@@ -164,7 +116,7 @@ const LoginPage: React.FC = () => {
|
|||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className="w-full px-4 py-3 bg-deep-forest/60 border-2 border-luxury-gold/30 rounded-xl text-cream placeholder-cream/50 focus:border-luxury-gold focus:outline-none focus:ring-2 focus:ring-luxury-gold/20 transition-all duration-300"
|
className="w-full px-4 py-3 rounded-2xl bg-white/15 border border-white/25 text-cream placeholder-cream/60 focus:outline-none focus:ring-4 focus:ring-luxury-gold/25 focus:border-luxury-gold/50 focus:ring-offset-2 focus:ring-offset-white/5 backdrop-blur-md shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
|
||||||
placeholder="Enter your password"
|
placeholder="Enter your password"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@@ -175,7 +127,7 @@ const LoginPage: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="w-full bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold py-4 rounded-xl hover:scale-105 hover:shadow-luxury-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold py-4 rounded-2xl hover:scale-[1.015] hover:shadow-[0_18px_40px_rgba(0,0,0,0.35)] transition-all duration-300 focus:outline-none focus:ring-4 focus:ring-luxury-gold/30 focus:ring-offset-2 focus:ring-offset-white/5 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center space-x-2">
|
<div className="flex items-center justify-center space-x-2">
|
||||||
@@ -190,7 +142,7 @@ const LoginPage: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDemoLogin}
|
onClick={handleDemoLogin}
|
||||||
className="w-full border-2 border-luxury-gold/50 text-luxury-gold font-montserrat font-semibold py-4 rounded-xl hover:bg-luxury-gold/10 hover:border-luxury-gold hover:scale-105 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50"
|
className="w-full border border-luxury-gold/50 text-luxury-gold font-montserrat font-semibold py-4 rounded-2xl hover:bg-luxury-gold/10 hover:border-luxury-gold hover:scale-[1.01] transition-all duration-300 focus:outline-none focus:ring-4 focus:ring-luxury-gold/25 focus:ring-offset-2 focus:ring-offset-white/5 backdrop-blur-md"
|
||||||
>
|
>
|
||||||
Demo Login
|
Demo Login
|
||||||
</button>
|
</button>
|
||||||
@@ -206,17 +158,6 @@ const LoginPage: React.FC = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{/*
|
|
||||||
Demo Credentials Info (commented out per request)
|
|
||||||
<div className="mt-8 bg-gradient-to-r from-luxury-gold/10 to-bronze/10 rounded-xl p-4 border border-luxury-gold/20">
|
|
||||||
<h3 className="font-montserrat font-semibold text-luxury-gold mb-2">Demo Credentials</h3>
|
|
||||||
<div className="font-montserrat text-sm text-cream/80 space-y-1">
|
|
||||||
<p>Email: <span className="text-luxury-gold">demo@pennyzo.com</span></p>
|
|
||||||
<p>Password: <span className="text-luxury-gold">demo123</span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
*/}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
0
src/pages/ProfilePage.tsx
Normal file
0
src/pages/ProfilePage.tsx
Normal file
0
src/pages/RegisterPage.tsx
Normal file
0
src/pages/RegisterPage.tsx
Normal file
83
src/pages/ResetPasswordOtpPage.tsx
Normal file
83
src/pages/ResetPasswordOtpPage.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
const ResetPasswordOtpPage: React.FC = () => {
|
||||||
|
const [otp, setOtp] = useState(['', '', '', '', '', '']);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const setDigit = (index: number, value: string) => {
|
||||||
|
const next = [...otp];
|
||||||
|
next[index] = value.replace(/\D/g, '').slice(0, 1);
|
||||||
|
setOtp(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
navigate('/reset-password-token');
|
||||||
|
}, 1200);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center px-4 relative overflow-hidden bg-gradient-to-br from-deep-forest/90 via-deep-forest/80 to-rich-brown/90">
|
||||||
|
{/* Ambient Gradient Glows */}
|
||||||
|
<div className="pointer-events-none absolute inset-0 -z-10 overflow-hidden">
|
||||||
|
<div className="absolute -top-24 -right-24 w-96 h-96 rounded-full bg-gradient-to-br from-luxury-gold/20 to-bronze/20 blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-16 -left-20 w-[28rem] h-[28rem] rounded-full bg-gradient-to-tr from-cream/10 to-luxury-gold/10 blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<div className="relative overflow-hidden backdrop-blur-2xl backdrop-saturate-150 bg-white/12 border border-white/25 rounded-3xl p-8 shadow-[0_10px_30px_rgba(0,0,0,0.35)] ring-1 ring-white/10">
|
||||||
|
<div className="pointer-events-none absolute -inset-0.5 rounded-3xl bg-gradient-to-br from-luxury-gold/10 to-bronze/10 blur-2xl"></div>
|
||||||
|
<div className="pointer-events-none absolute inset-[1px] rounded-3xl border border-white/20"></div>
|
||||||
|
<div className="pointer-events-none absolute -top-8 -left-8 w-[140%] h-1/2 rounded-[3rem] bg-gradient-to-b from-white/20 to-transparent opacity-60 rotate-[-6deg]"></div>
|
||||||
|
<div className="pointer-events-none absolute inset-0 rounded-3xl opacity-10 mix-blend-soft-light" style={{ backgroundImage: 'radial-gradient(rgba(255,255,255,0.10) 1px, transparent 1px)', backgroundSize: '6px 6px' }}></div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="flex items-center justify-center mb-4">
|
||||||
|
<div className="h-16 flex items-center">
|
||||||
|
<img
|
||||||
|
src="/src/assets/Dark-LineLogo-cropped.svg"
|
||||||
|
alt="Pennyzo Logo"
|
||||||
|
className="h-full w-auto object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 className="font-playfair text-3xl font-bold text-cream mb-2">Verify OTP</h1>
|
||||||
|
<p className="font-montserrat text-cream/80 text-lg">Enter the 6-digit code sent to your email</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div className="grid grid-cols-6 gap-3">
|
||||||
|
{otp.map((d, i) => (
|
||||||
|
<input
|
||||||
|
key={i}
|
||||||
|
value={d}
|
||||||
|
onChange={(e) => setDigit(i, e.target.value)}
|
||||||
|
inputMode="numeric"
|
||||||
|
className="text-center text-xl font-montserrat w-full py-3 rounded-2xl bg-white/15 border border-white/25 text-cream focus:outline-none focus:ring-4 focus:ring-luxury-gold/25 focus:border-luxury-gold/50 focus:ring-offset-2 focus:ring-offset-white/5 backdrop-blur-md shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
|
||||||
|
placeholder="_"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" disabled={isLoading} className="w-full bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold py-4 rounded-2xl hover:scale-[1.015] hover:shadow-[0_18px_40px_rgba(0,0,0,0.35)] transition-all duration-300 focus:outline-none focus:ring-4 focus:ring-luxury-gold/30 focus:ring-offset-2 focus:ring-offset-white/5 disabled:opacity-50">
|
||||||
|
{isLoading ? 'Verifying…' : 'Verify Code'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Link to="/forgot-password" className="font-montserrat text-luxury-gold hover:text-brass underline">Resend OTP</Link>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResetPasswordOtpPage;
|
||||||
88
src/pages/ResetPasswordTokenPage.tsx
Normal file
88
src/pages/ResetPasswordTokenPage.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
const ResetPasswordTokenPage: React.FC = () => {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [confirm, setConfirm] = useState('');
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
navigate('/login');
|
||||||
|
}, 1200);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center px-4 relative overflow-hidden bg-gradient-to-br from-deep-forest/90 via-deep-forest/80 to-rich-brown/90">
|
||||||
|
{/* Ambient Gradient Glows */}
|
||||||
|
<div className="pointer-events-none absolute inset-0 -z-10 overflow-hidden">
|
||||||
|
<div className="absolute -top-24 -right-24 w-96 h-96 rounded-full bg-gradient-to-br from-luxury-gold/20 to-bronze/20 blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-16 -left-20 w-[28rem] h-[28rem] rounded-full bg-gradient-to-tr from-cream/10 to-luxury-gold/10 blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<div className="relative overflow-hidden backdrop-blur-2xl backdrop-saturate-150 bg-white/12 border border-white/25 rounded-3xl p-8 shadow-[0_10px_30px_rgba(0,0,0,0.35)] ring-1 ring-white/10">
|
||||||
|
<div className="pointer-events-none absolute -inset-0.5 rounded-3xl bg-gradient-to-br from-luxury-gold/10 to-bronze/10 blur-2xl"></div>
|
||||||
|
<div className="pointer-events-none absolute inset-[1px] rounded-3xl border border-white/20"></div>
|
||||||
|
<div className="pointer-events-none absolute -top-8 -left-8 w-[140%] h-1/2 rounded-[3rem] bg-gradient-to-b from-white/20 to-transparent opacity-60 rotate-[-6deg]"></div>
|
||||||
|
<div className="pointer-events-none absolute inset-0 rounded-3xl opacity-10 mix-blend-soft-light" style={{ backgroundImage: 'radial-gradient(rgba(255,255,255,0.10) 1px, transparent 1px)', backgroundSize: '6px 6px' }}></div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="flex items-center justify-center mb-4">
|
||||||
|
<div className="h-16 flex items-center">
|
||||||
|
<img
|
||||||
|
src="/src/assets/Dark-LineLogo-cropped.svg"
|
||||||
|
alt="Pennyzo Logo"
|
||||||
|
className="h-full w-auto object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 className="font-playfair text-3xl font-bold text-cream mb-2">Set New Password</h1>
|
||||||
|
<p className="font-montserrat text-cream/80 text-lg">Choose a strong password for your account</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label className="block font-montserrat text-cream font-medium mb-2">New Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-2xl bg-white/15 border border-white/25 text-cream placeholder-cream/60 focus:outline-none focus:ring-4 focus:ring-luxury-gold/25 focus:border-luxury-gold/50 focus:ring-offset-2 focus:ring-offset-white/5 backdrop-blur-md shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
|
||||||
|
placeholder="Enter new password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block font-montserrat text-cream font-medium mb-2">Confirm Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={confirm}
|
||||||
|
onChange={(e) => setConfirm(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-2xl bg-white/15 border border-white/25 text-cream placeholder-cream/60 focus:outline-none focus:ring-4 focus:ring-luxury-gold/25 focus:border-luxury-gold/50 focus:ring-offset-2 focus:ring-offset-white/5 backdrop-blur-md shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
|
||||||
|
placeholder="Confirm new password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" disabled={isLoading} className="w-full bg-gradient-to-r from-luxury-gold to-brass text-deep-forest font-montserrat font-bold py-4 rounded-2xl hover:scale-[1.015] hover:shadow-[0_18px_40px_rgba(0,0,0,0.35)] transition-all duration-300 focus:outline-none focus:ring-4 focus:ring-luxury-gold/30 focus:ring-offset-2 focus:ring-offset-white/5 disabled:opacity-50">
|
||||||
|
{isLoading ? 'Saving…' : 'Save Password'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Link to="/login" className="font-montserrat text-luxury-gold hover:text-brass underline">Back to Login</Link>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResetPasswordTokenPage;
|
||||||
0
src/pages/VerifyEmailPage.tsx
Normal file
0
src/pages/VerifyEmailPage.tsx
Normal file
0
src/services/auth.ts
Normal file
0
src/services/auth.ts
Normal file
0
src/services/registration.ts
Normal file
0
src/services/registration.ts
Normal file
0
src/services/system.ts
Normal file
0
src/services/system.ts
Normal file
0
src/services/users.ts
Normal file
0
src/services/users.ts
Normal file
Reference in New Issue
Block a user