ecom / client /src /pages /profile.tsx
shashwatIDR's picture
Upload 106 files
1684141 verified
import { useState } from "react";
import { useLocation } from "wouter";
import { useAuth } from "@/hooks/use-auth";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { userApi, ordersApi } from "@/lib/api";
import Header from "@/components/layout/header";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { useToast } from "@/hooks/use-toast";
import { User, Package, MapPin, CreditCard, Settings, LogOut, Edit, Save, X, Plus, Trash2, Mail, Bell, Shield, Download, Eye, EyeOff } from "lucide-react";
export default function Profile() {
const [, setLocation] = useLocation();
const { user, logout, isLoading } = useAuth();
const [searchQuery, setSearchQuery] = useState("");
const [isEditing, setIsEditing] = useState(false);
const [editForm, setEditForm] = useState<any>({});
const [activeTab, setActiveTab] = useState("profile");
const [addresses, setAddresses] = useState<any[]>([]);
const [paymentMethods, setPaymentMethods] = useState<any[]>([]);
const [addressForm, setAddressForm] = useState<any>({});
const [paymentForm, setPaymentForm] = useState<any>({});
const [isAddingAddress, setIsAddingAddress] = useState(false);
const [isAddingPayment, setIsAddingPayment] = useState(false);
const [settings, setSettings] = useState({
emailNotifications: true,
orderNotifications: true,
promotionalEmails: false,
twoFactorAuth: false,
showProfile: true
});
const [showPassword, setShowPassword] = useState(false);
const [passwordForm, setPasswordForm] = useState({ current: '', new: '', confirm: '' });
const { toast } = useToast();
const queryClient = useQueryClient();
// Always call hooks before any early returns
const { data: profile, isLoading: profileLoading } = useQuery({
queryKey: ['/api/user/profile'],
queryFn: async () => {
const response = await userApi.getProfile();
return response.json();
},
enabled: !!user, // Only fetch when user is authenticated
});
const { data: orders = [], isLoading: ordersLoading } = useQuery({
queryKey: ['/api/orders'],
queryFn: async () => {
const response = await ordersApi.get();
return response.json();
},
enabled: !!user, // Only fetch when user is authenticated
});
const updateProfileMutation = useMutation({
mutationFn: (data: any) => userApi.updateProfile(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/user/profile'] });
setIsEditing(false);
toast({
title: "Profile updated",
description: "Your profile has been successfully updated.",
});
},
onError: () => {
toast({
title: "Error",
description: "Failed to update profile. Please try again.",
variant: "destructive",
});
},
});
// Show loading while authentication is being verified
if (isLoading) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
<p className="text-muted-foreground">Loading...</p>
</div>
</div>
);
}
// Redirect if not authenticated after loading completes
if (!user) {
setLocation('/auth');
return null;
}
const handleEditProfile = () => {
setEditForm({
firstName: profile?.firstName || user.firstName || '',
lastName: profile?.lastName || user.lastName || '',
email: profile?.email || user.email || '',
phone: profile?.phone || '',
street: profile?.street || '',
city: profile?.city || '',
state: profile?.state || '',
pinCode: profile?.pinCode || '',
country: profile?.country || '',
});
setIsEditing(true);
};
const handleSaveProfile = () => {
updateProfileMutation.mutate(editForm);
};
const handleCancelEdit = () => {
setIsEditing(false);
setEditForm({});
};
const handleInputChange = (field: string, value: string) => {
setEditForm(prev => ({ ...prev, [field]: value }));
};
const handleTabNavigation = (section: string) => {
setActiveTab(section);
if (section === 'orders') {
setLocation('/orders');
}
};
const handleLogout = () => {
logout();
setLocation('/');
};
// Address functions
const handleAddAddress = () => {
setAddressForm({ type: 'home', street: '', city: '', state: '', pinCode: '', country: '', isDefault: false });
setIsAddingAddress(true);
};
const handleSaveAddress = () => {
if (!addressForm.street || !addressForm.city || !addressForm.state || !addressForm.pinCode) {
toast({ title: "Error", description: "Please fill in all required fields.", variant: "destructive" });
return;
}
const newAddress = { ...addressForm, id: Date.now().toString() };
setAddresses(prev => [...prev, newAddress]);
setIsAddingAddress(false);
setAddressForm({});
toast({ title: "Success", description: "Address added successfully." });
};
const handleDeleteAddress = (addressId: string) => {
setAddresses(prev => prev.filter(addr => addr.id !== addressId));
toast({ title: "Success", description: "Address deleted successfully." });
};
// Payment functions
const handleAddPayment = () => {
setPaymentForm({ type: 'card', cardNumber: '', expiryDate: '', cvv: '', nameOnCard: '', isDefault: false });
setIsAddingPayment(true);
};
const handleSavePayment = () => {
if (!paymentForm.cardNumber || !paymentForm.expiryDate || !paymentForm.cvv || !paymentForm.nameOnCard) {
toast({ title: "Error", description: "Please fill in all required fields.", variant: "destructive" });
return;
}
const newPayment = {
...paymentForm,
id: Date.now().toString(),
cardNumber: `****-****-****-${paymentForm.cardNumber.slice(-4)}`
};
setPaymentMethods(prev => [...prev, newPayment]);
setIsAddingPayment(false);
setPaymentForm({});
toast({ title: "Success", description: "Payment method added successfully." });
};
const handleDeletePayment = (paymentId: string) => {
setPaymentMethods(prev => prev.filter(payment => payment.id !== paymentId));
toast({ title: "Success", description: "Payment method deleted successfully." });
};
// Settings functions
const handleSettingsChange = (key: string, value: boolean) => {
setSettings(prev => ({ ...prev, [key]: value }));
toast({ title: "Settings updated", description: "Your preferences have been saved." });
};
const handlePasswordChange = () => {
if (!passwordForm.current || !passwordForm.new || !passwordForm.confirm) {
toast({ title: "Error", description: "Please fill in all password fields.", variant: "destructive" });
return;
}
if (passwordForm.new !== passwordForm.confirm) {
toast({ title: "Error", description: "New passwords do not match.", variant: "destructive" });
return;
}
if (passwordForm.new.length < 6) {
toast({ title: "Error", description: "Password must be at least 6 characters long.", variant: "destructive" });
return;
}
setPasswordForm({ current: '', new: '', confirm: '' });
toast({ title: "Success", description: "Password changed successfully." });
};
const handleDataExport = () => {
toast({ title: "Data Export", description: "Your data export will be emailed to you within 24 hours." });
};
const handleAccountDelete = () => {
toast({ title: "Account Deletion", description: "Please contact support to delete your account.", variant: "destructive" });
};
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case 'delivered':
return 'default';
case 'shipped':
return 'secondary';
case 'pending':
return 'outline';
case 'cancelled':
return 'destructive';
default:
return 'outline';
}
};
return (
<div className="min-h-screen bg-gray-50">
<Header searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8" data-testid="profile-page">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900" data-testid="profile-title">
My Account
</h1>
<p className="text-gray-600 mt-2">Manage your account and view your order history</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
{/* Account Navigation */}
<div className="lg:col-span-1">
<Card>
<CardContent className="p-4">
<nav className="space-y-2" data-testid="profile-nav">
<Button
variant="ghost"
className={`w-full justify-start ${
activeTab === 'profile' ? 'bg-primary/10 text-primary' : ''
}`}
onClick={() => handleTabNavigation('profile')}
>
<User className="mr-2 h-4 w-4" />
Profile
</Button>
<Button
variant="ghost"
className={`w-full justify-start ${
activeTab === 'orders' ? 'bg-primary/10 text-primary' : ''
}`}
onClick={() => handleTabNavigation('orders')}
>
<Package className="mr-2 h-4 w-4" />
Order History
</Button>
<Button
variant="ghost"
className={`w-full justify-start ${
activeTab === 'addresses' ? 'bg-primary/10 text-primary' : ''
}`}
onClick={() => handleTabNavigation('addresses')}
>
<MapPin className="mr-2 h-4 w-4" />
Addresses
</Button>
<Button
variant="ghost"
className={`w-full justify-start ${
activeTab === 'payment' ? 'bg-primary/10 text-primary' : ''
}`}
onClick={() => handleTabNavigation('payment')}
>
<CreditCard className="mr-2 h-4 w-4" />
Payment Methods
</Button>
<Button
variant="ghost"
className={`w-full justify-start ${
activeTab === 'settings' ? 'bg-primary/10 text-primary' : ''
}`}
onClick={() => handleTabNavigation('settings')}
>
<Settings className="mr-2 h-4 w-4" />
Settings
</Button>
<Separator />
<Button
variant="ghost"
className="w-full justify-start text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={handleLogout}
data-testid="logout-button"
>
<LogOut className="mr-2 h-4 w-4" />
Sign Out
</Button>
</nav>
</CardContent>
</Card>
</div>
{/* Account Content */}
<div className="lg:col-span-3">
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
<TabsList className="hidden">
<TabsTrigger value="profile" data-testid="profile-tab">Profile Information</TabsTrigger>
<TabsTrigger value="orders" data-testid="orders-tab">Order History</TabsTrigger>
<TabsTrigger value="addresses" data-testid="addresses-tab">Addresses</TabsTrigger>
<TabsTrigger value="payment" data-testid="payment-tab">Payment Methods</TabsTrigger>
<TabsTrigger value="settings" data-testid="settings-tab">Settings</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<Card>
<CardHeader>
<CardTitle>Profile Information</CardTitle>
</CardHeader>
<CardContent>
{profileLoading ? (
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
</div>
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
</div>
) : (
<div className="space-y-6" data-testid="profile-info">
{!isEditing ? (
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label>First Name</Label>
<div className="p-3 bg-gray-50 rounded-lg" data-testid="first-name">
{profile?.firstName || user.firstName || 'Not provided'}
</div>
</div>
<div>
<Label>Last Name</Label>
<div className="p-3 bg-gray-50 rounded-lg" data-testid="last-name">
{profile?.lastName || user.lastName || 'Not provided'}
</div>
</div>
</div>
<div>
<Label>Email</Label>
<div className="p-3 bg-gray-50 rounded-lg" data-testid="email">
{profile?.email || user.email}
</div>
</div>
<div>
<Label>Username</Label>
<div className="p-3 bg-gray-50 rounded-lg" data-testid="username">
{profile?.username || user.username}
</div>
</div>
<div>
<Label>Phone</Label>
<div className="p-3 bg-gray-50 rounded-lg" data-testid="phone">
{profile?.phone || 'Not provided'}
</div>
</div>
<div>
<Label>Address</Label>
<div className="p-3 bg-gray-50 rounded-lg" data-testid="address">
{(profile?.street || profile?.city) ?
`${profile.street || ''}, ${profile.city || ''}, ${profile.state || ''} ${profile.pinCode || ''}, ${profile.country || ''}`.replace(/^,\s*|,\s*$|,\s*,/g, '') :
'Not provided'
}
</div>
</div>
<Button
onClick={handleEditProfile}
className="bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600"
data-testid="edit-profile"
>
<Edit className="h-4 w-4 mr-2" />
Edit Profile
</Button>
</>
) : (
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label htmlFor="firstName">First Name</Label>
<Input
id="firstName"
value={editForm.firstName || ''}
onChange={(e) => handleInputChange('firstName', e.target.value)}
data-testid="edit-first-name"
/>
</div>
<div>
<Label htmlFor="lastName">Last Name</Label>
<Input
id="lastName"
value={editForm.lastName || ''}
onChange={(e) => handleInputChange('lastName', e.target.value)}
data-testid="edit-last-name"
/>
</div>
</div>
<div>
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
value={editForm.email || ''}
onChange={(e) => handleInputChange('email', e.target.value)}
data-testid="edit-email"
/>
</div>
<div>
<Label htmlFor="phone">Phone</Label>
<Input
id="phone"
value={editForm.phone || ''}
onChange={(e) => handleInputChange('phone', e.target.value)}
data-testid="edit-phone"
/>
</div>
<div className="space-y-4">
<h4 className="font-medium text-gray-900">Address Information</h4>
<div>
<Label htmlFor="street">Street Address</Label>
<Input
id="street"
value={editForm.street || ''}
onChange={(e) => handleInputChange('street', e.target.value)}
data-testid="edit-street"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<Label htmlFor="city">City</Label>
<Input
id="city"
value={editForm.city || ''}
onChange={(e) => handleInputChange('city', e.target.value)}
data-testid="edit-city"
/>
</div>
<div>
<Label htmlFor="state">State</Label>
<Input
id="state"
value={editForm.state || ''}
onChange={(e) => handleInputChange('state', e.target.value)}
data-testid="edit-state"
/>
</div>
<div>
<Label htmlFor="pinCode">PIN Code</Label>
<Input
id="pinCode"
value={editForm.pinCode || ''}
onChange={(e) => handleInputChange('pinCode', e.target.value)}
data-testid="edit-pin-code"
/>
</div>
</div>
<div>
<Label htmlFor="country">Country</Label>
<Input
id="country"
value={editForm.country || ''}
onChange={(e) => handleInputChange('country', e.target.value)}
data-testid="edit-country"
/>
</div>
</div>
<div className="flex space-x-3">
<Button
onClick={handleSaveProfile}
disabled={updateProfileMutation.isPending}
className="bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600"
data-testid="save-profile"
>
<Save className="h-4 w-4 mr-2" />
{updateProfileMutation.isPending ? 'Saving...' : 'Save Changes'}
</Button>
<Button
variant="outline"
onClick={handleCancelEdit}
data-testid="cancel-edit"
>
<X className="h-4 w-4 mr-2" />
Cancel
</Button>
</div>
</>
)}
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="addresses">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center">
<MapPin className="h-5 w-5 mr-2" />
Addresses
</CardTitle>
<Dialog open={isAddingAddress} onOpenChange={setIsAddingAddress}>
<DialogTrigger asChild>
<Button onClick={handleAddAddress} data-testid="add-address">
<Plus className="h-4 w-4 mr-2" />
Add Address
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add New Address</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="addressType">Address Type</Label>
<Select value={addressForm.type || ''} onValueChange={(value) => setAddressForm(prev => ({ ...prev, type: value }))}>
<SelectTrigger>
<SelectValue placeholder="Select address type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="home">Home</SelectItem>
<SelectItem value="work">Work</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="street">Street Address</Label>
<Input
id="street"
value={addressForm.street || ''}
onChange={(e) => setAddressForm(prev => ({ ...prev, street: e.target.value }))}
placeholder="Enter street address"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="city">City</Label>
<Input
id="city"
value={addressForm.city || ''}
onChange={(e) => setAddressForm(prev => ({ ...prev, city: e.target.value }))}
placeholder="Enter city"
/>
</div>
<div>
<Label htmlFor="state">State</Label>
<Input
id="state"
value={addressForm.state || ''}
onChange={(e) => setAddressForm(prev => ({ ...prev, state: e.target.value }))}
placeholder="Enter state"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="pinCode">PIN Code</Label>
<Input
id="pinCode"
value={addressForm.pinCode || ''}
onChange={(e) => setAddressForm(prev => ({ ...prev, pinCode: e.target.value }))}
placeholder="Enter PIN code"
/>
</div>
<div>
<Label htmlFor="country">Country</Label>
<Input
id="country"
value={addressForm.country || ''}
onChange={(e) => setAddressForm(prev => ({ ...prev, country: e.target.value }))}
placeholder="Enter country"
/>
</div>
</div>
<div className="flex items-center space-x-2">
<Switch
id="defaultAddress"
checked={addressForm.isDefault || false}
onCheckedChange={(checked) => setAddressForm(prev => ({ ...prev, isDefault: checked }))}
/>
<Label htmlFor="defaultAddress">Set as default address</Label>
</div>
<div className="flex space-x-2">
<Button onClick={handleSaveAddress} className="flex-1">Save Address</Button>
<Button variant="outline" onClick={() => setIsAddingAddress(false)} className="flex-1">Cancel</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</CardHeader>
<CardContent>
{addresses.length === 0 ? (
<div className="text-center py-12">
<MapPin className="h-16 w-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No saved addresses</h3>
<p className="text-gray-500 mb-6">Add addresses for faster checkout</p>
</div>
) : (
<div className="space-y-4" data-testid="addresses-list">
{addresses.map((address: any) => (
<div key={address.id} className="border rounded-lg p-4" data-testid={`address-${address.id}`}>
<div className="flex justify-between items-start">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<Badge variant={address.isDefault ? 'default' : 'secondary'}>
{address.type.charAt(0).toUpperCase() + address.type.slice(1)}
</Badge>
{address.isDefault && <Badge>Default</Badge>}
</div>
<p className="text-gray-900">{address.street}</p>
<p className="text-gray-600">{address.city}, {address.state} {address.pinCode}</p>
<p className="text-gray-600">{address.country}</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleDeleteAddress(address.id)}
className="text-red-600 hover:text-red-700"
data-testid={`delete-address-${address.id}`}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="payment">
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center">
<CreditCard className="h-5 w-5 mr-2" />
Payment Methods
</CardTitle>
<Dialog open={isAddingPayment} onOpenChange={setIsAddingPayment}>
<DialogTrigger asChild>
<Button onClick={handleAddPayment} data-testid="add-payment">
<Plus className="h-4 w-4 mr-2" />
Add Payment Method
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Payment Method</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="nameOnCard">Name on Card</Label>
<Input
id="nameOnCard"
value={paymentForm.nameOnCard || ''}
onChange={(e) => setPaymentForm(prev => ({ ...prev, nameOnCard: e.target.value }))}
placeholder="Enter name on card"
/>
</div>
<div>
<Label htmlFor="cardNumber">Card Number</Label>
<Input
id="cardNumber"
value={paymentForm.cardNumber || ''}
onChange={(e) => setPaymentForm(prev => ({ ...prev, cardNumber: e.target.value }))}
placeholder="1234 5678 9012 3456"
maxLength={19}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="expiryDate">Expiry Date</Label>
<Input
id="expiryDate"
value={paymentForm.expiryDate || ''}
onChange={(e) => setPaymentForm(prev => ({ ...prev, expiryDate: e.target.value }))}
placeholder="MM/YY"
maxLength={5}
/>
</div>
<div>
<Label htmlFor="cvv">CVV</Label>
<Input
id="cvv"
value={paymentForm.cvv || ''}
onChange={(e) => setPaymentForm(prev => ({ ...prev, cvv: e.target.value }))}
placeholder="123"
maxLength={4}
/>
</div>
</div>
<div className="flex items-center space-x-2">
<Switch
id="defaultPayment"
checked={paymentForm.isDefault || false}
onCheckedChange={(checked) => setPaymentForm(prev => ({ ...prev, isDefault: checked }))}
/>
<Label htmlFor="defaultPayment">Set as default payment method</Label>
</div>
<div className="flex space-x-2">
<Button onClick={handleSavePayment} className="flex-1">Save Payment Method</Button>
<Button variant="outline" onClick={() => setIsAddingPayment(false)} className="flex-1">Cancel</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</CardHeader>
<CardContent>
{paymentMethods.length === 0 ? (
<div className="text-center py-12">
<CreditCard className="h-16 w-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No saved payment methods</h3>
<p className="text-gray-500 mb-6">Add payment methods for faster checkout</p>
</div>
) : (
<div className="space-y-4" data-testid="payment-methods-list">
{paymentMethods.map((payment: any) => (
<div key={payment.id} className="border rounded-lg p-4" data-testid={`payment-${payment.id}`}>
<div className="flex justify-between items-start">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<Badge variant={payment.isDefault ? 'default' : 'secondary'}>
Credit Card
</Badge>
{payment.isDefault && <Badge>Default</Badge>}
</div>
<p className="text-gray-900 font-medium">{payment.nameOnCard}</p>
<p className="text-gray-600">{payment.cardNumber}</p>
<p className="text-gray-600">Expires: {payment.expiryDate}</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleDeletePayment(payment.id)}
className="text-red-600 hover:text-red-700"
data-testid={`delete-payment-${payment.id}`}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="settings">
<div className="space-y-6">
{/* Notifications */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Bell className="h-5 w-5 mr-2" />
Notifications
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div>
<Label>Email Notifications</Label>
<p className="text-sm text-gray-500">Receive order updates and promotions via email</p>
</div>
<Switch
checked={settings.emailNotifications}
onCheckedChange={(checked) => handleSettingsChange('emailNotifications', checked)}
data-testid="email-notifications"
/>
</div>
<div className="flex items-center justify-between">
<div>
<Label>Order Notifications</Label>
<p className="text-sm text-gray-500">Get notified about order status changes</p>
</div>
<Switch
checked={settings.orderNotifications}
onCheckedChange={(checked) => handleSettingsChange('orderNotifications', checked)}
data-testid="order-notifications"
/>
</div>
<div className="flex items-center justify-between">
<div>
<Label>Promotional Emails</Label>
<p className="text-sm text-gray-500">Receive offers and deals via email</p>
</div>
<Switch
checked={settings.promotionalEmails}
onCheckedChange={(checked) => handleSettingsChange('promotionalEmails', checked)}
data-testid="promotional-emails"
/>
</div>
</CardContent>
</Card>
{/* Security */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Shield className="h-5 w-5 mr-2" />
Security
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between">
<div>
<Label>Two-Factor Authentication</Label>
<p className="text-sm text-gray-500">Add an extra layer of security to your account</p>
</div>
<Switch
checked={settings.twoFactorAuth}
onCheckedChange={(checked) => handleSettingsChange('twoFactorAuth', checked)}
data-testid="two-factor-auth"
/>
</div>
<div className="border-t pt-4">
<h4 className="font-medium text-gray-900 mb-3">Change Password</h4>
<div className="space-y-3">
<div>
<Label>Current Password</Label>
<div className="relative">
<Input
type={showPassword ? 'text' : 'password'}
value={passwordForm.current}
onChange={(e) => setPasswordForm(prev => ({ ...prev, current: e.target.value }))}
placeholder="Enter current password"
data-testid="current-password"
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
</div>
<div>
<Label>New Password</Label>
<Input
type="password"
value={passwordForm.new}
onChange={(e) => setPasswordForm(prev => ({ ...prev, new: e.target.value }))}
placeholder="Enter new password"
data-testid="new-password"
/>
</div>
<div>
<Label>Confirm New Password</Label>
<Input
type="password"
value={passwordForm.confirm}
onChange={(e) => setPasswordForm(prev => ({ ...prev, confirm: e.target.value }))}
placeholder="Confirm new password"
data-testid="confirm-password"
/>
</div>
<Button onClick={handlePasswordChange} data-testid="change-password">Change Password</Button>
</div>
</div>
</CardContent>
</Card>
{/* Privacy */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Eye className="h-5 w-5 mr-2" />
Privacy
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div>
<Label>Show Profile Publicly</Label>
<p className="text-sm text-gray-500">Make your profile visible to other users</p>
</div>
<Switch
checked={settings.showProfile}
onCheckedChange={(checked) => handleSettingsChange('showProfile', checked)}
data-testid="show-profile"
/>
</div>
</CardContent>
</Card>
{/* Data & Account */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Download className="h-5 w-5 mr-2" />
Data & Account
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div>
<Label>Export Data</Label>
<p className="text-sm text-gray-500">Download all your account data</p>
</div>
<Button variant="outline" onClick={handleDataExport} data-testid="export-data">
<Download className="h-4 w-4 mr-2" />
Export
</Button>
</div>
<div className="flex items-center justify-between">
<div>
<Label>Delete Account</Label>
<p className="text-sm text-gray-500">Permanently delete your account and all data</p>
</div>
<Button
variant="outline"
onClick={handleAccountDelete}
className="text-red-600 hover:text-red-700 border-red-200 hover:border-red-300"
data-testid="delete-account"
>
<Trash2 className="h-4 w-4 mr-2" />
Delete
</Button>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="orders">
<Card>
<CardHeader>
<CardTitle>Order History</CardTitle>
</CardHeader>
<CardContent>
{ordersLoading ? (
<div className="space-y-4">
{[...Array(3)].map((_, i) => (
<div key={i} className="border rounded-lg p-4">
<Skeleton className="h-6 w-48 mb-2" />
<Skeleton className="h-4 w-32 mb-4" />
<div className="space-y-2">
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
</div>
</div>
))}
</div>
) : orders.length > 0 ? (
<div className="space-y-4" data-testid="orders-list">
{orders.map((order: any) => (
<div key={order.id} className="border border-gray-200 rounded-lg p-4" data-testid={`order-${order.id}`}>
<div className="flex justify-between items-start mb-3">
<div>
<h4 className="font-medium text-gray-900" data-testid={`order-id-${order.id}`}>
Order #{order.id.slice(0, 8)}
</h4>
<p className="text-sm text-gray-500" data-testid={`order-date-${order.id}`}>
Placed on {new Date(order.createdAt).toLocaleDateString()}
</p>
</div>
<Badge variant={getStatusColor(order.status)} data-testid={`order-status-${order.id}`}>
{order.status}
</Badge>
</div>
{order.items && order.items.length > 0 && (
<div className="space-y-2 mb-4">
{order.items.map((item: any) => (
<div key={item.id} className="flex items-center space-x-3" data-testid={`order-item-${item.id}`}>
<div className="w-12 h-12 bg-gray-100 rounded overflow-hidden">
{item.product?.images && item.product.images.length > 0 ? (
<img
src={item.product.images[0]}
alt={item.product.title}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-gray-200 flex items-center justify-center">
<span className="text-xs text-gray-400">No Image</span>
</div>
)}
</div>
<div className="flex-1">
<p className="text-sm font-medium text-gray-900" data-testid={`order-item-title-${item.id}`}>
{item.product?.title || 'Unknown Product'}
</p>
<p className="text-xs text-gray-500" data-testid={`order-item-seller-${item.id}`}>
{item.product?.store?.name || 'Unknown Seller'}
</p>
</div>
<div className="text-right">
<p className="text-sm font-semibold text-gray-900" data-testid={`order-item-price-${item.id}`}>
₹{parseFloat(item.price).toFixed(2)}
</p>
<p className="text-xs text-gray-500" data-testid={`order-item-quantity-${item.id}`}>
Qty: {item.quantity}
</p>
</div>
</div>
))}
</div>
)}
<div className="flex justify-between items-center pt-3 border-t border-gray-200">
<span className="font-semibold text-gray-900" data-testid={`order-total-${order.id}`}>
Total: ₹{parseFloat(order.total).toFixed(2)}
</span>
<div className="space-x-2">
<Button variant="outline" size="sm" data-testid={`track-order-${order.id}`}>
Track Order
</Button>
<Button variant="outline" size="sm" data-testid={`reorder-${order.id}`}>
Reorder
</Button>
</div>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12" data-testid="no-orders">
<Package className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500">You haven't placed any orders yet.</p>
<Button
className="mt-4"
onClick={() => setLocation('/')}
data-testid="start-shopping"
>
Start Shopping
</Button>
</div>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
</div>
</div>
</div>
);
}