added profile page

This commit is contained in:
priyanshuvish
2025-09-19 14:37:32 +05:30
parent b8068d1a52
commit 309ee03187
7 changed files with 489 additions and 14 deletions

View File

@@ -16,6 +16,7 @@ import SupportPage from "./components/SupportPage";
import BookingManagementPage from "./components/BookingManagementPage";
import RecurringBlockPage from "./components/RecurringBlockPage";
import NotificationsPage from "./components/NotificationsPage";
import ProfilePage from "./components/ProfilePage";
export default function App() {
const [showPassword, setShowPassword] = useState(false);
@@ -57,7 +58,10 @@ export default function App() {
{/* Main Content with left margin for sidebar */}
<div className="ml-[280px]">
{/* Header with notifications and profile */}
<Header onNavigateToNotifications={() => setActiveNavItem("notifications")} />
<Header
onNavigateToNotifications={() => setActiveNavItem("notifications")}
onNavigateToProfile={() => setActiveNavItem("profile")}
/>
<motion.div
initial={{ opacity: 0, y: 20 }}
@@ -78,6 +82,7 @@ export default function App() {
{activeNavItem === "staff" && <StaffManagementPage />}
{activeNavItem === "support" && <SupportPage />}
{activeNavItem === "notifications" && <NotificationsPage />}
{activeNavItem === "profile" && <ProfilePage />}
</motion.div>
</div>
</div>

View File

@@ -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 { motion, AnimatePresence } from "motion/react";
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
@@ -7,9 +7,10 @@ import { Badge } from "./ui/badge";
interface HeaderProps {
onNavigateToNotifications?: () => void;
onNavigateToProfile?: () => void;
}
export default function Header({ onNavigateToNotifications }: HeaderProps) {
export default function Header({ onNavigateToNotifications, onNavigateToProfile }: HeaderProps) {
const [showNotifications, setShowNotifications] = useState(false);
const [showProfileMenu, setShowProfileMenu] = useState(false);
@@ -166,17 +167,14 @@ export default function Header({ onNavigateToNotifications }: HeaderProps) {
<Button
variant="ghost"
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" />
<span>Profile</span>
</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>
<Button
variant="ghost"

View 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>
);
}

View File

@@ -597,6 +597,10 @@
bottom: calc(var(--spacing) * 1);
}
.bottom-3 {
bottom: calc(var(--spacing) * 3);
}
.bottom-\[7\.01\%\] {
bottom: 7.01%;
}
@@ -753,6 +757,10 @@
display: none;
}
.inline-block {
display: inline-block;
}
.inline-flex {
display: inline-flex;
}
@@ -874,6 +882,10 @@
height: calc(var(--spacing) * 16);
}
.h-20 {
height: calc(var(--spacing) * 20);
}
.h-32 {
height: calc(var(--spacing) * 32);
}
@@ -1249,6 +1261,10 @@
gap: calc(var(--spacing) * 6);
}
.gap-8 {
gap: calc(var(--spacing) * 8);
}
:where(.space-y-0 > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));
@@ -1685,6 +1701,10 @@
padding: calc(var(--spacing) * 1);
}
.p-1\.5 {
padding: calc(var(--spacing) * 1.5);
}
.p-2 {
padding: calc(var(--spacing) * 2);
}
@@ -1737,6 +1757,10 @@
padding-block: calc(var(--spacing) * 2);
}
.py-2\.5 {
padding-block: calc(var(--spacing) * 2.5);
}
.py-3 {
padding-block: calc(var(--spacing) * 3);
}
@@ -2764,6 +2788,22 @@
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] {
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) {
.lg\:grid-cols-2 {
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 *) {
border-color: var(--input);
}

View File

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

View File

@@ -1,4 +1,3 @@
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
:root {

View File

@@ -1,7 +1,7 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import path from 'path';
import * as path from 'path';
export default defineConfig({
plugins: [react()],
@@ -56,7 +56,7 @@
outDir: 'build',
},
server: {
port: 3000,
port: 4001,
open: true,
},
});