new src added

This commit is contained in:
priyanshuvish
2025-10-09 17:02:48 +05:30
parent 309ee03187
commit 4641296623
19 changed files with 1042 additions and 411 deletions

View File

@@ -7,6 +7,8 @@ import { Mail, Lock, Eye, EyeOff, Search, Users, Calendar, TrendingUp, Clock, Us
import { useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import FlightBookingRafiki from "./imports/FlightBookingRafiki";
import cityCardsLogo from './assets/city-logo.png';
import { ImageWithFallback } from "./components/figma/ImageWithFallback";
import Sidebar from "./components/Sidebar";
import Header from "./components/Header";
import Dashboard from "./components/Dashboard";
@@ -31,6 +33,7 @@ export default function App() {
const [confirmPassword, setConfirmPassword] = useState("");
const [showPasswordPolicies, setShowPasswordPolicies] = useState(false);
const [activeNavItem, setActiveNavItem] = useState("dashboard");
const [showNotifications, setShowNotifications] = useState(false);
// Password validation functions
const validatePassword = (password) => {
@@ -49,10 +52,15 @@ export default function App() {
if (isLoggedIn) {
return (
<div className="min-h-screen bg-gray-50">
{/* Main Layout Container - Blurred together when notifications open */}
<div className={`transition-all duration-300 ${
showNotifications ? 'blur-[1px]' : ''
}`}>
{/* Fixed Sidebar */}
<Sidebar
activeItem={activeNavItem}
onItemSelect={setActiveNavItem}
isNotificationsOpen={showNotifications}
/>
{/* Main Content with left margin for sidebar */}
@@ -61,6 +69,10 @@ export default function App() {
<Header
onNavigateToNotifications={() => setActiveNavItem("notifications")}
onNavigateToProfile={() => setActiveNavItem("profile")}
onSignOut={() => setIsLoggedIn(false)}
showNotifications={showNotifications}
onNotificationsChange={setShowNotifications}
renderNotificationsOutside={true}
/>
<motion.div
@@ -86,6 +98,17 @@ export default function App() {
</motion.div>
</div>
</div>
{/* Notifications Panel - Rendered outside of blurred content */}
<Header
onNavigateToNotifications={() => setActiveNavItem("notifications")}
onNavigateToProfile={() => setActiveNavItem("profile")}
onSignOut={() => setIsLoggedIn(false)}
showNotifications={showNotifications}
onNotificationsChange={setShowNotifications}
renderOnlyNotifications={true}
/>
</div>
);
}
@@ -101,7 +124,11 @@ export default function App() {
</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>
<ImageWithFallback
src={cityCardsLogo}
alt="CityCards Logo"
className="h-12 w-auto object-contain mx-auto"
/>
</div>
</div>
</div>
@@ -177,7 +204,7 @@ export default function App() {
<button
type="button"
onClick={() => setShowForgotPassword(true)}
className="text-sm font-normal text-blue-600 hover:text-blue-700"
className="text-sm font-normal text-[#F95F62] hover:text-[#E54B4E]"
>
Forgot Password?
</button>
@@ -190,7 +217,7 @@ export default function App() {
e.preventDefault();
setIsLoggedIn(true);
}}
className="w-full h-12 bg-gray-900 hover:bg-gray-800 text-white rounded-lg font-medium"
className="w-full h-12 bg-[#F95F62] hover:bg-[#E54B4E] text-white rounded-lg font-medium"
>
Sign In
</Button>
@@ -236,7 +263,7 @@ export default function App() {
e.preventDefault();
setShowOTPVerification(true);
}}
className="w-full h-12 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium mt-8"
className="w-full h-12 bg-[#F95F62] hover:bg-[#E54B4E] text-white rounded-lg font-medium mt-8"
>
Continue
</Button>
@@ -246,7 +273,7 @@ export default function App() {
<button
type="button"
onClick={() => setShowForgotPassword(false)}
className="text-sm font-normal text-blue-600 hover:text-blue-700 mt-6"
className="text-sm font-normal text-[#F95F62] hover:text-[#E54B4E] mt-6"
>
Back to Sign In
</button>
@@ -304,7 +331,7 @@ export default function App() {
e.preventDefault();
setShowResetPassword(true);
}}
className="w-full h-12 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium mt-8"
className="w-full h-12 bg-[#F95F62] hover:bg-[#E54B4E] text-white rounded-lg font-medium mt-8"
>
Continue
</Button>
@@ -314,7 +341,7 @@ export default function App() {
<button
type="button"
onClick={() => setShowOTPVerification(false)}
className="text-sm font-normal text-blue-600 hover:text-blue-700 mt-6"
className="text-sm font-normal text-[#F95F62] hover:text-[#E54B4E] mt-6"
>
Back to Forgot Password
</button>
@@ -439,7 +466,7 @@ export default function App() {
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"
className="w-full h-12 bg-[#F95F62] hover:bg-[#E54B4E] disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded-lg font-medium mt-8"
>
Continue
</Button>
@@ -449,7 +476,7 @@ export default function App() {
<button
type="button"
onClick={() => setShowResetPassword(false)}
className="text-sm font-normal text-blue-600 hover:text-blue-700 mt-6"
className="text-sm font-normal text-[#F95F62] hover:text-[#E54B4E] mt-6"
>
Back to OTP Verification
</button>

BIN
src/assets/city-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -0,0 +1,166 @@
import { ArrowLeft } from "lucide-react";
import { motion } from "motion/react";
import { Button } from "./ui/button";
import { Badge } from "./ui/badge";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./ui/table";
interface BookingDetailViewProps {
bookingData: {
fullName: string;
email: string;
cardType: string;
bookingId: string;
date: string;
attendantName: string;
};
onBack: () => void;
}
export default function BookingDetailView({ bookingData, onBack }: BookingDetailViewProps) {
// Mock redemption history data
const redemptionHistory = [
{
date: "2024/9/15",
time: "14:30",
scanned: true
},
{
date: "2024/9/16",
time: "10:15",
scanned: true
},
{
date: "2024/9/17",
time: "15:45",
scanned: false
},
{
date: "2024/9/18",
time: "11:20",
scanned: true
}
];
return (
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4, ease: "easeInOut" }}
className="min-h-screen bg-gray-50 p-6"
>
<div className="max-w-7xl mx-auto">
{/* Header with Back Button */}
<div className="mb-8">
<Button
variant="ghost"
onClick={onBack}
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 mb-4 p-0"
>
<ArrowLeft className="h-4 w-4" />
Back
</Button>
<h1 className="text-3xl font-bold text-gray-900">Detail View</h1>
</div>
<div className="space-y-6">
{/* Customer Information */}
<div className="bg-white rounded-lg p-6 shadow-sm border border-gray-200">
<h2 className="text-lg font-medium text-gray-900 mb-4">Customer Information</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<p className="text-sm text-gray-600 mb-1">Email</p>
<p className="text-gray-900">{bookingData.email}</p>
</div>
<div>
<p className="text-sm text-gray-600 mb-1">Name</p>
<p className="text-gray-900">{bookingData.fullName}</p>
</div>
<div>
<p className="text-sm text-gray-600 mb-1">Phone</p>
<p className="text-gray-900">(+971) 050 421 4456</p>
</div>
</div>
</div>
{/* City Card Information */}
<div className="bg-white rounded-lg p-6 shadow-sm border border-gray-200">
<h2 className="text-lg font-medium text-gray-900 mb-4">City Card Information</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<p className="text-sm text-gray-600 mb-1">Card Type</p>
<p className="text-gray-900">{bookingData.cardType}</p>
</div>
<div>
<p className="text-sm text-gray-600 mb-1">Validity</p>
<p className="text-gray-900">Valid</p>
</div>
</div>
</div>
{/* Booking Information */}
<div className="bg-white rounded-lg p-6 shadow-sm border border-gray-200">
<h2 className="text-lg font-medium text-gray-900 mb-4">Booking Information</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<p className="text-sm text-gray-600 mb-1">Attraction Booked</p>
<p className="text-gray-900">The Enchanted Garden</p>
</div>
<div>
<p className="text-sm text-gray-600 mb-1">Booking Date</p>
<p className="text-gray-900">{bookingData.date}</p>
</div>
<div>
<p className="text-sm text-gray-600 mb-1">Timeslot</p>
<p className="text-gray-900">10:30AM - 3:30PM</p>
</div>
</div>
</div>
{/* Redemption History */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
<div className="p-6 border-b border-gray-200">
<h2 className="text-lg font-medium text-gray-900">Redemption History</h2>
</div>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow className="border-b border-gray-200 bg-gray-50">
<TableHead className="font-medium text-gray-900 py-4 text-center">Date</TableHead>
<TableHead className="font-medium text-gray-900 text-center">Time</TableHead>
<TableHead className="font-medium text-gray-900 text-center">Scanned</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{redemptionHistory.map((item, index) => (
<motion.tr
key={index}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }}
className="border-b border-gray-100 hover:bg-gray-50"
>
<TableCell className="text-gray-900 py-4 text-center">{item.date}</TableCell>
<TableCell className="text-gray-600 text-center">{item.time}</TableCell>
<TableCell className="text-center">
{item.scanned ? (
<Badge className="bg-green-100 text-green-800 hover:bg-green-100">
Scanned
</Badge>
) : (
<Badge className="bg-gray-100 text-gray-800 hover:bg-gray-100">
Not Scanned
</Badge>
)}
</TableCell>
</motion.tr>
))}
</TableBody>
</Table>
</div>
</div>
</div>
</div>
</motion.div>
);
}

View File

@@ -2,6 +2,7 @@ import { useState } from "react";
import { motion } from "motion/react";
import { Search, Download, Filter, MoreHorizontal, Eye, Trash2 } from "lucide-react";
import CalendarView from "./CalendarView";
import BookingDetailView from "./BookingDetailView";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import {
@@ -26,6 +27,8 @@ interface BookingManagementPageProps {
export default function BookingManagementPage({ activeView, onNavigateToRecurringBlock }: BookingManagementPageProps) {
const [searchQuery, setSearchQuery] = useState("");
const [selectedBooking, setSelectedBooking] = useState<any>(null);
const [showDetailView, setShowDetailView] = useState(false);
// Sample booking data
const bookings = [
@@ -137,6 +140,23 @@ export default function BookingManagementPage({ activeView, onNavigateToRecurrin
booking.attendantName.toLowerCase().includes(searchQuery.toLowerCase())
);
// Handle view details click
const handleViewDetails = (booking: any) => {
setSelectedBooking(booking);
setShowDetailView(true);
};
// Handle back from detail view
const handleBackFromDetail = () => {
setShowDetailView(false);
setSelectedBooking(null);
};
// Show detail view if a booking is selected
if (showDetailView && selectedBooking) {
return <BookingDetailView bookingData={selectedBooking} onBack={handleBackFromDetail} />;
}
if (activeView === "booking-calendar") {
return (
<div className="min-h-screen bg-gray-50 p-6">
@@ -188,7 +208,7 @@ export default function BookingManagementPage({ activeView, onNavigateToRecurrin
</div>
<div className="flex items-center gap-3">
<Button className="flex items-center gap-2 bg-gray-900 hover:bg-gray-800 text-white">
<Button className="flex items-center gap-2 bg-[#F95F62] hover:bg-[#E54B4E] text-white">
<Download className="h-4 w-4" />
Export data
</Button>
@@ -231,7 +251,10 @@ export default function BookingManagementPage({ activeView, onNavigateToRecurrin
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem className="flex items-center gap-2">
<DropdownMenuItem
className="flex items-center gap-2"
onClick={() => handleViewDetails(booking)}
>
<Eye className="h-4 w-4" />
View Details
</DropdownMenuItem>
@@ -257,7 +280,7 @@ export default function BookingManagementPage({ activeView, onNavigateToRecurrin
<Button variant="outline" size="sm" disabled>
Previous
</Button>
<Button variant="outline" size="sm" className="bg-gray-900 text-white">
<Button variant="outline" size="sm" className="bg-[#F95F62] text-white">
1
</Button>
<Button variant="outline" size="sm" disabled>

View File

@@ -212,7 +212,7 @@ export default function CalendarView({ onNavigateToRecurringBlock }: CalendarVie
{/* Add Recurring Block Button */}
<Button
className="bg-gray-900 hover:bg-gray-800 text-white px-4 py-2"
className="bg-[#F95F62] hover:bg-[#E54B4E] text-white px-4 py-2"
onClick={onNavigateToRecurringBlock}
>
Add Recurring Block

View File

@@ -1,12 +1,27 @@
import { Search, UserPlus, Grid3X3, Users, Clock, TrendingUp, Calendar, ChevronDown, CreditCard } from "lucide-react";
import { Search, UserPlus, Grid3X3, Users, Clock, TrendingUp, Calendar, ChevronDown, CreditCard, Download, FileText, FileSpreadsheet, X } from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
import { useState } from "react";
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
import { Input } from "./ui/input";
import { Button } from "./ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { Calendar as CalendarComponent } from "./ui/calendar";
import RedemptionModal from "./RedemptionModal";
export default function Dashboard() {
const [isRedemptionModalOpen, setIsRedemptionModalOpen] = useState(false);
const [showExportPanel, setShowExportPanel] = useState(false);
const [startDate, setStartDate] = useState<Date>();
const [endDate, setEndDate] = useState<Date>();
const [exportFormat, setExportFormat] = useState<'pdf' | 'excel' | null>(null);
const handleExport = (format: 'pdf' | 'excel') => {
// Mock export functionality
console.log(`Exporting report from ${startDate} to ${endDate} as ${format}`);
alert(`Report will be downloaded as ${format.toUpperCase()}\nDate Range: ${startDate?.toLocaleDateString()} - ${endDate?.toLocaleDateString()}`);
setShowExportPanel(false);
};
return (
<motion.div
className="min-h-screen bg-gray-50 p-6"
@@ -17,13 +32,264 @@ export default function Dashboard() {
<div className="max-w-7xl mx-auto">
{/* Page Title Section */}
<motion.div
className="mb-8"
className="mb-8 flex items-start justify-between"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-1">Dashboard</h1>
<p className="text-gray-600">Welcome back, Kassandra!</p>
</div>
{/* Export Reports Button */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
>
<Button
onClick={() => setShowExportPanel(!showExportPanel)}
className="bg-white border border-gray-200 text-gray-900 hover:bg-gray-50 hover:border-gray-300 shadow-sm"
>
<Download className="h-4 w-4 mr-2" />
Download Report
</Button>
</motion.div>
</motion.div>
{/* Export Panel */}
<AnimatePresence>
{showExportPanel && (
<motion.div
initial={{ opacity: 0, height: 0, marginBottom: 0 }}
animate={{ opacity: 1, height: "auto", marginBottom: 32 }}
exit={{ opacity: 0, height: 0, marginBottom: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="overflow-hidden"
>
<div className="bg-white rounded-lg border border-gray-200 shadow-sm p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-medium text-gray-900">Export Dashboard Report</h3>
<button
onClick={() => setShowExportPanel(false)}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
<X className="h-5 w-5" />
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* Start Date */}
<div className="space-y-2">
<label className="text-base font-medium text-gray-900">Start Date</label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-start text-left font-normal bg-gray-50 border-gray-200 hover:bg-gray-100"
>
<Calendar className="mr-2 h-4 w-4" />
{startDate ? startDate.toLocaleDateString() : "Select start date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<CalendarComponent
mode="single"
selected={startDate}
onSelect={setStartDate}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
{/* End Date */}
<div className="space-y-2">
<label className="text-base font-medium text-gray-900">End Date</label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-start text-left font-normal bg-gray-50 border-gray-200 hover:bg-gray-100"
>
<Calendar className="mr-2 h-4 w-4" />
{endDate ? endDate.toLocaleDateString() : "Select end date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<CalendarComponent
mode="single"
selected={endDate}
onSelect={setEndDate}
initialFocus
disabled={(date) => startDate ? date < startDate : false}
/>
</PopoverContent>
</Popover>
</div>
</div>
{/* Export Format Options */}
<div className="space-y-3 mb-6">
<label className="text-base font-medium text-gray-900">Export Format</label>
<div className="grid grid-cols-2 gap-4">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setExportFormat('pdf')}
className={`flex items-center justify-center gap-3 p-4 rounded-lg border-2 transition-all ${
exportFormat === 'pdf'
? 'border-[#F95F62] bg-[#F95F62]/5'
: 'border-gray-200 bg-white hover:border-gray-300'
}`}
>
<FileText className={`h-6 w-6 ${exportFormat === 'pdf' ? 'text-[#F95F62]' : 'text-gray-600'}`} />
<div className="text-left">
<p className="font-medium text-gray-900">PDF</p>
<p className="text-sm text-gray-600">Portable Document</p>
</div>
</motion.button>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setExportFormat('excel')}
className={`flex items-center justify-center gap-3 p-4 rounded-lg border-2 transition-all ${
exportFormat === 'excel'
? 'border-[#F95F62] bg-[#F95F62]/5'
: 'border-gray-200 bg-white hover:border-gray-300'
}`}
>
<FileSpreadsheet className={`h-6 w-6 ${exportFormat === 'excel' ? 'text-[#F95F62]' : 'text-gray-600'}`} />
<div className="text-left">
<p className="font-medium text-gray-900">Excel</p>
<p className="text-sm text-gray-600">Spreadsheet</p>
</div>
</motion.button>
</div>
</div>
{/* Export Button */}
<Button
onClick={() => exportFormat && handleExport(exportFormat)}
disabled={!startDate || !endDate || !exportFormat}
className="w-full h-12 bg-[#F95F62] hover:bg-[#E54B4E] disabled:bg-gray-300 disabled:cursor-not-allowed text-white"
>
<Download className="h-4 w-4 mr-2" />
Export Report
</Button>
</div>
</motion.div>
)}
</AnimatePresence>
{/* Upcoming Bookings */}
<motion.div
className="mb-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1, ease: "easeOut" }}
>
<motion.h2
className="text-lg font-medium text-gray-900 mb-4"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2, duration: 0.4 }}
>
Upcoming Bookings
</motion.h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ delay: 0.3, duration: 0.5, ease: "easeOut" }}
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="bg-gray-100 rounded-lg p-6 hover:bg-gray-200 transition-all cursor-pointer group"
>
<motion.div
className="flex items-center gap-3 mb-2"
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
>
<motion.div
whileHover={{ scale: 1.2, rotate: 10 }}
transition={{ duration: 0.3 }}
>
<Calendar className="h-5 w-5 text-gray-600 group-hover:text-[#F95F62] transition-colors" />
</motion.div>
<span className="font-medium text-gray-900">July 19, 2024</span>
</motion.div>
<motion.p
className="text-gray-600"
whileHover={{ x: 4 }}
transition={{ duration: 0.2, delay: 0.05 }}
>
10:30AM - 3:30PM
</motion.p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ delay: 0.4, duration: 0.5, ease: "easeOut" }}
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="bg-gray-100 rounded-lg p-6 hover:bg-gray-200 transition-all cursor-pointer group"
>
<motion.div
className="flex items-center gap-3 mb-2"
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
>
<motion.div
whileHover={{ scale: 1.2, rotate: 10 }}
transition={{ duration: 0.3 }}
>
<Calendar className="h-5 w-5 text-gray-600 group-hover:text-green-600 transition-colors" />
</motion.div>
<span className="font-medium text-gray-900">July 20, 2024</span>
</motion.div>
<motion.p
className="text-gray-600"
whileHover={{ x: 4 }}
transition={{ duration: 0.2, delay: 0.05 }}
>
10:30AM - 3:30PM
</motion.p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ delay: 0.5, duration: 0.5, ease: "easeOut" }}
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="bg-gray-100 rounded-lg p-6 hover:bg-gray-200 transition-all cursor-pointer group"
>
<motion.div
className="flex items-center gap-3 mb-2"
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
>
<motion.div
whileHover={{ scale: 1.2, rotate: 10 }}
transition={{ duration: 0.3 }}
>
<Calendar className="h-5 w-5 text-gray-600 group-hover:text-purple-600 transition-colors" />
</motion.div>
<span className="font-medium text-gray-900">July 21, 2024</span>
</motion.div>
<motion.p
className="text-gray-600"
whileHover={{ x: 4 }}
transition={{ duration: 0.2, delay: 0.05 }}
>
10:30AM - 3:30PM
</motion.p>
</motion.div>
</div>
</motion.div>
{/* Quick Links Section */}
@@ -31,21 +297,21 @@ export default function Dashboard() {
className="mb-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1, ease: "easeOut" }}
transition={{ duration: 0.6, delay: 0.6, ease: "easeOut" }}
>
<h2 className="text-lg font-medium text-gray-900 mb-4">Quick Links</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 0.2, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 0.7, ease: "easeOut" }}
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 hover:shadow-lg transition-all cursor-pointer group"
>
<div className="flex items-center gap-4">
<motion.div
className="bg-blue-50 p-3 rounded-lg"
className="bg-[#F95F62]/10 p-3 rounded-lg"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ duration: 0.3 }}
>
@@ -53,14 +319,14 @@ export default function Dashboard() {
whileHover={{ scale: 1.1, rotate: -5 }}
transition={{ duration: 0.2 }}
>
<Search className="h-6 w-6 text-blue-600" />
<Search className="h-6 w-6 text-[#F95F62]" />
</motion.div>
</motion.div>
<motion.div
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
>
<h3 className="font-medium text-gray-900 group-hover:text-blue-600 transition-colors">View Redemption Logs</h3>
<h3 className="font-medium text-gray-900 group-hover:text-[#F95F62] transition-colors">View Redemption Logs</h3>
<p className="text-sm text-gray-600">Check recent activity</p>
</motion.div>
</div>
@@ -69,7 +335,7 @@ export default function Dashboard() {
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 0.3, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 0.8, ease: "easeOut" }}
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 hover:shadow-lg transition-all cursor-pointer group"
@@ -100,7 +366,7 @@ export default function Dashboard() {
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 0.4, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 0.9, ease: "easeOut" }}
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => setIsRedemptionModalOpen(true)}
@@ -136,13 +402,13 @@ export default function Dashboard() {
className="mb-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5, ease: "easeOut" }}
transition={{ duration: 0.6, delay: 1.0, ease: "easeOut" }}
>
<motion.div
className="flex items-center justify-between mb-4"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6, duration: 0.4 }}
transition={{ delay: 1.1, duration: 0.4 }}
>
<h2 className="text-lg font-medium text-gray-900">Summary</h2>
<motion.span
@@ -159,7 +425,7 @@ export default function Dashboard() {
className="text-base font-medium text-gray-900 mb-4"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.7, duration: 0.4 }}
transition={{ delay: 1.2, duration: 0.4 }}
>
Total Redemptions
</motion.h3>
@@ -168,7 +434,7 @@ export default function Dashboard() {
className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 hover:shadow-md transition-all group cursor-pointer"
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 0.8, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 1.3, ease: "easeOut" }}
whileHover={{ y: -2, scale: 1.02 }}
>
<div className="flex items-center justify-between mb-4">
@@ -177,7 +443,7 @@ export default function Dashboard() {
whileHover={{ scale: 1.1, rotate: 10 }}
transition={{ duration: 0.3 }}
>
<CreditCard className="h-8 w-8 text-gray-400 group-hover:text-blue-500 transition-colors" />
<CreditCard className="h-8 w-8 text-gray-400 group-hover:text-[#F95F62] transition-colors" />
</motion.div>
</div>
<motion.div
@@ -202,7 +468,7 @@ export default function Dashboard() {
className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 hover:shadow-md transition-all group cursor-pointer"
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 0.9, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 1.4, ease: "easeOut" }}
whileHover={{ y: -2, scale: 1.02 }}
>
<div className="flex items-center justify-between mb-4">
@@ -236,7 +502,7 @@ export default function Dashboard() {
className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 hover:shadow-md transition-all group cursor-pointer"
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 1.0, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 1.5, ease: "easeOut" }}
whileHover={{ y: -2, scale: 1.02 }}
>
<div className="flex items-center justify-between mb-4">
@@ -274,13 +540,13 @@ export default function Dashboard() {
className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 1.1, ease: "easeOut" }}
transition={{ duration: 0.6, delay: 1.6, ease: "easeOut" }}
>
<motion.div
className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 hover:shadow-md transition-all group cursor-pointer"
initial={{ opacity: 0, x: -20, scale: 0.95 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 1.2, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 1.7, ease: "easeOut" }}
whileHover={{ y: -2, scale: 1.02 }}
>
<div className="flex items-center justify-between mb-4">
@@ -289,7 +555,7 @@ export default function Dashboard() {
whileHover={{ scale: 1.15, rotate: 15 }}
transition={{ duration: 0.3 }}
>
<Users className="h-8 w-8 text-gray-400 group-hover:text-blue-500 transition-colors" />
<Users className="h-8 w-8 text-gray-400 group-hover:text-[#F95F62] transition-colors" />
</motion.div>
</div>
<motion.div
@@ -314,7 +580,7 @@ export default function Dashboard() {
className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 hover:shadow-md transition-all group cursor-pointer"
initial={{ opacity: 0, x: 20, scale: 0.95 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 1.3, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 1.8, ease: "easeOut" }}
whileHover={{ y: -2, scale: 1.02 }}
>
<div className="flex items-center justify-between mb-4">
@@ -371,13 +637,13 @@ export default function Dashboard() {
className="mb-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 1.4, ease: "easeOut" }}
transition={{ duration: 0.6, delay: 1.9, ease: "easeOut" }}
>
<motion.h2
className="text-lg font-medium text-gray-900 mb-4"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 1.5, duration: 0.4 }}
transition={{ delay: 2.0, duration: 0.4 }}
>
Graphs
</motion.h2>
@@ -387,7 +653,7 @@ export default function Dashboard() {
className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 hover:shadow-md transition-all group"
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 1.6, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 2.1, ease: "easeOut" }}
whileHover={{ y: -2, scale: 1.01 }}
>
<motion.div
@@ -420,7 +686,7 @@ export default function Dashboard() {
<motion.path
d="M 20 80 Q 50 60 80 70 T 140 50 T 200 60 T 260 40"
fill="none"
stroke="#ec4899"
stroke="#F95F62"
strokeWidth="2"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
@@ -434,7 +700,7 @@ export default function Dashboard() {
cx={point.x}
cy={point.y}
r="3"
fill="#ec4899"
fill="#F95F62"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: i * 0.1 + 1, duration: 0.3 }}
@@ -460,7 +726,7 @@ export default function Dashboard() {
className="bg-white rounded-lg p-6 shadow-sm border border-gray-200 hover:shadow-md transition-all group"
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.5, delay: 1.7, ease: "easeOut" }}
transition={{ duration: 0.5, delay: 2.2, ease: "easeOut" }}
whileHover={{ y: -2, scale: 1.01 }}
>
<motion.div
@@ -484,13 +750,13 @@ export default function Dashboard() {
{[20, 35, 45, 55, 70, 85, 60, 40].map((height, i) => (
<motion.div
key={i}
className="bg-pink-200 rounded-t flex-1 hover:bg-pink-300 transition-colors cursor-pointer"
className="bg-[#F95F62]/20 rounded-t flex-1 hover:bg-[#F95F62]/30 transition-colors cursor-pointer"
style={{ height: `${height}%` }}
initial={{ height: 0, scaleY: 0 }}
animate={{ height: `${height}%`, scaleY: 1 }}
whileHover={{ scaleY: 1.05, scaleX: 1.1 }}
transition={{
delay: i * 0.1 + 1.8,
delay: i * 0.1 + 2.3,
duration: 0.6,
whileHover: { duration: 0.2 }
}}
@@ -512,113 +778,6 @@ export default function Dashboard() {
</motion.div>
</div>
</motion.div>
{/* Upcoming Bookings */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 2.5, ease: "easeOut" }}
>
<motion.h2
className="text-lg font-medium text-gray-900 mb-4"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 2.6, duration: 0.4 }}
>
Upcoming Bookings
</motion.h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ delay: 2.7, duration: 0.5, ease: "easeOut" }}
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="bg-gray-100 rounded-lg p-6 hover:bg-gray-200 transition-all cursor-pointer group"
>
<motion.div
className="flex items-center gap-3 mb-2"
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
>
<motion.div
whileHover={{ scale: 1.2, rotate: 10 }}
transition={{ duration: 0.3 }}
>
<Calendar className="h-5 w-5 text-gray-600 group-hover:text-blue-600 transition-colors" />
</motion.div>
<span className="font-medium text-gray-900">July 19, 2024</span>
</motion.div>
<motion.p
className="text-gray-600"
whileHover={{ x: 4 }}
transition={{ duration: 0.2, delay: 0.05 }}
>
10:30AM - 3:30PM
</motion.p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ delay: 2.8, duration: 0.5, ease: "easeOut" }}
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="bg-gray-100 rounded-lg p-6 hover:bg-gray-200 transition-all cursor-pointer group"
>
<motion.div
className="flex items-center gap-3 mb-2"
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
>
<motion.div
whileHover={{ scale: 1.2, rotate: 10 }}
transition={{ duration: 0.3 }}
>
<Calendar className="h-5 w-5 text-gray-600 group-hover:text-green-600 transition-colors" />
</motion.div>
<span className="font-medium text-gray-900">July 20, 2024</span>
</motion.div>
<motion.p
className="text-gray-600"
whileHover={{ x: 4 }}
transition={{ duration: 0.2, delay: 0.05 }}
>
10:30AM - 3:30PM
</motion.p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ delay: 2.9, duration: 0.5, ease: "easeOut" }}
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="bg-gray-100 rounded-lg p-6 hover:bg-gray-200 transition-all cursor-pointer group"
>
<motion.div
className="flex items-center gap-3 mb-2"
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
>
<motion.div
whileHover={{ scale: 1.2, rotate: 10 }}
transition={{ duration: 0.3 }}
>
<Calendar className="h-5 w-5 text-gray-600 group-hover:text-purple-600 transition-colors" />
</motion.div>
<span className="font-medium text-gray-900">July 21, 2024</span>
</motion.div>
<motion.p
className="text-gray-600"
whileHover={{ x: 4 }}
transition={{ duration: 0.2, delay: 0.05 }}
>
10:30AM - 3:30PM
</motion.p>
</motion.div>
</div>
</motion.div>
</div>
{/* Redemption Modal */}

View File

@@ -8,10 +8,14 @@ import { Badge } from "./ui/badge";
interface HeaderProps {
onNavigateToNotifications?: () => void;
onNavigateToProfile?: () => void;
onSignOut?: () => void;
showNotifications?: boolean;
onNotificationsChange?: (show: boolean) => void;
renderNotificationsOutside?: boolean;
renderOnlyNotifications?: boolean;
}
export default function Header({ onNavigateToNotifications, onNavigateToProfile }: HeaderProps) {
const [showNotifications, setShowNotifications] = useState(false);
export default function Header({ onNavigateToNotifications, onNavigateToProfile, onSignOut, showNotifications = false, onNotificationsChange, renderNotificationsOutside = false, renderOnlyNotifications = false }: HeaderProps) {
const [showProfileMenu, setShowProfileMenu] = useState(false);
const notifications = [
@@ -40,43 +44,25 @@ export default function Header({ onNavigateToNotifications, onNavigateToProfile
const unreadCount = notifications.filter(n => n.unread).length;
// If renderOnlyNotifications is true, only render the notifications panel and backdrop
if (renderOnlyNotifications) {
return (
<div className="bg-white border-b border-gray-200 px-6 py-4">
<div className="max-w-7xl mx-auto flex items-center justify-end gap-4">
{/* Notifications */}
<div className="relative">
<Button
variant="ghost"
size="sm"
onClick={() => setShowNotifications(!showNotifications)}
className="relative p-2 hover:bg-gray-100 rounded-lg"
>
<Bell className="h-5 w-5 text-gray-600" />
{unreadCount > 0 && (
<Badge
variant="destructive"
className="absolute -top-1 -right-1 h-5 w-5 rounded-full p-0 flex items-center justify-center text-xs"
>
{unreadCount}
</Badge>
)}
</Button>
{/* Notifications Dropdown */}
<>
{/* Notifications Full Height Panel */}
<AnimatePresence>
{showNotifications && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.2 }}
className="absolute right-0 top-full mt-2 w-80 bg-white border border-gray-200 rounded-lg shadow-lg z-50"
initial={{ opacity: 0, x: 400 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 400 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="fixed right-0 top-0 h-full w-96 bg-white border-l border-gray-200 shadow-xl z-50"
>
<div className="p-4 border-b border-gray-100">
<div className="flex items-center justify-between">
<h3 className="font-medium text-gray-900">Notifications</h3>
{unreadCount > 0 && (
<span className="text-sm text-blue-600 hover:text-blue-700 cursor-pointer">
<span className="text-sm text-[#F95F62] hover:text-[#E54B4E] cursor-pointer">
Mark all as read
</span>
)}
@@ -87,12 +73,12 @@ export default function Header({ onNavigateToNotifications, onNavigateToProfile
<div
key={notification.id}
className={`p-4 border-b border-gray-50 hover:bg-gray-50 cursor-pointer ${
notification.unread ? "bg-blue-50" : ""
notification.unread ? "bg-[#F95F62]/10" : ""
}`}
>
<div className="flex items-start gap-3">
<div className={`w-2 h-2 rounded-full mt-2 ${
notification.unread ? "bg-blue-600" : "bg-transparent"
notification.unread ? "bg-[#F95F62]" : "bg-transparent"
}`}></div>
<div className="flex-1">
<h4 className="font-medium text-gray-900 text-sm">
@@ -113,9 +99,9 @@ export default function Header({ onNavigateToNotifications, onNavigateToProfile
<Button
variant="ghost"
size="sm"
className="text-blue-600 hover:text-blue-700"
className="text-[#F95F62] hover:text-[#E54B4E]"
onClick={() => {
setShowNotifications(false);
onNotificationsChange?.(false);
onNavigateToNotifications?.();
}}
>
@@ -129,10 +115,112 @@ export default function Header({ onNavigateToNotifications, onNavigateToProfile
{/* Notifications Backdrop */}
{showNotifications && (
<div
className="fixed inset-0 z-40"
onClick={() => setShowNotifications(false)}
className="fixed inset-0 bg-black/20 z-40"
onClick={() => onNotificationsChange?.(false)}
/>
)}
</>
);
}
return (
<div className="bg-white border-b border-gray-200 px-6 py-4">
<div className="max-w-7xl mx-auto flex items-center justify-end gap-4">
{/* Notifications */}
<div className="relative">
<Button
variant="ghost"
size="sm"
onClick={() => onNotificationsChange?.(!showNotifications)}
className="relative p-2 hover:bg-gray-100 rounded-lg"
>
<Bell className="h-5 w-5 text-gray-600" />
{unreadCount > 0 && (
<Badge
variant="destructive"
className="absolute -top-1 -right-1 h-5 w-5 rounded-full p-0 flex items-center justify-center text-xs"
>
{unreadCount}
</Badge>
)}
</Button>
{/* Only render notifications panel here if not rendering outside */}
{!renderNotificationsOutside && (
<>
{/* Notifications Full Height Panel */}
<AnimatePresence>
{showNotifications && (
<motion.div
initial={{ opacity: 0, x: 400 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 400 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="fixed right-0 top-0 h-full w-96 bg-white border-l border-gray-200 shadow-xl z-50"
>
<div className="p-4 border-b border-gray-100">
<div className="flex items-center justify-between">
<h3 className="font-medium text-gray-900">Notifications</h3>
{unreadCount > 0 && (
<span className="text-sm text-[#F95F62] hover:text-[#E54B4E] cursor-pointer">
Mark all as read
</span>
)}
</div>
</div>
<div className="max-h-96 overflow-y-auto">
{notifications.map((notification) => (
<div
key={notification.id}
className={`p-4 border-b border-gray-50 hover:bg-gray-50 cursor-pointer ${
notification.unread ? "bg-[#F95F62]/10" : ""
}`}
>
<div className="flex items-start gap-3">
<div className={`w-2 h-2 rounded-full mt-2 ${
notification.unread ? "bg-[#F95F62]" : "bg-transparent"
}`}></div>
<div className="flex-1">
<h4 className="font-medium text-gray-900 text-sm">
{notification.title}
</h4>
<p className="text-sm text-gray-600 mt-1">
{notification.message}
</p>
<span className="text-xs text-gray-500 mt-1">
{notification.time}
</span>
</div>
</div>
</div>
))}
</div>
<div className="p-4 text-center border-t border-gray-100">
<Button
variant="ghost"
size="sm"
className="text-[#F95F62] hover:text-[#E54B4E]"
onClick={() => {
onNotificationsChange?.(false);
onNavigateToNotifications?.();
}}
>
View all notifications
</Button>
</div>
</motion.div>
)}
</AnimatePresence>
{/* Notifications Backdrop */}
{showNotifications && (
<div
className="fixed inset-0 bg-black/20 z-40"
onClick={() => onNotificationsChange?.(false)}
/>
)}
</>
)}
</div>
{/* Profile Menu */}
@@ -179,6 +267,10 @@ export default function Header({ onNavigateToNotifications, onNavigateToProfile
<Button
variant="ghost"
className="w-full justify-start gap-2 px-3 py-2 hover:bg-gray-100 text-red-600 hover:text-red-700"
onClick={() => {
setShowProfileMenu(false);
onSignOut?.();
}}
>
<LogOut className="h-4 w-4" />
<span>Sign out</span>

View File

@@ -95,7 +95,7 @@ export default function ProfilePage() {
onClick={() => setActiveTab(tab.id)}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all duration-200 ${
activeTab === tab.id
? 'bg-blue-50 text-blue-700 border border-blue-200'
? 'bg-[#F95F62]/10 text-[#F95F62] border border-[#F95F62]/20'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
}`}
>

View File

@@ -135,7 +135,7 @@ export default function RecurringBlockPage({ onNavigateBack }: RecurringBlockPag
onClick={() => toggleDay(day)}
className={`w-full text-left p-3 rounded-lg transition-colors ${
selectedDays.includes(day)
? "bg-gray-100 text-gray-900"
? "bg-[#F95F62]/10 text-[#F95F62] border border-[#F95F62]/30"
: "hover:bg-gray-50 text-gray-700"
}`}
>
@@ -199,7 +199,7 @@ export default function RecurringBlockPage({ onNavigateBack }: RecurringBlockPag
>
<div className="flex items-center justify-center w-4 h-4 border border-gray-300 rounded bg-white">
{selectedAttractions.includes(attraction) && (
<Check className="h-3 w-3 text-blue-600" />
<Check className="h-3 w-3 text-[#F95F62]" />
)}
</div>
<span className="text-sm text-gray-700 flex-1">{attraction}</span>
@@ -227,12 +227,12 @@ export default function RecurringBlockPage({ onNavigateBack }: RecurringBlockPag
{selectedAttractions.map((attraction) => (
<div
key={attraction}
className="inline-flex items-center gap-2 bg-blue-50 text-blue-700 px-3 py-1.5 rounded-full text-sm"
className="inline-flex items-center gap-2 bg-[#F95F62]/10 text-[#F95F62] px-3 py-1.5 rounded-full text-sm"
>
<span className="truncate max-w-48">{attraction}</span>
<button
onClick={() => toggleAttraction(attraction)}
className="hover:bg-blue-100 rounded-full p-0.5 transition-colors"
className="hover:bg-[#F95F62]/20 rounded-full p-0.5 transition-colors"
>
<ChevronUp className="h-3 w-3 rotate-45" />
</button>
@@ -264,7 +264,7 @@ export default function RecurringBlockPage({ onNavigateBack }: RecurringBlockPag
<span className="text-lg font-medium text-gray-900">{day}</span>
<div className="flex items-center gap-3">
{expandedDays.includes(day) && (
<span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded-full">
<span className="text-sm text-[#F95F62] bg-[#F95F62]/10 px-2 py-1 rounded-full border border-[#F95F62]/30">
2 slots selected
</span>
)}
@@ -300,7 +300,7 @@ export default function RecurringBlockPage({ onNavigateBack }: RecurringBlockPag
variant={index < 2 ? "default" : "outline"}
className={`h-11 justify-start text-sm font-medium transition-all ${
index < 2
? "bg-blue-600 hover:bg-blue-700 text-white shadow-sm"
? "bg-[#F95F62] hover:bg-[#E54B4E] text-white shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-50 border-gray-200"
}`}
>
@@ -319,46 +319,46 @@ export default function RecurringBlockPage({ onNavigateBack }: RecurringBlockPag
<div>
<h3 className="text-base font-medium text-gray-900 mb-4">Selected Slots Configuration</h3>
<div className="space-y-4">
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="bg-[#F95F62]/10 border border-[#F95F62]/30 rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-blue-600 rounded-full"></div>
<span className="font-medium text-blue-900">10:00AM - 1:30PM</span>
<div className="w-3 h-3 bg-[#F95F62] rounded-full"></div>
<span className="font-medium text-[#C94145]">10:00AM - 1:30PM</span>
</div>
<Button variant="ghost" size="sm" className="text-blue-600 hover:text-blue-700 hover:bg-blue-100 h-8 px-2">
<Button variant="ghost" size="sm" className="text-[#F95F62] hover:text-[#E54B4E] hover:bg-[#F95F62]/20 h-8 px-2">
Remove
</Button>
</div>
<div className="flex items-center gap-3">
<label className="text-sm font-medium text-blue-900">Capacity:</label>
<label className="text-sm font-medium text-[#C94145]">Capacity:</label>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" className="h-8 w-8 p-0 border-blue-300 text-blue-600 hover:bg-blue-100">-</Button>
<div className="w-16 h-10 bg-white border border-blue-300 rounded-md flex items-center justify-center">
<span className="font-medium text-blue-900">35</span>
<Button variant="outline" size="sm" className="h-8 w-8 p-0 border-[#F95F62]/50 text-[#F95F62] hover:bg-[#F95F62]/20">-</Button>
<div className="w-16 h-10 bg-white border border-[#F95F62]/50 rounded-md flex items-center justify-center">
<span className="font-medium text-[#C94145]">35</span>
</div>
<Button variant="outline" size="sm" className="h-8 w-8 p-0 border-blue-300 text-blue-600 hover:bg-blue-100">+</Button>
<Button variant="outline" size="sm" className="h-8 w-8 p-0 border-[#F95F62]/50 text-[#F95F62] hover:bg-[#F95F62]/20">+</Button>
</div>
</div>
</div>
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
<div className="bg-[#F95F62]/10 border border-[#F95F62]/30 rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-green-600 rounded-full"></div>
<span className="font-medium text-green-900">1:00PM - 3:30PM</span>
<div className="w-3 h-3 bg-[#F95F62] rounded-full"></div>
<span className="font-medium text-[#C94145]">1:00PM - 3:30PM</span>
</div>
<Button variant="ghost" size="sm" className="text-green-600 hover:text-green-700 hover:bg-green-100 h-8 px-2">
<Button variant="ghost" size="sm" className="text-[#F95F62] hover:text-[#E54B4E] hover:bg-[#F95F62]/20 h-8 px-2">
Remove
</Button>
</div>
<div className="flex items-center gap-3">
<label className="text-sm font-medium text-green-900">Capacity:</label>
<label className="text-sm font-medium text-[#C94145]">Capacity:</label>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" className="h-8 w-8 p-0 border-green-300 text-green-600 hover:bg-green-100">-</Button>
<div className="w-16 h-10 bg-white border border-green-300 rounded-md flex items-center justify-center">
<span className="font-medium text-green-900">25</span>
<Button variant="outline" size="sm" className="h-8 w-8 p-0 border-[#F95F62]/50 text-[#F95F62] hover:bg-[#F95F62]/20">-</Button>
<div className="w-16 h-10 bg-white border border-[#F95F62]/50 rounded-md flex items-center justify-center">
<span className="font-medium text-[#C94145]">25</span>
</div>
<Button variant="outline" size="sm" className="h-8 w-8 p-0 border-green-300 text-green-600 hover:bg-green-100">+</Button>
<Button variant="outline" size="sm" className="h-8 w-8 p-0 border-[#F95F62]/50 text-[#F95F62] hover:bg-[#F95F62]/20">+</Button>
</div>
</div>
</div>
@@ -366,11 +366,11 @@ export default function RecurringBlockPage({ onNavigateBack }: RecurringBlockPag
</div>
{/* Repeat Toggle */}
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
<div className="bg-[#F95F62]/10 rounded-lg p-4 border border-[#F95F62]/30">
<div className="flex items-center justify-between">
<div>
<span className="font-medium text-gray-900">Repeat Every {day}</span>
<p className="text-sm text-gray-600 mt-1">This configuration will repeat weekly</p>
<span className="font-medium text-[#C94145]">Repeat Every {day}</span>
<p className="text-sm text-[#C94145]/80 mt-1">This configuration will repeat weekly</p>
</div>
<Switch defaultChecked />
</div>
@@ -393,11 +393,11 @@ export default function RecurringBlockPage({ onNavigateBack }: RecurringBlockPag
<div className="text-sm text-gray-600 mb-4">Every Monday</div>
{/* Preview Card */}
<Card className="w-80 bg-gray-800 text-white">
<Card className="w-80 bg-[#F95F62] text-white">
<CardContent className="p-4">
<div className="mb-3">
<h3 className="font-medium text-white">The Enchanted Forest Adventure Park</h3>
<div className="text-xs text-gray-300 mt-1">2 time slots</div>
<div className="text-xs text-white/80 mt-1">2 time slots</div>
</div>
<div className="space-y-2">
@@ -429,7 +429,7 @@ export default function RecurringBlockPage({ onNavigateBack }: RecurringBlockPag
{/* Create Button */}
<div>
<Button className="w-full h-12 bg-gray-900 hover:bg-gray-800 text-white rounded-lg">
<Button className="w-full h-12 bg-[#F95F62] hover:bg-[#E54B4E] text-white rounded-lg">
Create Recurring Block
</Button>
</div>

View File

@@ -131,7 +131,7 @@ export default function RedemptionModal({ isOpen, onClose }: RedemptionModalProp
<Button
type="submit"
disabled={!ticketNumber.trim() || isLoading}
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"
className="w-full h-12 bg-[#F95F62] hover:bg-[#E54B4E] disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded-lg font-medium"
>
{isLoading ? "Searching..." : "Submit"}
</Button>
@@ -218,7 +218,7 @@ export default function RedemptionModal({ isOpen, onClose }: RedemptionModalProp
<div className="space-y-3 pt-4">
<Button
onClick={handleApprove}
className="w-full h-12 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium"
className="w-full h-12 bg-[#F95F62] hover:bg-[#E54B4E] text-white rounded-lg font-medium"
>
Approve
</Button>

View File

@@ -256,7 +256,7 @@ export default function RedemptionsPage() {
const getCardTypeBadge = (cardType: string) => {
return cardType === "Unlimited"
? <Badge variant="secondary" className="bg-blue-100 text-blue-800 hover:bg-blue-100">Unlimited</Badge>
? <Badge variant="secondary" className="bg-[#F95F62]/10 text-[#F95F62] hover:bg-[#F95F62]/10">Unlimited</Badge>
: <Badge variant="secondary" className="bg-purple-100 text-purple-800 hover:bg-purple-100">Selective</Badge>;
};
@@ -315,7 +315,7 @@ export default function RedemptionsPage() {
Filter
</Button>
<Button
className="flex items-center gap-2 bg-gray-800 hover:bg-gray-900 text-white"
className="flex items-center gap-2 bg-[#F95F62] hover:bg-[#E54B4E] text-white"
>
<Download className="h-4 w-4" />
Export Logs
@@ -359,7 +359,7 @@ export default function RedemptionsPage() {
variant="ghost"
size="sm"
onClick={() => handleViewCustomer(item)}
className="flex items-center gap-1 text-blue-600 hover:text-blue-700 hover:bg-blue-50 px-2 py-1"
className="flex items-center gap-1 text-[#F95F62] hover:text-[#E54B4E] hover:bg-[#F95F62]/10 px-2 py-1"
>
<Eye className="h-4 w-4" />
View
@@ -399,7 +399,7 @@ export default function RedemptionsPage() {
onClick={() => setCurrentPage(i + 1)}
className={`w-8 h-8 p-0 ${
currentPage === i + 1
? 'bg-gray-900 text-white hover:bg-gray-800'
? 'bg-[#F95F62] text-white hover:bg-[#E54B4E]'
: 'bg-white text-gray-600 hover:bg-gray-50'
}`}
>

View File

@@ -2,13 +2,16 @@ import { Home, Calendar, Users, CreditCard, HelpCircle, LogOut, ChevronDown, Set
import { motion } from "motion/react";
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
import { useState } from "react";
import { ImageWithFallback } from "./figma/ImageWithFallback";
import cityCardsLogo from '../assets/city-logo.png';
interface SidebarProps {
activeItem: string;
onItemSelect: (item: string) => void;
isNotificationsOpen?: boolean;
}
export default function Sidebar({ activeItem, onItemSelect }: SidebarProps) {
export default function Sidebar({ activeItem, onItemSelect, isNotificationsOpen = false }: SidebarProps) {
const [isProfileOpen, setIsProfileOpen] = useState(false);
const [isBookingExpanded, setIsBookingExpanded] = useState(activeItem === 'booking-table' || activeItem === 'booking-calendar');
@@ -32,11 +35,12 @@ export default function Sidebar({ activeItem, onItemSelect }: SidebarProps) {
<div className="fixed left-0 top-0 w-[280px] h-full bg-white border-r border-gray-200 flex flex-col z-50">
{/* Logo Section */}
<div className="p-6 border-b border-gray-100">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-gradient-to-br from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">CC</span>
</div>
<span className="text-xl font-bold text-gray-900">CityCards</span>
<div className="flex items-center justify-center">
<ImageWithFallback
src={cityCardsLogo}
alt="CityCards Logo"
className="h-12 w-auto object-contain"
/>
</div>
</div>
@@ -53,9 +57,9 @@ export default function Sidebar({ activeItem, onItemSelect }: SidebarProps) {
return (
<div key={item.id} className={`${
hasActiveSubItem || isDirectlyActive
? 'bg-slate-800 rounded-lg shadow-sm mx-2'
? 'bg-[#F95F62] rounded-lg shadow-sm mx-2'
: isExpanded && hasSubItems
? 'bg-gray-100 rounded-lg mx-2'
? 'bg-[#F95F62]/5 rounded-lg mx-2'
: ''
}`}>
<motion.button
@@ -96,7 +100,7 @@ export default function Sidebar({ activeItem, onItemSelect }: SidebarProps) {
{/* Expand/Collapse arrow for sub-items */}
{hasSubItems && (
<motion.div
animate={{ rotate: isExpanded ? 90 : 0 }}
animate={{ rotate: isExpanded ? 0 : -90 }}
transition={{ duration: 0.2 }}
className="ml-auto"
>

View File

@@ -267,7 +267,7 @@ export default function SlotDetailPanel({ isOpen, onClose, slotData }: SlotDetai
onClick={() => toggleTimeSlot(timeSlot)}
className={`text-xs h-8 ${
selectedTimeSlots.includes(timeSlot)
? "bg-gray-900 text-white hover:bg-gray-800"
? "bg-[#F95F62]/20 text-[#F95F62] hover:bg-[#F95F62]/30 border-[#F95F62]/30"
: "text-gray-600 hover:text-gray-900"
}`}
>
@@ -277,7 +277,7 @@ export default function SlotDetailPanel({ isOpen, onClose, slotData }: SlotDetai
<Button
variant="outline"
size="sm"
className="text-xs h-8 text-gray-600 hover:text-gray-900 bg-gray-600 text-white hover:bg-gray-700"
className="text-xs h-8 text-gray-600 hover:text-gray-900 bg-[#F95F62] text-white hover:bg-[#E54B4E]"
>
<Plus className="h-3 w-3 mr-1" />
Add Timing
@@ -316,7 +316,7 @@ export default function SlotDetailPanel({ isOpen, onClose, slotData }: SlotDetai
variant="outline"
size="sm"
onClick={() => setEditingCapacity(true)}
className="bg-gray-600 text-white hover:bg-gray-700"
className="bg-[#F95F62] text-white hover:bg-[#E54B4E]"
>
Edit Capacity
</Button>

View File

@@ -181,8 +181,8 @@ export default function StaffManagementPage() {
const getRoleBadge = (role: string) => {
return role === "Manager"
? <Badge variant="secondary" className="bg-blue-100 text-blue-800 hover:bg-blue-100">Manager</Badge>
: <Badge variant="secondary" className="bg-purple-100 text-purple-800 hover:bg-purple-100">Scanner</Badge>;
? <Badge variant="secondary" className="bg-[#F95F62]/10 text-[#F95F62] hover:bg-[#F95F62]/10">Manager</Badge>
: <Badge variant="secondary" className="bg-[#F95F62]/10 text-[#F95F62] hover:bg-[#F95F62]/10">Scanner</Badge>;
};
const handleToggleStatus = (idNo: string) => {
@@ -375,7 +375,7 @@ export default function StaffManagementPage() {
<Button
onClick={handleUpdateProfile}
className="w-full mt-6 bg-gray-800 hover:bg-gray-900 text-white"
className="w-full mt-6 bg-[#F95F62] hover:bg-[#E54B4E] text-white"
>
Update Profile
</Button>
@@ -472,7 +472,7 @@ export default function StaffManagementPage() {
<Button
onClick={handleCreateStaff}
disabled={!addForm.fullName || !addForm.phone || !addForm.role || !addForm.staff}
className="w-full mt-6 bg-gray-800 hover:bg-gray-900 text-white disabled:bg-gray-400 disabled:cursor-not-allowed"
className="w-full mt-6 bg-[#F95F62] hover:bg-[#E54B4E] text-white disabled:bg-gray-400 disabled:cursor-not-allowed"
>
Create Staff
</Button>
@@ -511,7 +511,7 @@ export default function StaffManagementPage() {
</Button>
<Button
onClick={handleAddStaff}
className="flex items-center gap-2 bg-gray-800 hover:bg-gray-900 text-white"
className="flex items-center gap-2 bg-[#F95F62] hover:bg-[#E54B4E] text-white"
>
<UserPlus className="h-4 w-4" />
Add Staff
@@ -556,7 +556,7 @@ export default function StaffManagementPage() {
variant="ghost"
size="sm"
onClick={() => handleEditStaff(item.idNo)}
className="flex items-center gap-1 text-blue-600 hover:text-blue-700 hover:bg-blue-50 px-2 py-1"
className="flex items-center gap-1 text-[#F95F62] hover:text-[#E54B4E] hover:bg-[#F95F62]/10 px-2 py-1"
>
<Edit className="h-4 w-4" />
Edit
@@ -565,7 +565,7 @@ export default function StaffManagementPage() {
<Switch
checked={item.isActive}
onCheckedChange={() => handleToggleStatus(item.idNo)}
className="data-[state=checked]:bg-green-600"
className="data-[state=checked]:bg-[#F95F62]"
/>
<span className="text-xs text-gray-600 min-w-[45px]">
{item.isActive ? "Active" : "Inactive"}
@@ -607,7 +607,7 @@ export default function StaffManagementPage() {
onClick={() => setCurrentPage(i + 1)}
className={`w-8 h-8 p-0 ${
currentPage === i + 1
? 'bg-gray-900 text-white hover:bg-gray-800'
? 'bg-[#F95F62] text-white hover:bg-[#E54B4E]'
: 'bg-white text-gray-600 hover:bg-gray-50'
}`}
>

View File

@@ -94,7 +94,7 @@ export default function SupportPage() {
<span className="font-medium">{selectedFile.name}</span>
) : (
<>
<span className="font-medium text-blue-600 hover:text-blue-700">
<span className="font-medium text-[#F95F62] hover:text-[#E54B4E]">
Upload file
</span>
<span> or drag and drop</span>
@@ -157,7 +157,7 @@ export default function SupportPage() {
<div className="pt-4">
<Button
type="submit"
className="w-full h-12 bg-gray-900 hover:bg-gray-800 text-white rounded-lg font-medium"
className="w-full h-12 bg-[#F95F62] hover:bg-[#E54B4E] text-white rounded-lg font-medium"
>
Submit Ticket
</Button>

View File

@@ -1,3 +1,4 @@
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap");
/*! tailwindcss v4.1.3 | MIT License | https://tailwindcss.com */
@layer properties {
@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
@@ -77,33 +78,22 @@
--color-orange-500: oklch(.705 .213 47.604);
--color-green-50: oklch(.982 .018 155.826);
--color-green-100: oklch(.962 .044 156.743);
--color-green-200: oklch(.925 .084 155.995);
--color-green-300: oklch(.871 .15 154.449);
--color-green-500: oklch(.723 .219 149.579);
--color-green-600: oklch(.627 .194 149.214);
--color-green-700: oklch(.527 .154 150.069);
--color-green-800: oklch(.448 .119 151.328);
--color-green-900: oklch(.393 .095 152.535);
--color-emerald-500: oklch(.696 .17 162.48);
--color-teal-500: oklch(.704 .14 182.503);
--color-blue-50: oklch(.97 .014 254.604);
--color-blue-100: oklch(.932 .032 255.585);
--color-blue-200: oklch(.882 .059 254.128);
--color-blue-300: oklch(.809 .105 251.813);
--color-blue-500: oklch(.623 .214 259.815);
--color-blue-600: oklch(.546 .245 262.881);
--color-blue-700: oklch(.488 .243 264.376);
--color-blue-800: oklch(.424 .199 265.638);
--color-blue-900: oklch(.379 .146 265.522);
--color-purple-50: oklch(.977 .014 308.299);
--color-purple-100: oklch(.946 .033 307.174);
--color-purple-500: oklch(.627 .265 303.9);
--color-purple-600: oklch(.558 .288 302.321);
--color-purple-800: oklch(.438 .218 303.724);
--color-pink-200: oklch(.899 .061 343.231);
--color-pink-300: oklch(.823 .12 346.018);
--color-slate-700: oklch(.372 .044 257.287);
--color-slate-800: oklch(.279 .041 260.031);
--color-gray-50: oklch(.985 .002 247.839);
--color-gray-100: oklch(.967 .003 264.542);
--color-gray-200: oklch(.928 .006 264.531);
@@ -134,8 +124,6 @@
--text-2xl--line-height: calc(2 / 1.5);
--text-3xl: 1.875rem;
--text-3xl--line-height: calc(2.25 / 1.875);
--text-4xl: 2.25rem;
--text-4xl--line-height: calc(2.5 / 2.25);
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
@@ -405,6 +393,11 @@
}
}
body {
background-color: var(--background);
color: var(--foreground);
}
* {
border-color: var(--border);
outline-color: var(--ring);
@@ -419,6 +412,7 @@
body {
background-color: var(--background);
color: var(--foreground);
font-family: Poppins, sans-serif;
}
:where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) h1 {
@@ -473,6 +467,10 @@
pointer-events: none;
}
.invisible {
visibility: hidden;
}
.sr-only {
clip: rect(0, 0, 0, 0);
white-space: nowrap;
@@ -609,6 +607,10 @@
left: calc(var(--spacing) * 0);
}
.left-1 {
left: calc(var(--spacing) * 1);
}
.left-2 {
left: calc(var(--spacing) * 2);
}
@@ -809,6 +811,16 @@
height: calc(var(--spacing) * 5);
}
.size-7 {
width: calc(var(--spacing) * 7);
height: calc(var(--spacing) * 7);
}
.size-8 {
width: calc(var(--spacing) * 8);
height: calc(var(--spacing) * 8);
}
.size-9 {
width: calc(var(--spacing) * 9);
height: calc(var(--spacing) * 9);
@@ -1006,6 +1018,10 @@
width: calc(var(--spacing) * 64);
}
.w-72 {
width: calc(var(--spacing) * 72);
}
.w-80 {
width: calc(var(--spacing) * 80);
}
@@ -1042,6 +1058,10 @@
width: 449px;
}
.w-auto {
width: auto;
}
.w-fit {
width: fit-content;
}
@@ -1110,10 +1130,18 @@
caption-side: bottom;
}
.border-collapse {
border-collapse: collapse;
}
.origin-\(--radix-dropdown-menu-content-transform-origin\) {
transform-origin: var(--radix-dropdown-menu-content-transform-origin);
}
.origin-\(--radix-popover-content-transform-origin\) {
transform-origin: var(--radix-popover-content-transform-origin);
}
.origin-\(--radix-select-content-transform-origin\) {
transform-origin: var(--radix-select-content-transform-origin);
}
@@ -1307,6 +1335,12 @@
margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)));
}
:where(.space-x-1 > :not(:last-child)) {
--tw-space-x-reverse: 0;
margin-inline-start: calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));
margin-inline-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)));
}
:where(.space-x-2 > :not(:last-child)) {
--tw-space-x-reverse: 0;
margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));
@@ -1447,17 +1481,30 @@
border-bottom-width: 1px;
}
.border-l {
border-left-style: var(--tw-border-style);
border-left-width: 1px;
}
.border-dashed {
--tw-border-style: dashed;
border-style: dashed;
}
.border-blue-200 {
border-color: var(--color-blue-200);
.border-\[\#F95F62\] {
border-color: #f95f62;
}
.border-blue-300 {
border-color: var(--color-blue-300);
.border-\[\#F95F62\]\/20 {
border-color: oklab(68.6249% .174448 .0719368 / .2);
}
.border-\[\#F95F62\]\/30 {
border-color: oklab(68.6249% .174448 .0719368 / .3);
}
.border-\[\#F95F62\]\/50 {
border-color: oklab(68.6249% .174448 .0719368 / .5);
}
.border-gray-50 {
@@ -1476,14 +1523,6 @@
border-color: var(--color-gray-300);
}
.border-green-200 {
border-color: var(--color-green-200);
}
.border-green-300 {
border-color: var(--color-green-300);
}
.border-input {
border-color: var(--input);
}
@@ -1500,10 +1539,30 @@
border-color: #0000;
}
.bg-\[\#F95F62\] {
background-color: #f95f62;
}
.bg-\[\#F95F62\]\/5 {
background-color: oklab(68.6249% .174448 .0719368 / .05);
}
.bg-\[\#F95F62\]\/10 {
background-color: oklab(68.6249% .174448 .0719368 / .1);
}
.bg-\[\#F95F62\]\/20 {
background-color: oklab(68.6249% .174448 .0719368 / .2);
}
.bg-\[\#f6f6f6\] {
background-color: #f6f6f6;
}
.bg-accent {
background-color: var(--accent);
}
.bg-background {
background-color: var(--background);
}
@@ -1532,10 +1591,6 @@
background-color: var(--color-blue-50);
}
.bg-blue-100 {
background-color: var(--color-blue-100);
}
.bg-blue-500 {
background-color: var(--color-blue-500);
}
@@ -1576,14 +1631,6 @@
background-color: var(--color-gray-400);
}
.bg-gray-600 {
background-color: var(--color-gray-600);
}
.bg-gray-800 {
background-color: var(--color-gray-800);
}
.bg-gray-900 {
background-color: var(--color-gray-900);
}
@@ -1618,10 +1665,6 @@
}
}
.bg-pink-200 {
background-color: var(--color-pink-200);
}
.bg-popover {
background-color: var(--popover);
}
@@ -1654,10 +1697,6 @@
background-color: var(--secondary);
}
.bg-slate-800 {
background-color: var(--color-slate-800);
}
.bg-teal-500 {
background-color: var(--color-teal-500);
}
@@ -1689,6 +1728,10 @@
fill: currentColor;
}
.object-contain {
object-fit: contain;
}
.p-0 {
padding: calc(var(--spacing) * 0);
}
@@ -1851,11 +1894,6 @@
line-height: var(--tw-leading, var(--text-3xl--line-height));
}
.text-4xl {
font-size: var(--text-4xl);
line-height: var(--tw-leading, var(--text-4xl--line-height));
}
.text-base {
font-size: var(--text-base);
line-height: var(--tw-leading, var(--text-base--line-height));
@@ -1881,6 +1919,10 @@
line-height: var(--tw-leading, var(--text-xs--line-height));
}
.text-\[0\.8rem\] {
font-size: .8rem;
}
.text-\[14px\] {
font-size: 14px;
}
@@ -1943,10 +1985,26 @@
white-space: nowrap;
}
.text-\[\#C94145\] {
color: #c94145;
}
.text-\[\#C94145\]\/80 {
color: oklab(57.076% .158645 .0668682 / .8);
}
.text-\[\#F95F62\] {
color: #f95f62;
}
.text-\[rgba\(0\,0\,0\,0\.42\)\] {
color: #0000006b;
}
.text-accent-foreground {
color: var(--accent-foreground);
}
.text-black {
color: var(--color-black);
}
@@ -1955,18 +2013,6 @@
color: var(--color-blue-600);
}
.text-blue-700 {
color: var(--color-blue-700);
}
.text-blue-800 {
color: var(--color-blue-800);
}
.text-blue-900 {
color: var(--color-blue-900);
}
.text-card-foreground {
color: var(--card-foreground);
}
@@ -1999,6 +2045,10 @@
color: var(--color-gray-700);
}
.text-gray-800 {
color: var(--color-gray-800);
}
.text-gray-900 {
color: var(--color-gray-900);
}
@@ -2011,10 +2061,6 @@
color: var(--color-green-800);
}
.text-green-900 {
color: var(--color-green-900);
}
.text-muted-foreground {
color: var(--muted-foreground);
}
@@ -2059,6 +2105,16 @@
color: var(--color-white);
}
.text-white\/80 {
color: #fffc;
}
@supports (color: color-mix(in lab, red, red)) {
.text-white\/80 {
color: color-mix(in oklab, var(--color-white) 80%, transparent);
}
}
.lowercase {
text-transform: lowercase;
}
@@ -2112,6 +2168,11 @@
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-xl {
--tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, #0000001a), 0 8px 10px -6px var(--tw-shadow-color, #0000001a);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-xs {
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, #0000000d);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -2143,6 +2204,11 @@
outline-width: 1px;
}
.blur-\[1px\] {
--tw-blur: blur(1px);
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
}
.filter {
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
}
@@ -2198,6 +2264,11 @@
transition-duration: .2s;
}
.duration-300 {
--tw-duration: .3s;
transition-duration: .3s;
}
.duration-1000 {
--tw-duration: 1s;
transition-duration: 1s;
@@ -2214,14 +2285,8 @@
}
@media (hover: hover) {
.group-hover\:text-blue-500:is(:where(.group):hover *) {
color: var(--color-blue-500);
}
}
@media (hover: hover) {
.group-hover\:text-blue-600:is(:where(.group):hover *) {
color: var(--color-blue-600);
.group-hover\:text-\[\#F95F62\]:is(:where(.group):hover *) {
color: #f95f62;
}
}
@@ -2340,6 +2405,14 @@
border-right-width: 0;
}
.focus-within\:relative:focus-within {
position: relative;
}
.focus-within\:z-20:focus-within {
z-index: 20;
}
@media (hover: hover) {
.hover\:scale-105:hover {
--tw-scale-x: 105%;
@@ -2349,12 +2422,42 @@
}
}
@media (hover: hover) {
.hover\:border-gray-300:hover {
border-color: var(--color-gray-300);
}
}
@media (hover: hover) {
.hover\:border-red-400:hover {
border-color: var(--color-red-400);
}
}
@media (hover: hover) {
.hover\:bg-\[\#E54B4E\]:hover {
background-color: #e54b4e;
}
}
@media (hover: hover) {
.hover\:bg-\[\#F95F62\]\/10:hover {
background-color: oklab(68.6249% .174448 .0719368 / .1);
}
}
@media (hover: hover) {
.hover\:bg-\[\#F95F62\]\/20:hover {
background-color: oklab(68.6249% .174448 .0719368 / .2);
}
}
@media (hover: hover) {
.hover\:bg-\[\#F95F62\]\/30:hover {
background-color: oklab(68.6249% .174448 .0719368 / .3);
}
}
@media (hover: hover) {
.hover\:bg-accent:hover {
background-color: var(--accent);
@@ -2367,12 +2470,6 @@
}
}
@media (hover: hover) {
.hover\:bg-blue-100:hover {
background-color: var(--color-blue-100);
}
}
@media (hover: hover) {
.hover\:bg-blue-700:hover {
background-color: var(--color-blue-700);
@@ -2421,24 +2518,12 @@
}
}
@media (hover: hover) {
.hover\:bg-gray-700:hover {
background-color: var(--color-gray-700);
}
}
@media (hover: hover) {
.hover\:bg-gray-800:hover {
background-color: var(--color-gray-800);
}
}
@media (hover: hover) {
.hover\:bg-gray-900:hover {
background-color: var(--color-gray-900);
}
}
@media (hover: hover) {
.hover\:bg-green-100:hover {
background-color: var(--color-green-100);
@@ -2458,8 +2543,8 @@
}
@media (hover: hover) {
.hover\:bg-pink-300:hover {
background-color: var(--color-pink-300);
.hover\:bg-primary:hover {
background-color: var(--primary);
}
}
@@ -2523,6 +2608,12 @@
}
}
@media (hover: hover) {
.hover\:text-\[\#E54B4E\]:hover {
color: #e54b4e;
}
}
@media (hover: hover) {
.hover\:text-accent-foreground:hover {
color: var(--accent-foreground);
@@ -2560,8 +2651,8 @@
}
@media (hover: hover) {
.hover\:text-green-700:hover {
color: var(--color-green-700);
.hover\:text-primary-foreground:hover {
color: var(--primary-foreground);
}
}
@@ -2617,10 +2708,18 @@
background-color: var(--accent);
}
.focus\:bg-primary:focus {
background-color: var(--primary);
}
.focus\:text-accent-foreground:focus {
color: var(--accent-foreground);
}
.focus\:text-primary-foreground:focus {
color: var(--primary-foreground);
}
.focus\:ring-0:focus {
--tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -2739,6 +2838,30 @@
}
}
.aria-selected\:bg-accent[aria-selected="true"] {
background-color: var(--accent);
}
.aria-selected\:bg-primary[aria-selected="true"] {
background-color: var(--primary);
}
.aria-selected\:text-accent-foreground[aria-selected="true"] {
color: var(--accent-foreground);
}
.aria-selected\:text-muted-foreground[aria-selected="true"] {
color: var(--muted-foreground);
}
.aria-selected\:text-primary-foreground[aria-selected="true"] {
color: var(--primary-foreground);
}
.aria-selected\:opacity-100[aria-selected="true"] {
opacity: 1;
}
.data-\[active\=true\]\:z-10[data-active="true"] {
z-index: 10;
}
@@ -2880,8 +3003,8 @@
border-color: var(--primary);
}
.data-\[state\=checked\]\:bg-green-600[data-state="checked"] {
background-color: var(--color-green-600);
.data-\[state\=checked\]\:bg-\[\#F95F62\][data-state="checked"] {
background-color: #f95f62;
}
.data-\[state\=checked\]\:bg-primary[data-state="checked"] {
@@ -3169,6 +3292,39 @@
border-width: 0;
}
.\[\&\:has\(\>\.day-range-end\)\]\:rounded-r-md:has( > .day-range-end) {
border-top-right-radius: calc(var(--radius) - 2px);
border-bottom-right-radius: calc(var(--radius) - 2px);
}
.\[\&\:has\(\>\.day-range-start\)\]\:rounded-l-md:has( > .day-range-start) {
border-top-left-radius: calc(var(--radius) - 2px);
border-bottom-left-radius: calc(var(--radius) - 2px);
}
.\[\&\:has\(\[aria-selected\]\)\]\:rounded-md:has([aria-selected]) {
border-radius: calc(var(--radius) - 2px);
}
.\[\&\:has\(\[aria-selected\]\)\]\:bg-accent:has([aria-selected]) {
background-color: var(--accent);
}
.first\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-l-md:first-child:has([aria-selected]) {
border-top-left-radius: calc(var(--radius) - 2px);
border-bottom-left-radius: calc(var(--radius) - 2px);
}
.last\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-r-md:last-child:has([aria-selected]) {
border-top-right-radius: calc(var(--radius) - 2px);
border-bottom-right-radius: calc(var(--radius) - 2px);
}
.\[\&\:has\(\[aria-selected\]\.day-range-end\)\]\:rounded-r-md:has([aria-selected].day-range-end) {
border-top-right-radius: calc(var(--radius) - 2px);
border-bottom-right-radius: calc(var(--radius) - 2px);
}
.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role="checkbox"]) {
padding-right: calc(var(--spacing) * 0);
}
@@ -3269,6 +3425,7 @@
}
}
:root {
--font-size: 16px;
--background: #fff;

View File

@@ -1,6 +1,6 @@
import { createRoot } from "react-dom/client";
import App from "./App";
import App from "./App.tsx";
import "./index.css";
createRoot(document.getElementById("root")!).render(<App />);

View File

@@ -1,3 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap');
@custom-variant dark (&:is(.dark *));
:root {
@@ -126,6 +128,7 @@
body {
@apply bg-background text-foreground;
font-family: 'Poppins', sans-serif;
}
}

View File

@@ -56,7 +56,7 @@ import * as path from 'path';
outDir: 'build',
},
server: {
port: 4001,
port: 4007,
open: true,
},
});