hey-pharmacist-ecommerce 1.1.13 → 1.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/index.d.mts +2 -4
  2. package/dist/index.d.ts +2 -4
  3. package/dist/index.js +1039 -857
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1039 -856
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +3 -3
  8. package/src/components/AccountAddressesTab.tsx +209 -0
  9. package/src/components/AccountOrdersTab.tsx +151 -0
  10. package/src/components/AccountOverviewTab.tsx +209 -0
  11. package/src/components/AccountPaymentTab.tsx +116 -0
  12. package/src/components/AccountSavedItemsTab.tsx +76 -0
  13. package/src/components/AccountSettingsTab.tsx +116 -0
  14. package/src/components/AddressFormModal.tsx +23 -10
  15. package/src/components/CartItem.tsx +60 -56
  16. package/src/components/Header.tsx +69 -16
  17. package/src/components/Notification.tsx +148 -0
  18. package/src/components/ProductCard.tsx +215 -178
  19. package/src/components/QuickViewModal.tsx +314 -0
  20. package/src/components/TabNavigation.tsx +48 -0
  21. package/src/components/ui/Button.tsx +1 -1
  22. package/src/components/ui/ConfirmModal.tsx +84 -0
  23. package/src/hooks/usePaymentMethods.ts +58 -0
  24. package/src/index.ts +0 -1
  25. package/src/providers/CartProvider.tsx +22 -6
  26. package/src/providers/EcommerceProvider.tsx +8 -7
  27. package/src/providers/FavoritesProvider.tsx +10 -3
  28. package/src/providers/NotificationProvider.tsx +79 -0
  29. package/src/providers/WishlistProvider.tsx +34 -9
  30. package/src/screens/AddressesScreen.tsx +72 -61
  31. package/src/screens/CartScreen.tsx +48 -32
  32. package/src/screens/ChangePasswordScreen.tsx +155 -0
  33. package/src/screens/CheckoutScreen.tsx +162 -125
  34. package/src/screens/EditProfileScreen.tsx +165 -0
  35. package/src/screens/LoginScreen.tsx +59 -72
  36. package/src/screens/NewAddressScreen.tsx +16 -10
  37. package/src/screens/ProductDetailScreen.tsx +334 -234
  38. package/src/screens/ProfileScreen.tsx +190 -200
  39. package/src/screens/RegisterScreen.tsx +51 -70
  40. package/src/screens/SearchResultsScreen.tsx +2 -1
  41. package/src/screens/ShopScreen.tsx +260 -384
  42. package/src/screens/WishlistScreen.tsx +226 -224
  43. package/src/styles/globals.css +9 -0
  44. package/src/screens/CategoriesScreen.tsx +0 -122
  45. package/src/screens/HomeScreen.tsx +0 -211
@@ -0,0 +1,116 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { CreditCard, Plus, Trash2 } from 'lucide-react';
5
+ import { usePaymentMethods } from '@/hooks/usePaymentMethods';
6
+ import { EmptyState } from './EmptyState';
7
+ import { Button } from './ui/Button';
8
+ import { Badge } from './ui/Badge';
9
+
10
+ export function AccountPaymentTab() {
11
+ const { paymentMethods, isLoading, error, deletePaymentMethod } = usePaymentMethods();
12
+ const [deletingId, setDeletingId] = useState<string | null>(null);
13
+
14
+ const handleDelete = async (paymentMethodId: string) => {
15
+ if (!confirm('Are you sure you want to remove this payment method?')) return;
16
+
17
+ setDeletingId(paymentMethodId);
18
+ try {
19
+ await deletePaymentMethod(paymentMethodId);
20
+ } catch (error) {
21
+ console.error('Failed to delete payment method:', error);
22
+ alert('Failed to delete payment method. Please try again.');
23
+ } finally {
24
+ setDeletingId(null);
25
+ }
26
+ };
27
+
28
+ if (isLoading) {
29
+ return (
30
+ <div className="p-6">
31
+ <div className="space-y-4">
32
+ {Array.from({ length: 2 }).map((_, index) => (
33
+ <div
34
+ key={index}
35
+ className="h-24 animate-pulse rounded-lg border border-slate-100 bg-slate-50"
36
+ />
37
+ ))}
38
+ </div>
39
+ </div>
40
+ );
41
+ }
42
+
43
+ if (error) {
44
+ return (
45
+ <div className="p-6">
46
+ <div className="rounded-lg border border-red-100 bg-red-50 p-6 text-sm text-red-700">
47
+ {error.message}
48
+ </div>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ return (
54
+ <div className="p-6">
55
+ <div className="mb-6 flex items-center justify-between">
56
+ <div>
57
+ <h3 className="text-lg font-semibold text-slate-900">Payment Methods</h3>
58
+ <p className="text-sm text-slate-600">Manage your saved payment methods</p>
59
+ </div>
60
+ <Button variant="primary" size="sm">
61
+ <Plus className="h-4 w-4" />
62
+ Add Card
63
+ </Button>
64
+ </div>
65
+
66
+ {paymentMethods.length === 0 ? (
67
+ <EmptyState
68
+ icon={CreditCard}
69
+ title="No payment methods"
70
+ description="Add a payment method to make checkout faster and easier."
71
+ actionLabel="Add your first card"
72
+ onAction={() => {
73
+ // TODO: Implement add payment method modal
74
+ alert('Add payment method functionality coming soon');
75
+ }}
76
+ />
77
+ ) : (
78
+ <div className="space-y-4">
79
+ {paymentMethods.map((method) => (
80
+ <div
81
+ key={method.id}
82
+ className="flex items-center justify-between rounded-lg border border-slate-200 bg-white p-4 hover:shadow-md transition-shadow"
83
+ >
84
+ <div className="flex items-center gap-4">
85
+ <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary-50">
86
+ <CreditCard className="h-6 w-6 text-primary-600" />
87
+ </div>
88
+ {/* <div>
89
+ <div className="flex items-center gap-2">
90
+ <p className="font-medium text-slate-900">
91
+ {method.card?.brand || 'Card'} •••• {method.card?.last4 || '****'}
92
+ </p>
93
+ {method.isDefault && (
94
+ <Badge variant="success">Default</Badge>
95
+ )}
96
+ </div>
97
+ <p className="text-sm text-slate-500">
98
+ Expires {method.card?.expMonth}/{method.card?.expYear}
99
+ </p>
100
+ </div> */}
101
+ </div>
102
+ <button
103
+ onClick={() => handleDelete(method.id)}
104
+ disabled={deletingId === method.id}
105
+ className="inline-flex items-center gap-2 rounded-full border border-red-200 px-3 py-1 text-sm font-semibold text-red-600 transition hover:border-red-300 hover:text-red-700 disabled:opacity-50"
106
+ >
107
+ <Trash2 className="h-4 w-4" />
108
+ {deletingId === method.id ? 'Deleting...' : 'Delete'}
109
+ </button>
110
+ </div>
111
+ ))}
112
+ </div>
113
+ )}
114
+ </div>
115
+ );
116
+ }
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { Heart, ShoppingCart } from 'lucide-react';
5
+ import { useWishlist } from '@/providers/WishlistProvider';
6
+ import { useWishlistProducts } from '@/hooks/useWishlistProducts';
7
+ import { ProductCard } from './ProductCard';
8
+ import { EmptyState } from './EmptyState';
9
+
10
+ export function AccountSavedItemsTab() {
11
+ const { products: wishlistProductIds } = useWishlist();
12
+
13
+ const { products: wishlistProducts, isLoading, error } = useWishlistProducts(
14
+ wishlistProductIds as string[]
15
+ );
16
+
17
+ const products = wishlistProducts || [];
18
+ console.log(products);
19
+
20
+ if (isLoading) {
21
+ return (
22
+ <div className="p-6">
23
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
24
+ {Array.from({ length: 3 }).map((_, index) => (
25
+ <div
26
+ key={index}
27
+ className="h-80 animate-pulse rounded-lg border border-slate-100 bg-slate-50"
28
+ />
29
+ ))}
30
+ </div>
31
+ </div>
32
+ );
33
+ }
34
+
35
+ if (error) {
36
+ return (
37
+ <div className="p-6">
38
+ <div className="rounded-lg border border-red-100 bg-red-50 p-6 text-sm text-red-700">
39
+ {error.message}
40
+ </div>
41
+ </div>
42
+ );
43
+ }
44
+
45
+ if (products.length === 0) {
46
+ return (
47
+ <div className="p-6">
48
+ <EmptyState
49
+ icon={Heart}
50
+ title="No saved items"
51
+ description="Items you save will appear here for easy access later."
52
+ />
53
+ </div>
54
+ );
55
+ }
56
+
57
+ return (
58
+ <div className="pb-24 p-6">
59
+ <div className="p-6 bg-white rounded-xl">
60
+ <div className="mb-4">
61
+ <h3 className="text-lg font-semibold text-secondary">
62
+ Saved for Later
63
+ </h3>
64
+ <p className="text-sm text-secondary">
65
+ {products.length} {products.length === 1 ? 'item' : 'items'} saved
66
+ </p>
67
+ </div>
68
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
69
+ {products.map((product) => (
70
+ <ProductCard key={product._id} product={product} />
71
+ ))}
72
+ </div>
73
+ </div>
74
+ </div>
75
+ );
76
+ }
@@ -0,0 +1,116 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { Bell, Lock, Trash2 } from 'lucide-react';
5
+ import { Button } from './ui/Button';
6
+ import { useRouter } from 'next/navigation';
7
+ import { useBasePath } from '@/providers/BasePathProvider';
8
+
9
+ export function AccountSettingsTab() {
10
+ const router = useRouter();
11
+ const { buildPath } = useBasePath();
12
+ const [emailNotifications, setEmailNotifications] = useState(true);
13
+ const [orderUpdates, setOrderUpdates] = useState(true);
14
+ const [promotionalEmails, setPromotionalEmails] = useState(false);
15
+
16
+ return (
17
+ <div className="p-6 space-y-6">
18
+ {/* Account Preferences */}
19
+ <div className="rounded-2xl border border-slate-200 bg-white p-6">
20
+ <div className="flex items-center gap-2 mb-4">
21
+ <Bell className="h-5 w-5 text-secondary" />
22
+ <h3 className="text-lg font-semibold text-secondary">Account Preferences</h3>
23
+ </div>
24
+ <div className="space-y-4">
25
+ <label className="flex items-center justify-between gap-4 rounded-lg border border-slate-200 bg-slate-50 px-4 py-3">
26
+ <div>
27
+ <p className="text-sm font-medium text-secondary">Email Notifications</p>
28
+ <p className="text-xs text-muted">Receive updates about your orders</p>
29
+ </div>
30
+ <input
31
+ type="checkbox"
32
+ checked={emailNotifications}
33
+ onChange={(e) => setEmailNotifications(e.target.checked)}
34
+ className="h-4 w-4 rounded border-primary-300 text-secondary focus:ring-primary-500"
35
+ />
36
+ </label>
37
+ <label className="flex items-center justify-between gap-4 rounded-lg border border-slate-200 bg-slate-50 px-4 py-3">
38
+ <div>
39
+ <p className="text-sm font-medium text-secondary">Order Updates</p>
40
+ <p className="text-xs text-muted">Get notified about shipping status</p>
41
+ </div>
42
+ <input
43
+ type="checkbox"
44
+ checked={orderUpdates}
45
+ onChange={(e) => setOrderUpdates(e.target.checked)}
46
+ className="h-4 w-4 rounded border-primary-300 text-secondary focus:ring-primary-500"
47
+ />
48
+ </label>
49
+ <label className="flex items-center justify-between gap-4 rounded-lg border border-slate-200 bg-slate-50 px-4 py-3">
50
+ <div>
51
+ <p className="text-sm font-medium text-secondary">Promotional Emails</p>
52
+ <p className="text-xs text-muted">Receive special offers and discounts</p>
53
+ </div>
54
+ <input
55
+ type="checkbox"
56
+ checked={promotionalEmails}
57
+ onChange={(e) => setPromotionalEmails(e.target.checked)}
58
+ className="h-4 w-4 rounded border-primary-300 text-secondary focus:ring-primary-500"
59
+ />
60
+ </label>
61
+ </div>
62
+ </div>
63
+
64
+ {/* Security */}
65
+ <div className="rounded-2xl border border-slate-200 bg-white p-6">
66
+ <div className="flex items-center gap-2 mb-4">
67
+ <Lock className="h-5 w-5 text-secondary" />
68
+ <h3 className="text-lg font-semibold text-secondary">Security</h3>
69
+ </div>
70
+ <div className="space-y-3">
71
+ <div className="flex items-center justify-between rounded-lg border border-slate-200 bg-slate-50 px-4 py-3">
72
+ <div>
73
+ <p className="text-sm font-medium text-secondary">Change Password</p>
74
+ <p className="text-xs text-muted">Update your account password</p>
75
+ </div>
76
+ <Button
77
+ variant="outline"
78
+ size="sm"
79
+ onClick={() => router.push(buildPath('/account/change-password'))}
80
+ >
81
+ Change
82
+ </Button>
83
+ </div>
84
+ {/* <div className="flex items-center justify-between rounded-lg border border-slate-200 bg-slate-50 px-4 py-3">
85
+ <div>
86
+ <p className="text-sm font-medium text-secondary">Two-Factor Authentication</p>
87
+ <p className="text-xs text-muted">Add an extra layer of security</p>
88
+ </div>
89
+ <Button variant="outline" size="sm">
90
+ Enable
91
+ </Button>
92
+ </div> */}
93
+ <div className="flex items-center justify-between rounded-lg border border-red-100 bg-red-50 px-4 py-3">
94
+ <div>
95
+ <p className="text-sm font-medium text-red-900">Delete Account</p>
96
+ <p className="text-xs text-red-600">Permanently delete your account and data</p>
97
+ </div>
98
+ <Button
99
+ variant="outline"
100
+ size="sm"
101
+ className="border-red-200 text-red-600 hover:bg-red-50"
102
+ onClick={() => {
103
+ if (confirm('Are you sure you want to delete your account? This action cannot be undone.')) {
104
+ alert('Account deletion functionality coming soon');
105
+ }
106
+ }}
107
+ >
108
+ <Trash2 className="h-4 w-4" />
109
+ Delete
110
+ </Button>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ );
116
+ }
@@ -8,7 +8,7 @@ import { addressSchema, type AddressFormData } from '@/lib/validations/address';
8
8
  import { AddressesApi } from '@/lib/Apis/apis/addresses-api';
9
9
  import { Address } from '@/lib/Apis';
10
10
  import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
11
- import { toast } from 'sonner';
11
+ import { useNotification } from '@/providers/NotificationProvider';
12
12
 
13
13
  interface AddressFormModalProps {
14
14
  isOpen: boolean;
@@ -20,6 +20,7 @@ interface AddressFormModalProps {
20
20
 
21
21
  export function AddressFormModal({ isOpen, onClose, onAddressAdded, onAddressUpdated, initialAddress }: AddressFormModalProps) {
22
22
  const [isSubmitting, setIsSubmitting] = useState(false);
23
+ const notification = useNotification();
23
24
 
24
25
  const {
25
26
  register,
@@ -69,7 +70,10 @@ export function AddressFormModal({ isOpen, onClose, onAddressAdded, onAddressUpd
69
70
  country: data.country,
70
71
  phone: data.phone,
71
72
  }, initialAddress.id);
72
- toast.success('Address updated successfully');
73
+ notification.success(
74
+ 'Address updated',
75
+ 'Your address has been updated successfully.'
76
+ );
73
77
  reset();
74
78
  onClose();
75
79
  if (onAddressUpdated) onAddressUpdated(response.data);
@@ -85,16 +89,25 @@ export function AddressFormModal({ isOpen, onClose, onAddressAdded, onAddressUpd
85
89
  phone: data.phone,
86
90
  });
87
91
  if (response.status === 201) {
88
- toast.success('Address added successfully');
92
+ notification.success(
93
+ 'Address added',
94
+ 'Your new address has been saved to your account.'
95
+ );
89
96
  reset();
90
97
  onClose();
91
98
  if (onAddressAdded) onAddressAdded(response.data);
92
99
  } else {
93
- toast.error('Failed to add address');
100
+ notification.error(
101
+ 'Could not add address',
102
+ 'We could not save this address. Please try again.'
103
+ );
94
104
  }
95
105
  }
96
106
  } catch (error) {
97
- toast.error('Failed to add address');
107
+ notification.error(
108
+ 'Could not save address',
109
+ 'Something went wrong while saving your address. Please try again.'
110
+ );
98
111
  } finally {
99
112
  setIsSubmitting(false);
100
113
  }
@@ -162,16 +175,16 @@ export function AddressFormModal({ isOpen, onClose, onAddressAdded, onAddressUpd
162
175
  />
163
176
  </div>
164
177
  <div className="flex justify-end gap-4">
165
- <Button
178
+ <button
166
179
  type="button"
167
- variant="outline"
168
180
  onClick={onClose}
181
+ className='flex flex-row items-center gap-2 px-6 py-2 border border-slate-200 rounded-xl text-slate-700 hover:opacity-80 transition-colors text-secondary text-sm'
169
182
  >
170
183
  Cancel
171
- </Button>
172
- <Button type="submit" disabled={isSubmitting}>
184
+ </button>
185
+ <button type="submit" disabled={isSubmitting} className='flex flex-row items-center gap-2 px-6 py-2 border border-slate-200 rounded-xl text-slate-700 hover:opacity-80 transition-colors bg-secondary text-white text-sm'>
173
186
  {isSubmitting ? 'Adding Address...' : 'Add Address'}
174
- </Button>
187
+ </button>
175
188
  </div>
176
189
  </form>
177
190
  </Modal>
@@ -51,77 +51,81 @@ export function CartItem({ item }: CartItemProps) {
51
51
  initial={{ opacity: 0, y: 20 }}
52
52
  animate={{ opacity: 1, y: 0 }}
53
53
  exit={{ opacity: 0, x: -100 }}
54
- className="relative bg-white rounded-lg shadow-sm border border-gray-200 p-4"
54
+ className="bg-white border-2 border-gray-100 rounded-[24px] p-6 hover:border-[#5B9BD5]/30 transition-all duration-300"
55
55
  >
56
- {/* Delete Icon - Top Right */}
57
- <button
58
- onClick={handleRemove}
59
- className="absolute top-4 right-4 p-1 text-gray-400 hover:text-red-600 transition-colors"
60
- aria-label="Remove item"
61
- >
62
- <Trash2 className="w-5 h-5" />
63
- </button>
56
+
64
57
 
65
58
  <div className="flex gap-4 pr-8">
66
59
  {/* Product Image */}
67
- <div className="relative w-20 h-24 rounded-md overflow-hidden flex-shrink-0 bg-gray-100">
60
+ <div className="w-28 h-28 rounded-[16px] overflow-hidden bg-gray-50 shrink-0">
68
61
  <Image
69
62
  src={item.productVariantData.productMedia[0]?.file || '/placeholder-product.jpg'}
70
63
  alt={item.productVariantData.name}
71
- fill
72
- className="object-cover"
73
- sizes="80px"
64
+ className="w-full h-full object-cover"
65
+ height={112}
66
+ width={112}
74
67
  />
75
68
  </div>
76
69
 
77
70
  {/* Product Info */}
78
71
  <div className="flex-1 min-w-0">
79
- <h3 className="text-base font-bold text-gray-900">
80
- {item.productVariantData.name}
81
- </h3>
82
-
83
- {/* Attributes */}
84
- {attributes.length > 0 && (
85
- <p className="text-sm text-gray-500 mt-1">
86
- {attributes.join(' ')}
87
- </p>
88
- )}
89
-
90
- {/* Quantity Controls */}
91
- <div className="flex items-center gap-2 mt-3">
92
- <button
93
- onClick={() => handleUpdateQuantity(item.quantity - 1)}
94
- disabled={isUpdating || item.quantity <= 1}
95
- className="p-1 hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors rounded"
96
- >
97
- <Minus className="w-4 h-4 text-gray-600" />
98
- </button>
99
- <span className="px-3 font-medium min-w-[2rem] text-center text-gray-900">
100
- {isUpdating ? (
101
- <span className="inline-block h-4 w-4 align-middle animate-spin rounded-full border-2 border-gray-300 border-t-gray-600" />
102
- ) : (
103
- item.quantity
104
- )}
72
+ <div className="flex items-start justify-between gap-4 mb-3">
73
+ <div className="flex-1 min-w-0">
74
+ <h3 className="font-['Poppins',sans-serif] font-semibold text-[#2B4B7C] mb-2">
75
+ {item.productVariantData.name}
76
+ </h3>
77
+ <div className="flex flex-wrap items-center gap-3">
78
+ <span className="font-['Poppins',sans-serif] text-[12px] text-[#676c80]">
79
+ Variant: <span className="font-medium text-[#2B4B7C]">{item.productVariantData.name}</span>
105
80
  </span>
106
- <button
107
- onClick={() => handleUpdateQuantity(item.quantity + 1)}
108
- disabled={isUpdating || item.quantity >= (item.productVariantData.inventoryCount || 999)}
109
- className="p-1 hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors rounded"
110
- >
111
- <Plus className="w-4 h-4 text-gray-600" />
112
- </button>
113
81
  </div>
114
- </div>
82
+ </div>
83
+
84
+ {/* Delete Icon - Top Right */}
85
+ <button
86
+ onClick={handleRemove}
87
+ className="p-2 hover:bg-red-50 rounded-full transition-colors group"
88
+ aria-label="Remove item"
89
+ >
90
+ <Trash2 className="size-5 text-[#676c80] group-hover:text-red-500 transition-colors" />
91
+ </button>
92
+ </div>
93
+
94
+ {/* Quantity and Price */}
95
+ <div className="flex items-center justify-between gap-4">
96
+ {/* Quantity Controls */}
97
+ <div className="flex items-center gap-3 bg-gray-50 rounded-full px-4 py-2">
98
+ <button
99
+ onClick={() => handleUpdateQuantity(item.quantity - 1)}
100
+ disabled={isUpdating || item.quantity <= 1}
101
+ className="p-1 hover:bg-white rounded-full transition-colors"
102
+ >
103
+ <Minus className="size-4 text-[#2B4B7C]" />
104
+ </button>
105
+ <span className="font-['Poppins',sans-serif] font-semibold text-[14px] text-[#2B4B7C] min-w-[20px] text-center">
106
+ {item.quantity}
107
+ </span>
108
+ <button
109
+ onClick={() => handleUpdateQuantity(item.quantity + 1)}
110
+ disabled={isUpdating || item.quantity >= (item.productVariantData.inventoryCount || 999)}
111
+ className="p-1 hover:bg-white rounded-full transition-colors"
112
+ >
113
+ <Plus className="size-4 text-[#2B4B7C]" />
114
+ </button>
115
+ </div>
116
+
117
+ {/* Price */}
118
+ <div className="text-right">
119
+ <p className="font-['Poppins',sans-serif] font-bold text-[18px] text-[#E67E50]">
120
+ {formatPrice(itemTotal)}
121
+ </p>
122
+ <p className="font-['Poppins',sans-serif] text-[11px] text-[#676c80]">
123
+ {formatPrice(unitPrice)} each
124
+ </p>
125
+ </div>
126
+ </div>
127
+ </div>
115
128
 
116
- {/* Pricing - Right Side */}
117
- <div className="text-right flex-shrink-0">
118
- <p className="text-lg font-bold text-orange-600">
119
- {formatPrice(itemTotal)}
120
- </p>
121
- <p className="text-sm text-gray-500 mt-1">
122
- {formatPrice(unitPrice)} each
123
- </p>
124
- </div>
125
129
  </div>
126
130
  </motion.div>
127
131
  );
@@ -2,36 +2,50 @@
2
2
 
3
3
  import React, { useState } from 'react';
4
4
  import { motion, AnimatePresence } from 'framer-motion';
5
- import { ShoppingCart, User, Menu, X, Search, Heart } from 'lucide-react';
5
+ import { ShoppingCart, User, Menu, X, Search, Heart, ChevronDown, Settings, LogOut } from 'lucide-react';
6
6
  import { useAuth } from '@/providers/AuthProvider';
7
7
  import { useCart } from '@/providers/CartProvider';
8
8
  import { useTheme } from '@/providers/ThemeProvider';
9
9
  import { useWishlist } from '@/providers/WishlistProvider';
10
10
  import { useBasePath } from '@/providers/BasePathProvider';
11
+ import { getInitials } from '@/lib/utils/format';
12
+ import { useRouter } from 'next/navigation';
11
13
  import Link from 'next/link';
12
14
  import Image from 'next/image';
13
15
 
14
16
  export function Header() {
15
17
  const { config } = useTheme();
16
- const { user, isAuthenticated } = useAuth();
18
+ const { user, isAuthenticated, logout } = useAuth();
17
19
  const { cart } = useCart() || { cart: { itemCount: 0 } };
18
20
  const { getWishlistCount } = useWishlist();
19
21
  const wishlistCount = getWishlistCount?.() || 0;
20
22
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
21
23
  const [isSearchOpen, setIsSearchOpen] = useState(false);
24
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
25
+ const [isLoggingOut, setIsLoggingOut] = useState(false);
22
26
  const [searchQuery, setSearchQuery] = useState('');
23
27
  const { buildPath } = useBasePath();
28
+ const router = useRouter();
29
+
30
+ const handleLogout = async () => {
31
+ setIsLoggingOut(true);
32
+ try {
33
+ await logout();
34
+ router.push(buildPath('/'));
35
+ } catch (error) {
36
+ console.error('Logout failed:', error);
37
+ } finally {
38
+ setIsLoggingOut(false);
39
+ setIsDropdownOpen(false);
40
+ }
41
+ };
24
42
 
25
43
  const navLinks = [
26
44
  { href: buildPath('/shop'), label: 'Shop' },
27
- { href: buildPath('/categories'), label: 'Categories' },
28
- { href: buildPath('/orders'), label: 'Orders' },
29
- { href: buildPath('/about'), label: 'About' },
30
- { href: buildPath('/contact'), label: 'Contact' },
31
45
  ];
32
46
 
33
47
  return (
34
- <header className="sticky top-0 z-40 bg-white/80 backdrop-blur-xl border-b border-gray-200 shadow-sm">
48
+ <header className="sticky top-0 z-10 bg-white/80 backdrop-blur-xl border-b border-gray-200 shadow-sm">
35
49
  <div className="container mx-auto px-4">
36
50
  <div className="flex items-center justify-between h-20">
37
51
  {/* Logo */}
@@ -44,9 +58,6 @@ export function Header() {
44
58
  className="object-contain"
45
59
  />
46
60
  </div>
47
- <span className="text-2xl font-bold text-gray-900 hidden sm:block">
48
- {config.storeName}
49
- </span>
50
61
  </Link>
51
62
 
52
63
  {/* Desktop Navigation */}
@@ -134,12 +145,54 @@ export function Header() {
134
145
 
135
146
  {/* User Menu */}
136
147
  {isAuthenticated ? (
137
- <Link
138
- href={buildPath('/account')}
139
- className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
140
- >
141
- <User className="w-6 h-6 text-gray-700" />
142
- </Link>
148
+ <div className="relative">
149
+ <button
150
+ onClick={() => setIsDropdownOpen(!isDropdownOpen)}
151
+ className="flex items-center gap-2 rounded-full border border-slate-200 bg-white px-3 py-2 hover:bg-slate-50 transition-colors"
152
+ >
153
+ <div className="flex h-7 w-7 items-center justify-center rounded-full bg-primary-100 text-xs font-semibold text-primary-700">
154
+ {getInitials(user?.firstname || '', user?.lastname || '')}
155
+ </div>
156
+ <ChevronDown className={`h-4 w-4 text-slate-400 transition-transform ${isDropdownOpen ? 'rotate-180' : ''}`} />
157
+ </button>
158
+
159
+ {/* Dropdown Menu */}
160
+ {isDropdownOpen && (
161
+ <>
162
+ <div
163
+ className="fixed inset-0 z-10"
164
+ onClick={() => setIsDropdownOpen(false)}
165
+ />
166
+ <div className="absolute right-0 top-full mt-2 w-56 rounded-lg border border-slate-200 bg-white shadow-lg z-20">
167
+ <div className="p-2">
168
+ <div className="px-3 py-2 border-b border-slate-200 mb-1">
169
+ <p className="text-sm font-medium text-secondary truncate">
170
+ {user?.firstname} {user?.lastname}
171
+ </p>
172
+ <p className="text-xs text-slate-500 truncate">{user?.email}</p>
173
+ </div>
174
+ <Link
175
+ href={buildPath('/account')}
176
+ onClick={() => setIsDropdownOpen(false)}
177
+ className="flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm text-slate-700 hover:bg-slate-100 transition-colors"
178
+ >
179
+ <User className="h-4 w-4" />
180
+ My Account
181
+ </Link>
182
+ <div className="my-1 border-t border-slate-200" />
183
+ <button
184
+ onClick={handleLogout}
185
+ disabled={isLoggingOut}
186
+ className="flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm text-red-600 hover:bg-red-50 transition-colors disabled:opacity-50"
187
+ >
188
+ <LogOut className="h-4 w-4" />
189
+ {isLoggingOut ? 'Signing out...' : 'Sign Out'}
190
+ </button>
191
+ </div>
192
+ </div>
193
+ </>
194
+ )}
195
+ </div>
143
196
  ) : (
144
197
  <Link
145
198
  href={buildPath('/login')}