new src added
This commit is contained in:
111
src/App.tsx
111
src/App.tsx
@@ -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,42 +52,62 @@ export default function App() {
|
||||
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")}
|
||||
onNavigateToProfile={() => setActiveNavItem("profile")}
|
||||
{/* 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}
|
||||
/>
|
||||
|
||||
<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 />}
|
||||
{activeNavItem === "profile" && <ProfilePage />}
|
||||
</motion.div>
|
||||
{/* Main Content with left margin for sidebar */}
|
||||
<div className="ml-[280px]">
|
||||
{/* Header with notifications and profile */}
|
||||
<Header
|
||||
onNavigateToNotifications={() => setActiveNavItem("notifications")}
|
||||
onNavigateToProfile={() => setActiveNavItem("profile")}
|
||||
onSignOut={() => setIsLoggedIn(false)}
|
||||
showNotifications={showNotifications}
|
||||
onNotificationsChange={setShowNotifications}
|
||||
renderNotificationsOutside={true}
|
||||
/>
|
||||
|
||||
<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 />}
|
||||
{activeNavItem === "profile" && <ProfilePage />}
|
||||
</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
BIN
src/assets/city-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
166
src/components/BookingDetailView.tsx
Normal file
166
src/components/BookingDetailView.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" }}
|
||||
>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-1">Dashboard</h1>
|
||||
<p className="text-gray-600">Welcome back, Kassandra!</p>
|
||||
<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 */}
|
||||
|
||||
@@ -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,6 +44,85 @@ 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 (
|
||||
<>
|
||||
{/* 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)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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">
|
||||
@@ -48,7 +131,7 @@ export default function Header({ onNavigateToNotifications, onNavigateToProfile
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowNotifications(!showNotifications)}
|
||||
onClick={() => onNotificationsChange?.(!showNotifications)}
|
||||
className="relative p-2 hover:bg-gray-100 rounded-lg"
|
||||
>
|
||||
<Bell className="h-5 w-5 text-gray-600" />
|
||||
@@ -62,76 +145,81 @@ export default function Header({ onNavigateToNotifications, onNavigateToProfile
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Notifications Dropdown */}
|
||||
<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"
|
||||
>
|
||||
<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">
|
||||
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-blue-50" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`w-2 h-2 rounded-full mt-2 ${
|
||||
notification.unread ? "bg-blue-600" : "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}
|
||||
{/* 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>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-4 text-center border-t border-gray-100">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-blue-600 hover:text-blue-700"
|
||||
onClick={() => {
|
||||
setShowNotifications(false);
|
||||
onNavigateToNotifications?.();
|
||||
}}
|
||||
>
|
||||
View all notifications
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<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 z-40"
|
||||
onClick={() => setShowNotifications(false)}
|
||||
/>
|
||||
{/* Notifications Backdrop */}
|
||||
{showNotifications && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/20 z-40"
|
||||
onClick={() => onNotificationsChange?.(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
353
src/index.css
353
src/index.css
@@ -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;
|
||||
|
||||
@@ -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 />);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ import * as path from 'path';
|
||||
outDir: 'build',
|
||||
},
|
||||
server: {
|
||||
port: 4001,
|
||||
port: 4007,
|
||||
open: true,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user