Files
citycards-partnerweb/src/App.tsx
priyanshuvish b8068d1a52 first commit
2025-09-18 15:03:41 +05:30

458 lines
21 KiB
TypeScript

import { Button } from "./components/ui/button";
import { Input } from "./components/ui/input";
import { Label } from "./components/ui/label";
import { Checkbox } from "./components/ui/checkbox";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "./components/ui/input-otp";
import { Mail, Lock, Eye, EyeOff, Search, Users, Calendar, TrendingUp, Clock, User } from "lucide-react";
import { useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import FlightBookingRafiki from "./imports/FlightBookingRafiki";
import Sidebar from "./components/Sidebar";
import Header from "./components/Header";
import Dashboard from "./components/Dashboard";
import RedemptionsPage from "./components/RedemptionsPage";
import StaffManagementPage from "./components/StaffManagementPage";
import SupportPage from "./components/SupportPage";
import BookingManagementPage from "./components/BookingManagementPage";
import RecurringBlockPage from "./components/RecurringBlockPage";
import NotificationsPage from "./components/NotificationsPage";
export default function App() {
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [rememberMe, setRememberMe] = useState(false);
const [showForgotPassword, setShowForgotPassword] = useState(false);
const [showOTPVerification, setShowOTPVerification] = useState(false);
const [showResetPassword, setShowResetPassword] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [otpValue, setOtpValue] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [showPasswordPolicies, setShowPasswordPolicies] = useState(false);
const [activeNavItem, setActiveNavItem] = useState("dashboard");
// Password validation functions
const validatePassword = (password) => {
return {
hasMinLength: password.length >= 8,
hasUppercase: /[A-Z]/.test(password),
hasLowercase: /[a-z]/.test(password),
hasNumber: /[0-9]/.test(password),
hasSpecialChar: /[!@#$%^&*(),.?":{}|<>]/.test(password)
};
};
const passwordValidation = validatePassword(newPassword);
// If user is logged in, show dashboard
if (isLoggedIn) {
return (
<div className="min-h-screen bg-gray-50">
{/* Fixed Sidebar */}
<Sidebar
activeItem={activeNavItem}
onItemSelect={setActiveNavItem}
/>
{/* Main Content with left margin for sidebar */}
<div className="ml-[280px]">
{/* Header with notifications and profile */}
<Header onNavigateToNotifications={() => setActiveNavItem("notifications")} />
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease: "easeInOut" }}
>
{activeNavItem === "dashboard" && <Dashboard />}
{(activeNavItem === "booking-table" || activeNavItem === "booking-calendar") && (
<BookingManagementPage
activeView={activeNavItem}
onNavigateToRecurringBlock={() => setActiveNavItem("recurring-block")}
/>
)}
{activeNavItem === "recurring-block" && (
<RecurringBlockPage onNavigateBack={() => setActiveNavItem("booking-calendar")} />
)}
{activeNavItem === "redemptions" && <RedemptionsPage />}
{activeNavItem === "staff" && <StaffManagementPage />}
{activeNavItem === "support" && <SupportPage />}
{activeNavItem === "notifications" && <NotificationsPage />}
</motion.div>
</div>
</div>
);
}
return (
<div className="min-h-screen flex">
{/* Left Panel - Welcome Section */}
<div className="flex-1 bg-gray-50 flex flex-col items-center justify-center p-12">
<div className="max-w-md text-center">
<div className="mb-8">
<div className="w-full h-64 mb-8 flex items-center justify-center">
<FlightBookingRafiki />
</div>
</div>
<div className="space-y-2">
<p className="text-gray-600 text-lg">Welcome to</p>
<h1 className="text-4xl text-gray-900 font-medium">CityCards</h1>
</div>
</div>
</div>
{/* Right Panel - Login Section */}
<div className="flex-1 bg-white flex items-center justify-center p-12">
<div className="w-full max-w-md">
<AnimatePresence mode="wait">
{!showForgotPassword && !showOTPVerification && !showResetPassword ? (
<motion.div
key="signin"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
>
<div className="mb-8">
<h2 className="text-3xl text-gray-900 font-bold">Sign In to Access Your Account</h2>
</div>
<form className="space-y-6">
{/* Email Field */}
<div className="space-y-2">
<Label htmlFor="email" className="text-base font-medium text-gray-900">Email</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" />
<Input
id="email"
type="email"
placeholder="Enter your email address"
className="pl-10 bg-white border border-gray-200 rounded-lg h-12 font-normal"
/>
</div>
</div>
{/* Password Field */}
<div className="space-y-2">
<Label htmlFor="password" className="text-base font-medium text-gray-900">Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
className="pl-10 pr-10 bg-white border border-gray-200 rounded-lg h-12 font-normal"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
{showPassword ? (
<EyeOff className="h-5 w-5" />
) : (
<Eye className="h-5 w-5" />
)}
</button>
</div>
</div>
{/* Remember Me and Forgot Password */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Checkbox
id="remember"
checked={rememberMe}
onCheckedChange={setRememberMe}
/>
<Label htmlFor="remember" className="text-sm font-normal text-gray-600">
Remember me
</Label>
</div>
<button
type="button"
onClick={() => setShowForgotPassword(true)}
className="text-sm font-normal text-blue-600 hover:text-blue-700"
>
Forgot Password?
</button>
</div>
{/* Sign In Button */}
<Button
type="submit"
onClick={(e) => {
e.preventDefault();
setIsLoggedIn(true);
}}
className="w-full h-12 bg-gray-900 hover:bg-gray-800 text-white rounded-lg font-medium"
>
Sign In
</Button>
</form>
</motion.div>
) : showForgotPassword && !showOTPVerification && !showResetPassword ? (
<motion.div
key="forgot"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="text-center"
>
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-4">Forgot Password</h1>
<p className="text-base font-normal text-gray-600 leading-relaxed">
Forgot your password? Don't worry — just enter your email and we'll help you reset it.
</p>
</div>
<form className="space-y-6">
{/* Email Field */}
<div className="space-y-2 text-left">
<Label htmlFor="forgot-email" className="text-base font-medium text-gray-900">
Email
</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" />
<Input
id="forgot-email"
type="email"
placeholder="Enter your email address"
className="pl-10 bg-gray-50 border border-gray-200 rounded-lg h-12 font-normal"
/>
</div>
</div>
{/* Continue Button */}
<Button
type="submit"
onClick={(e) => {
e.preventDefault();
setShowOTPVerification(true);
}}
className="w-full h-12 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium mt-8"
>
Continue
</Button>
</form>
{/* Back to Sign In */}
<button
type="button"
onClick={() => setShowForgotPassword(false)}
className="text-sm font-normal text-blue-600 hover:text-blue-700 mt-6"
>
Back to Sign In
</button>
</motion.div>
) : showOTPVerification && !showResetPassword ? (
<motion.div
key="otp"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="text-center"
>
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-4">Verify OTP</h1>
<p className="text-base font-normal text-gray-600 leading-relaxed">
We've sent an OTP to your registered email. Please enter it below, or use the reset link included in the email.
</p>
</div>
<form className="space-y-6">
{/* OTP Input */}
<div className="flex justify-center">
<InputOTP
maxLength={4}
value={otpValue}
onChange={(value) => setOtpValue(value)}
className="gap-4"
>
<InputOTPGroup className="gap-4">
<InputOTPSlot
index={0}
className="w-12 h-12 text-lg font-medium border-2 border-gray-200 rounded-lg bg-white focus:border-gray-400 focus:ring-0"
/>
<InputOTPSlot
index={1}
className="w-12 h-12 text-lg font-medium border-2 border-gray-200 rounded-lg bg-white focus:border-gray-400 focus:ring-0"
/>
<InputOTPSlot
index={2}
className="w-12 h-12 text-lg font-medium border-2 border-gray-200 rounded-lg bg-white focus:border-gray-400 focus:ring-0"
/>
<InputOTPSlot
index={3}
className="w-12 h-12 text-lg font-medium border-2 border-gray-200 rounded-lg bg-white focus:border-gray-400 focus:ring-0"
/>
</InputOTPGroup>
</InputOTP>
</div>
{/* Continue Button */}
<Button
type="submit"
onClick={(e) => {
e.preventDefault();
setShowResetPassword(true);
}}
className="w-full h-12 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium mt-8"
>
Continue
</Button>
</form>
{/* Back to Forgot Password */}
<button
type="button"
onClick={() => setShowOTPVerification(false)}
className="text-sm font-normal text-blue-600 hover:text-blue-700 mt-6"
>
Back to Forgot Password
</button>
</motion.div>
) : (
<motion.div
key="reset"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="text-center"
>
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-4">Reset Password</h1>
</div>
<form className="space-y-6">
{/* New Password Field */}
<div className="space-y-2 text-left">
<Label htmlFor="new-password" className="text-base font-medium text-gray-900">
New password
</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" />
<Input
id="new-password"
type={showPassword ? "text" : "password"}
placeholder="Enter your new password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
onFocus={() => setShowPasswordPolicies(true)}
className="pl-10 pr-10 bg-gray-50 border border-gray-200 rounded-lg h-12 font-normal"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
{showPassword ? (
<EyeOff className="h-5 w-5" />
) : (
<Eye className="h-5 w-5" />
)}
</button>
</div>
</div>
{/* Password Policies */}
{showPasswordPolicies && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }}
className="text-left bg-gray-50 p-4 rounded-lg space-y-2"
>
<div className="space-y-1">
<div className={`flex items-center text-sm ${passwordValidation.hasMinLength ? 'text-green-600' : 'text-gray-600'}`}>
<div className={`w-1.5 h-1.5 rounded-full mr-2 ${passwordValidation.hasMinLength ? 'bg-green-600' : 'bg-gray-400'}`}></div>
Contains at least 8 characters
</div>
<div className={`flex items-center text-sm ${passwordValidation.hasUppercase ? 'text-green-600' : 'text-gray-600'}`}>
<div className={`w-1.5 h-1.5 rounded-full mr-2 ${passwordValidation.hasUppercase ? 'bg-green-600' : 'bg-gray-400'}`}></div>
At least one uppercase letter (A-Z)
</div>
<div className={`flex items-center text-sm ${passwordValidation.hasLowercase ? 'text-green-600' : 'text-gray-600'}`}>
<div className={`w-1.5 h-1.5 rounded-full mr-2 ${passwordValidation.hasLowercase ? 'bg-green-600' : 'bg-gray-400'}`}></div>
At least one lowercase letter (a-z)
</div>
<div className={`flex items-center text-sm ${passwordValidation.hasNumber ? 'text-green-600' : 'text-gray-600'}`}>
<div className={`w-1.5 h-1.5 rounded-full mr-2 ${passwordValidation.hasNumber ? 'bg-green-600' : 'bg-gray-400'}`}></div>
At least one number (0-9)
</div>
<div className={`flex items-center text-sm ${passwordValidation.hasSpecialChar ? 'text-green-600' : 'text-gray-600'}`}>
<div className={`w-1.5 h-1.5 rounded-full mr-2 ${passwordValidation.hasSpecialChar ? 'bg-green-600' : 'bg-gray-400'}`}></div>
At least one special character (e.g., !, @, #, $, %, ^, &, *)
</div>
</div>
</motion.div>
)}
{/* Confirm Password Field */}
<div className="space-y-2 text-left">
<Label htmlFor="confirm-password" className="text-base font-medium text-gray-900">
Confirm Password
</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" />
<Input
id="confirm-password"
type={showConfirmPassword ? "text" : "password"}
placeholder="Confirm your new password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="pl-10 pr-10 bg-gray-50 border border-gray-200 rounded-lg h-12 font-normal"
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
{showConfirmPassword ? (
<EyeOff className="h-5 w-5" />
) : (
<Eye className="h-5 w-5" />
)}
</button>
</div>
{confirmPassword && newPassword !== confirmPassword && (
<p className="text-sm text-red-600">Passwords do not match</p>
)}
</div>
{/* Continue Button */}
<Button
type="submit"
disabled={!Object.values(passwordValidation).every(Boolean) || newPassword !== confirmPassword}
onClick={(e) => {
e.preventDefault();
if (Object.values(passwordValidation).every(Boolean) && newPassword === confirmPassword) {
setIsLoggedIn(true);
}
}}
className="w-full h-12 bg-gray-600 hover:bg-gray-700 disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded-lg font-medium mt-8"
>
Continue
</Button>
</form>
{/* Back to OTP */}
<button
type="button"
onClick={() => setShowResetPassword(false)}
className="text-sm font-normal text-blue-600 hover:text-blue-700 mt-6"
>
Back to OTP Verification
</button>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
);
}