added profile page
This commit is contained in:
@@ -16,6 +16,7 @@ import SupportPage from "./components/SupportPage";
|
|||||||
import BookingManagementPage from "./components/BookingManagementPage";
|
import BookingManagementPage from "./components/BookingManagementPage";
|
||||||
import RecurringBlockPage from "./components/RecurringBlockPage";
|
import RecurringBlockPage from "./components/RecurringBlockPage";
|
||||||
import NotificationsPage from "./components/NotificationsPage";
|
import NotificationsPage from "./components/NotificationsPage";
|
||||||
|
import ProfilePage from "./components/ProfilePage";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
@@ -57,7 +58,10 @@ export default function App() {
|
|||||||
{/* Main Content with left margin for sidebar */}
|
{/* Main Content with left margin for sidebar */}
|
||||||
<div className="ml-[280px]">
|
<div className="ml-[280px]">
|
||||||
{/* Header with notifications and profile */}
|
{/* Header with notifications and profile */}
|
||||||
<Header onNavigateToNotifications={() => setActiveNavItem("notifications")} />
|
<Header
|
||||||
|
onNavigateToNotifications={() => setActiveNavItem("notifications")}
|
||||||
|
onNavigateToProfile={() => setActiveNavItem("profile")}
|
||||||
|
/>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@@ -78,6 +82,7 @@ export default function App() {
|
|||||||
{activeNavItem === "staff" && <StaffManagementPage />}
|
{activeNavItem === "staff" && <StaffManagementPage />}
|
||||||
{activeNavItem === "support" && <SupportPage />}
|
{activeNavItem === "support" && <SupportPage />}
|
||||||
{activeNavItem === "notifications" && <NotificationsPage />}
|
{activeNavItem === "notifications" && <NotificationsPage />}
|
||||||
|
{activeNavItem === "profile" && <ProfilePage />}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Bell, User, Settings, LogOut, ChevronDown } from "lucide-react";
|
import { Bell, User, LogOut, ChevronDown } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { motion, AnimatePresence } from "motion/react";
|
import { motion, AnimatePresence } from "motion/react";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
||||||
@@ -7,9 +7,10 @@ import { Badge } from "./ui/badge";
|
|||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
onNavigateToNotifications?: () => void;
|
onNavigateToNotifications?: () => void;
|
||||||
|
onNavigateToProfile?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Header({ onNavigateToNotifications }: HeaderProps) {
|
export default function Header({ onNavigateToNotifications, onNavigateToProfile }: HeaderProps) {
|
||||||
const [showNotifications, setShowNotifications] = useState(false);
|
const [showNotifications, setShowNotifications] = useState(false);
|
||||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||||
|
|
||||||
@@ -166,17 +167,14 @@ export default function Header({ onNavigateToNotifications }: HeaderProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="w-full justify-start gap-2 px-3 py-2 hover:bg-gray-100"
|
className="w-full justify-start gap-2 px-3 py-2 hover:bg-gray-100"
|
||||||
|
onClick={() => {
|
||||||
|
setShowProfileMenu(false);
|
||||||
|
onNavigateToProfile?.();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<User className="h-4 w-4" />
|
<User className="h-4 w-4" />
|
||||||
<span>Profile</span>
|
<span>Profile</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="w-full justify-start gap-2 px-3 py-2 hover:bg-gray-100"
|
|
||||||
>
|
|
||||||
<Settings className="h-4 w-4" />
|
|
||||||
<span>Settings</span>
|
|
||||||
</Button>
|
|
||||||
<div className="border-t border-gray-100 my-1"></div>
|
<div className="border-t border-gray-100 my-1"></div>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
415
src/components/ProfilePage.tsx
Normal file
415
src/components/ProfilePage.tsx
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { motion } from "motion/react";
|
||||||
|
import { User, Mail, Phone, MapPin, Shield, Bell, Eye, EyeOff, Camera, Save, Edit3 } from "lucide-react";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Input } from "./ui/input";
|
||||||
|
import { Label } from "./ui/label";
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
||||||
|
import { Switch } from "./ui/switch";
|
||||||
|
import { Separator } from "./ui/separator";
|
||||||
|
import { Badge } from "./ui/badge";
|
||||||
|
|
||||||
|
export default function ProfilePage() {
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [showCurrentPassword, setShowCurrentPassword] = useState(false);
|
||||||
|
const [showNewPassword, setShowNewPassword] = useState(false);
|
||||||
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
|
const [activeTab, setActiveTab] = useState("personal");
|
||||||
|
|
||||||
|
// Profile data states
|
||||||
|
const [profileData, setProfileData] = useState({
|
||||||
|
firstName: "Kassandra",
|
||||||
|
lastName: "Adams",
|
||||||
|
email: "kassandra.adams@citycards.com",
|
||||||
|
phone: "+1 (555) 123-4567",
|
||||||
|
location: "San Francisco, CA",
|
||||||
|
role: "Admin",
|
||||||
|
department: "Operations",
|
||||||
|
currentPassword: "",
|
||||||
|
newPassword: "",
|
||||||
|
confirmPassword: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notification preferences
|
||||||
|
const [notifications, setNotifications] = useState({
|
||||||
|
emailNotifications: true,
|
||||||
|
pushNotifications: true,
|
||||||
|
bookingAlerts: true,
|
||||||
|
systemUpdates: false,
|
||||||
|
marketingEmails: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
setIsEditing(false);
|
||||||
|
// Handle save logic here
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ id: "personal", label: "Personal Info", icon: User },
|
||||||
|
{ id: "security", label: "Security", icon: Shield },
|
||||||
|
{ id: "notifications", label: "Notifications", icon: Bell }
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-6 py-8">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">Profile Settings</h1>
|
||||||
|
<p className="text-gray-600">Manage your account settings and preferences</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||||
|
{/* Sidebar Navigation */}
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<Card className="bg-white border border-gray-200">
|
||||||
|
<CardContent className="p-6">
|
||||||
|
{/* Profile Summary */}
|
||||||
|
<div className="text-center mb-6">
|
||||||
|
<div className="relative inline-block">
|
||||||
|
<Avatar className="h-20 w-20 mx-auto mb-4">
|
||||||
|
<AvatarImage src="/api/placeholder/80/80" alt="Profile" />
|
||||||
|
<AvatarFallback className="text-lg font-medium">KA</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<button className="absolute bottom-3 right-0 bg-blue-600 hover:bg-blue-700 text-white rounded-full p-1.5 transition-colors">
|
||||||
|
<Camera className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-medium text-gray-900">{profileData.firstName} {profileData.lastName}</h3>
|
||||||
|
<p className="text-sm text-gray-500">{profileData.role}</p>
|
||||||
|
<Badge variant="secondary" className="mt-2">
|
||||||
|
{profileData.department}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="mb-6" />
|
||||||
|
|
||||||
|
{/* Navigation Tabs */}
|
||||||
|
<nav className="space-y-2">
|
||||||
|
{tabs.map((tab) => {
|
||||||
|
const Icon = tab.icon;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
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'
|
||||||
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon className="h-4 w-4" />
|
||||||
|
<span className="font-medium">{tab.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="lg:col-span-3">
|
||||||
|
<motion.div
|
||||||
|
key={activeTab}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
{activeTab === "personal" && (
|
||||||
|
<Card className="bg-white border border-gray-200">
|
||||||
|
<CardHeader className="pb-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<CardTitle>Personal Information</CardTitle>
|
||||||
|
<CardDescription>Update your personal details and contact information</CardDescription>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant={isEditing ? "default" : "outline"}
|
||||||
|
onClick={() => isEditing ? handleSave() : setIsEditing(true)}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
{isEditing ? (
|
||||||
|
<>
|
||||||
|
<Save className="h-4 w-4" />
|
||||||
|
Save Changes
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Edit3 className="h-4 w-4" />
|
||||||
|
Edit Profile
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="firstName">First Name</Label>
|
||||||
|
<Input
|
||||||
|
id="firstName"
|
||||||
|
value={profileData.firstName}
|
||||||
|
onChange={(e) => setProfileData({...profileData, firstName: e.target.value})}
|
||||||
|
disabled={!isEditing}
|
||||||
|
className="bg-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="lastName">Last Name</Label>
|
||||||
|
<Input
|
||||||
|
id="lastName"
|
||||||
|
value={profileData.lastName}
|
||||||
|
onChange={(e) => setProfileData({...profileData, lastName: e.target.value})}
|
||||||
|
disabled={!isEditing}
|
||||||
|
className="bg-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">Email Address</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={profileData.email}
|
||||||
|
onChange={(e) => setProfileData({...profileData, email: e.target.value})}
|
||||||
|
disabled={!isEditing}
|
||||||
|
className="pl-10 bg-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="phone">Phone Number</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Phone className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||||
|
<Input
|
||||||
|
id="phone"
|
||||||
|
value={profileData.phone}
|
||||||
|
onChange={(e) => setProfileData({...profileData, phone: e.target.value})}
|
||||||
|
disabled={!isEditing}
|
||||||
|
className="pl-10 bg-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="location">Location</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||||
|
<Input
|
||||||
|
id="location"
|
||||||
|
value={profileData.location}
|
||||||
|
onChange={(e) => setProfileData({...profileData, location: e.target.value})}
|
||||||
|
disabled={!isEditing}
|
||||||
|
className="pl-10 bg-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="role">Role</Label>
|
||||||
|
<Input
|
||||||
|
id="role"
|
||||||
|
value={profileData.role}
|
||||||
|
disabled
|
||||||
|
className="bg-gray-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="department">Department</Label>
|
||||||
|
<Input
|
||||||
|
id="department"
|
||||||
|
value={profileData.department}
|
||||||
|
disabled
|
||||||
|
className="bg-gray-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "security" && (
|
||||||
|
<Card className="bg-white border border-gray-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Security Settings</CardTitle>
|
||||||
|
<CardDescription>Manage your password and security preferences</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="font-medium text-gray-900">Change Password</h3>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="currentPassword">Current Password</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
id="currentPassword"
|
||||||
|
type={showCurrentPassword ? "text" : "password"}
|
||||||
|
value={profileData.currentPassword}
|
||||||
|
onChange={(e) => setProfileData({...profileData, currentPassword: e.target.value})}
|
||||||
|
className="pr-10 bg-white"
|
||||||
|
placeholder="Enter current password"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowCurrentPassword(!showCurrentPassword)}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
|
>
|
||||||
|
{showCurrentPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="newPassword">New Password</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
id="newPassword"
|
||||||
|
type={showNewPassword ? "text" : "password"}
|
||||||
|
value={profileData.newPassword}
|
||||||
|
onChange={(e) => setProfileData({...profileData, newPassword: e.target.value})}
|
||||||
|
className="pr-10 bg-white"
|
||||||
|
placeholder="Enter new password"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowNewPassword(!showNewPassword)}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
|
>
|
||||||
|
{showNewPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="confirmPassword">Confirm New Password</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
id="confirmPassword"
|
||||||
|
type={showConfirmPassword ? "text" : "password"}
|
||||||
|
value={profileData.confirmPassword}
|
||||||
|
onChange={(e) => setProfileData({...profileData, confirmPassword: e.target.value})}
|
||||||
|
className="pr-10 bg-white"
|
||||||
|
placeholder="Confirm new password"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
|
>
|
||||||
|
{showConfirmPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
|
||||||
|
Update Password
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="font-medium text-gray-900">Security Information</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="bg-gray-50 p-4 rounded-lg">
|
||||||
|
<h4 className="font-medium text-gray-900 mb-1">Last Login</h4>
|
||||||
|
<p className="text-sm text-gray-600">Today at 2:34 PM</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 p-4 rounded-lg">
|
||||||
|
<h4 className="font-medium text-gray-900 mb-1">Account Created</h4>
|
||||||
|
<p className="text-sm text-gray-600">March 15, 2024</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "notifications" && (
|
||||||
|
<Card className="bg-white border border-gray-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Notification Preferences</CardTitle>
|
||||||
|
<CardDescription>Choose how you want to receive notifications</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">Email Notifications</h4>
|
||||||
|
<p className="text-sm text-gray-500">Receive notifications via email</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notifications.emailNotifications}
|
||||||
|
onCheckedChange={(checked) => setNotifications({...notifications, emailNotifications: checked})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">Push Notifications</h4>
|
||||||
|
<p className="text-sm text-gray-500">Receive push notifications in browser</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notifications.pushNotifications}
|
||||||
|
onCheckedChange={(checked) => setNotifications({...notifications, pushNotifications: checked})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">Booking Alerts</h4>
|
||||||
|
<p className="text-sm text-gray-500">Get notified about new bookings and changes</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notifications.bookingAlerts}
|
||||||
|
onCheckedChange={(checked) => setNotifications({...notifications, bookingAlerts: checked})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">System Updates</h4>
|
||||||
|
<p className="text-sm text-gray-500">Receive notifications about system updates</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notifications.systemUpdates}
|
||||||
|
onCheckedChange={(checked) => setNotifications({...notifications, systemUpdates: checked})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-gray-900">Marketing Emails</h4>
|
||||||
|
<p className="text-sm text-gray-500">Receive promotional emails and updates</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notifications.marketingEmails}
|
||||||
|
onCheckedChange={(checked) => setNotifications({...notifications, marketingEmails: checked})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="pt-4">
|
||||||
|
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
|
||||||
|
Save Preferences
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -597,6 +597,10 @@
|
|||||||
bottom: calc(var(--spacing) * 1);
|
bottom: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-3 {
|
||||||
|
bottom: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-\[7\.01\%\] {
|
.bottom-\[7\.01\%\] {
|
||||||
bottom: 7.01%;
|
bottom: 7.01%;
|
||||||
}
|
}
|
||||||
@@ -753,6 +757,10 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.inline-flex {
|
.inline-flex {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
@@ -874,6 +882,10 @@
|
|||||||
height: calc(var(--spacing) * 16);
|
height: calc(var(--spacing) * 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-20 {
|
||||||
|
height: calc(var(--spacing) * 20);
|
||||||
|
}
|
||||||
|
|
||||||
.h-32 {
|
.h-32 {
|
||||||
height: calc(var(--spacing) * 32);
|
height: calc(var(--spacing) * 32);
|
||||||
}
|
}
|
||||||
@@ -1249,6 +1261,10 @@
|
|||||||
gap: calc(var(--spacing) * 6);
|
gap: calc(var(--spacing) * 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-8 {
|
||||||
|
gap: calc(var(--spacing) * 8);
|
||||||
|
}
|
||||||
|
|
||||||
:where(.space-y-0 > :not(:last-child)) {
|
:where(.space-y-0 > :not(:last-child)) {
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));
|
margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));
|
||||||
@@ -1685,6 +1701,10 @@
|
|||||||
padding: calc(var(--spacing) * 1);
|
padding: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-1\.5 {
|
||||||
|
padding: calc(var(--spacing) * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
.p-2 {
|
.p-2 {
|
||||||
padding: calc(var(--spacing) * 2);
|
padding: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -1737,6 +1757,10 @@
|
|||||||
padding-block: calc(var(--spacing) * 2);
|
padding-block: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.py-2\.5 {
|
||||||
|
padding-block: calc(var(--spacing) * 2.5);
|
||||||
|
}
|
||||||
|
|
||||||
.py-3 {
|
.py-3 {
|
||||||
padding-block: calc(var(--spacing) * 3);
|
padding-block: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
@@ -2764,6 +2788,22 @@
|
|||||||
padding-left: calc(var(--spacing) * 8);
|
padding-left: calc(var(--spacing) * 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-\[orientation\=horizontal\]\:h-px[data-orientation="horizontal"] {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-\[orientation\=horizontal\]\:w-full[data-orientation="horizontal"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-\[orientation\=vertical\]\:h-full[data-orientation="vertical"] {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-\[orientation\=vertical\]\:w-px[data-orientation="vertical"] {
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.data-\[placeholder\]\:text-muted-foreground[data-placeholder] {
|
.data-\[placeholder\]\:text-muted-foreground[data-placeholder] {
|
||||||
color: var(--muted-foreground);
|
color: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
@@ -2968,12 +3008,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (width >= 64rem) {
|
||||||
|
.lg\:col-span-1 {
|
||||||
|
grid-column: span 1 / span 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width >= 64rem) {
|
||||||
|
.lg\:col-span-3 {
|
||||||
|
grid-column: span 3 / span 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
.lg\:grid-cols-2 {
|
.lg\:grid-cols-2 {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (width >= 64rem) {
|
||||||
|
.lg\:grid-cols-4 {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:border-input:is(.dark *) {
|
.dark\:border-input:is(.dark *) {
|
||||||
border-color: var(--input);
|
border-color: var(--input);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import App from "./App.tsx";
|
import App from "./App";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(<App />);
|
createRoot(document.getElementById("root")!).render(<App />);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react-swc';
|
import react from '@vitejs/plugin-react-swc';
|
||||||
import path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
outDir: 'build',
|
outDir: 'build',
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 4001,
|
||||||
open: true,
|
open: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user