hey-pharmacist-ecommerce 1.1.12 → 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 (49) hide show
  1. package/dist/index.d.mts +2 -4
  2. package/dist/index.d.ts +2 -4
  3. package/dist/index.js +1123 -972
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1123 -971
  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/FilterChips.tsx +54 -80
  17. package/src/components/Header.tsx +69 -16
  18. package/src/components/Notification.tsx +148 -0
  19. package/src/components/OrderCard.tsx +89 -56
  20. package/src/components/ProductCard.tsx +215 -178
  21. package/src/components/QuickViewModal.tsx +314 -0
  22. package/src/components/TabNavigation.tsx +48 -0
  23. package/src/components/ui/Button.tsx +1 -1
  24. package/src/components/ui/ConfirmModal.tsx +84 -0
  25. package/src/hooks/useOrders.ts +1 -0
  26. package/src/hooks/usePaymentMethods.ts +58 -0
  27. package/src/index.ts +0 -1
  28. package/src/providers/CartProvider.tsx +22 -6
  29. package/src/providers/EcommerceProvider.tsx +8 -7
  30. package/src/providers/FavoritesProvider.tsx +10 -3
  31. package/src/providers/NotificationProvider.tsx +79 -0
  32. package/src/providers/WishlistProvider.tsx +34 -9
  33. package/src/screens/AddressesScreen.tsx +72 -61
  34. package/src/screens/CartScreen.tsx +48 -32
  35. package/src/screens/ChangePasswordScreen.tsx +155 -0
  36. package/src/screens/CheckoutScreen.tsx +162 -125
  37. package/src/screens/EditProfileScreen.tsx +165 -0
  38. package/src/screens/LoginScreen.tsx +59 -72
  39. package/src/screens/NewAddressScreen.tsx +16 -10
  40. package/src/screens/OrdersScreen.tsx +91 -148
  41. package/src/screens/ProductDetailScreen.tsx +334 -234
  42. package/src/screens/ProfileScreen.tsx +190 -200
  43. package/src/screens/RegisterScreen.tsx +51 -70
  44. package/src/screens/SearchResultsScreen.tsx +2 -1
  45. package/src/screens/ShopScreen.tsx +260 -384
  46. package/src/screens/WishlistScreen.tsx +226 -224
  47. package/src/styles/globals.css +9 -0
  48. package/src/screens/CategoriesScreen.tsx +0 -122
  49. package/src/screens/HomeScreen.tsx +0 -211
@@ -2,12 +2,10 @@
2
2
 
3
3
  import React, { useState } from 'react';
4
4
  import { motion } from 'framer-motion';
5
- import { useForm } from 'react-hook-form';
6
- import { zodResolver } from '@hookform/resolvers/zod';
7
- import { z } from 'zod';
8
5
  import Link from 'next/link';
9
6
  import { useRouter } from 'next/navigation';
10
7
  import {
8
+ ArrowRight,
11
9
  Heart,
12
10
  HeartPulse,
13
11
  LogOut,
@@ -20,60 +18,32 @@ import {
20
18
  User,
21
19
  } from 'lucide-react';
22
20
  import { Button } from '@/components/ui/Button';
23
- import { Input } from '@/components/ui/Input';
24
21
  import { useAuth } from '@/providers/AuthProvider';
25
- import { toast } from 'sonner';
26
22
  import { getInitials } from '@/lib/utils/format';
27
23
  import { useBasePath } from '@/providers/BasePathProvider';
28
24
 
29
- const profileSchema = z.object({
30
- firstName: z.string().min(2, 'First name is required'),
31
- lastName: z.string().min(2, 'Last name is required'),
32
- email: z.string().email('Enter a valid email address'),
33
- phone: z.string().optional(),
34
- });
35
-
36
- type ProfileFormData = z.infer<typeof profileSchema>;
37
-
38
25
  export function ProfileScreen() {
39
26
  const router = useRouter();
40
- const { user, updateUser, logout } = useAuth();
27
+ const { user, logout, isLoading } = useAuth();
41
28
  const { buildPath } = useBasePath();
42
- const [isSubmitting, setIsSubmitting] = useState(false);
29
+ const [isLoggingOut, setIsLoggingOut] = useState(false);
30
+ const [logoutError, setLogoutError] = useState<string | null>(null);
43
31
 
44
- const {
45
- register,
46
- handleSubmit,
47
- formState: { errors },
48
- } = useForm<ProfileFormData>({
49
- resolver: zodResolver(profileSchema),
50
- defaultValues: {
51
- firstName: user?.firstname || '',
52
- lastName: user?.lastname || '',
53
- email: user?.email || '',
54
- phone: user?.phoneNumber || '',
55
- },
56
- });
57
-
58
- const onSubmit = async (data: ProfileFormData) => {
59
- setIsSubmitting(true);
32
+ const handleLogout = async () => {
33
+ setIsLoggingOut(true);
34
+ setLogoutError(null);
60
35
  try {
61
- await updateUser(data);
62
- toast.success('Profile updated successfully!');
36
+ await logout();
37
+ router.push(buildPath('/'));
63
38
  } catch (error: any) {
64
- toast.error(error.response?.data?.message || 'Failed to update profile');
39
+ setLogoutError(error?.response?.data?.message || 'Failed to logout. Please try again.');
65
40
  } finally {
66
- setIsSubmitting(false);
41
+ setIsLoggingOut(false);
67
42
  }
68
43
  };
69
44
 
70
- const handleLogout = async () => {
71
- await logout();
72
- toast.success('Logged out successfully');
73
- router.push(buildPath('/'));
74
- };
75
-
76
45
  if (!user) {
46
+ if (isLoading) return null;
77
47
  router.push(buildPath('/login'));
78
48
  return null;
79
49
  }
@@ -81,8 +51,8 @@ export function ProfileScreen() {
81
51
  const quickLinks = [
82
52
  {
83
53
  icon: Package,
84
- label: 'Order history',
85
- description: 'Track shipments and download invoices',
54
+ label: 'Orders & invoices',
55
+ description: 'Track shipments, invoices, and receipts',
86
56
  href: buildPath('/orders'),
87
57
  },
88
58
  {
@@ -100,188 +70,208 @@ export function ProfileScreen() {
100
70
  ];
101
71
 
102
72
  return (
103
- <div className="min-h-screen bg-slate-50">
104
- <section className="relative overflow-hidden bg-gradient-to-br from-[rgb(var(--header-from))] via-[rgb(var(--header-via))] to-[rgb(var(--header-to))] text-white mb-8">
105
- <div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(255,255,255,0.35),_transparent_60%)]" />
106
- <div className="relative container mx-auto px-4 py-16">
73
+ <div className="min-h-screen bg-slate-50 text-slate-900">
74
+ <div className="relative container mx-auto px-4 pb-16 pt-10">
75
+ <div className="rounded-3xl border border-slate-200 bg-white/95 p-8 shadow-xl shadow-primary-50 backdrop-blur">
76
+ <div className="mb-6 flex flex-wrap items-center justify-between gap-3">
77
+ <div className="text-sm text-slate-500">
78
+ Stay signed in to keep prescriptions and deliveries synced.
79
+ </div>
80
+ <div className="flex items-center gap-3">
81
+ {logoutError && (
82
+ <div className="flex items-start gap-2 rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700">
83
+ <ShieldCheck className="mt-[1px] h-4 w-4 text-red-500" />
84
+ <span>{logoutError}</span>
85
+ </div>
86
+ )}
87
+ <Button
88
+ type="button"
89
+ variant="outline"
90
+ size="sm"
91
+ className="border-red-200 text-red-600 hover:bg-red-50"
92
+ onClick={handleLogout}
93
+ disabled={isLoggingOut}
94
+ >
95
+ <LogOut className="h-4 w-4" />
96
+ {isLoggingOut ? 'Logging out...' : 'Log out'}
97
+ </Button>
98
+ </div>
99
+ </div>
107
100
  <motion.div
108
- initial={{ opacity: 0, y: 24 }}
101
+ initial={{ opacity: 0, y: 18 }}
109
102
  animate={{ opacity: 1, y: 0 }}
110
- className="flex flex-col gap-8 md:flex-row md:items-center md:justify-between"
103
+ className="grid gap-8 md:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)] md:items-center"
111
104
  >
112
- <div className="space-y-5">
113
- <span className="inline-flex items-center gap-2 rounded-full bg-white/15 px-3 py-1 text-sm font-semibold uppercase tracking-[0.35em] text-white/70 backdrop-blur">
105
+ <div className="space-y-4">
106
+ <div className="inline-flex items-center gap-2 rounded-full bg-slate-900 text-white px-4 py-1 text-xs font-semibold uppercase tracking-[0.32em]">
114
107
  <HeartPulse className="h-4 w-4" />
115
- My account
116
- </span>
117
- <h1 className="text-4xl font-bold md:text-5xl">
118
- Hello, {user.firstname} {user.lastname}
119
- </h1>
120
- <p className="max-w-2xl text-white/80 md:text-lg">
121
- Manage profile details, shipping preferences, and personalized recommendations. Our
122
- pharmacists keep your care plan up to date.
123
- </p>
124
- <div className="flex flex-wrap items-center gap-4 text-sm text-white/80">
125
- <span className="inline-flex items-center gap-2 rounded-full bg-white/15 px-4 py-2">
126
- <ShieldCheck className="h-4 w-4" />
127
- Account secured with multi-factor ready login
108
+ Account
109
+ </div>
110
+ <div>
111
+ <h1 className="text-4xl font-semibold md:text-5xl">
112
+ Welcome back, {user.firstname} {user.lastname}
113
+ </h1>
114
+ <p className="mt-3 max-w-2xl text-base text-slate-600">
115
+ Keep your identity and delivery details accurate for smoother refills, faster
116
+ shipping, and timely pharmacist guidance.
117
+ </p>
118
+ </div>
119
+ <div className="flex flex-wrap gap-3 text-sm text-slate-600">
120
+ <span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-2">
121
+ <ShieldCheck className="h-4 w-4 text-primary-600" />
122
+ Secure login enabled
123
+ </span>
124
+ <span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-2">
125
+ <Mail className="h-4 w-4 text-primary-600" />
126
+ {user.email}
127
+ </span>
128
+ <span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-2">
129
+ <Phone className="h-4 w-4 text-primary-600" />
130
+ {user.phoneNumber || 'Add phone for urgent updates'}
128
131
  </span>
129
132
  </div>
130
133
  </div>
131
- <div className="flex flex-col items-center gap-4 rounded-3xl bg-white/15 p-6 text-center backdrop-blur">
132
- <div className="flex h-24 w-24 items-center justify-center rounded-full bg-white/20 text-3xl font-bold text-white">
133
- {getInitials(user?.firstname || '', user?.lastname || '') || ''}
134
+
135
+ <div className="flex flex-col gap-4 rounded-2xl border border-slate-200 bg-slate-50/70 p-6 shadow-sm">
136
+ <div className="flex items-center gap-4">
137
+ <div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-white text-2xl font-semibold text-slate-900 shadow-sm">
138
+ {getInitials(user?.firstname || '', user?.lastname || '') || ''}
139
+ </div>
140
+ <div className="space-y-1">
141
+ <p className="text-sm text-slate-500">Signed in as</p>
142
+ <p className="text-lg font-semibold text-slate-900">{user.firstname}</p>
143
+ <p className="text-sm text-slate-500">{user.email}</p>
144
+ </div>
145
+ </div>
146
+ <div className="grid gap-3 sm:grid-cols-2">
147
+ <Button
148
+ variant="outline"
149
+ size="md"
150
+ className="border-slate-300 text-slate-800 hover:bg-white"
151
+ onClick={() => router.push(buildPath('/account/edit'))}
152
+ >
153
+ Edit profile
154
+ </Button>
155
+ <Button
156
+ variant="ghost"
157
+ size="md"
158
+ className="text-slate-700 hover:bg-white"
159
+ onClick={() => router.push(buildPath('/account/change-password'))}
160
+ >
161
+ Change password
162
+ </Button>
134
163
  </div>
135
- <p className="text-sm text-white/80">{user.email}</p>
136
- <Button
137
- variant="ghost"
138
- className="text-white hover:bg-white/20"
139
- onClick={() => router.push(buildPath('/account/change-password'))}
140
- >
141
- Change password
142
- </Button>
143
164
  </div>
144
165
  </motion.div>
145
166
  </div>
146
- </section>
147
167
 
148
- <div className="relative -mt-16 pb-20">
149
- <div className="container mx-auto px-4">
150
- <div className="grid gap-10 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
151
- <motion.div
152
- initial={{ opacity: 0, y: 24 }}
153
- animate={{ opacity: 1, y: 0 }}
154
- className="space-y-6"
155
- >
156
- <section className="rounded-3xl border border-slate-100 bg-white p-8 shadow-lg shadow-primary-50">
157
- <div className="flex items-center justify-between">
158
- <h2 className="text-xl font-semibold text-slate-900">
159
- Personal information
160
- </h2>
161
- <Sparkles className="h-5 w-5 text-primary-500" />
168
+ <div className="mt-10 grid gap-8 lg:grid-cols-[minmax(0,1.08fr)_minmax(0,0.92fr)] items-stretch">
169
+ <motion.div
170
+ initial={{ opacity: 0, y: 18 }}
171
+ animate={{ opacity: 1, y: 0 }}
172
+ className="space-y-6"
173
+ >
174
+ <section className="rounded-3xl border border-slate-200 bg-white p-7 shadow-lg shadow-primary-50/40 min-h-[420px] h-full flex flex-col">
175
+ <div className="flex items-center justify-between gap-3">
176
+ <div>
177
+ <p className="text-xs font-semibold uppercase tracking-[0.3em] text-slate-400">
178
+ Essentials
179
+ </p>
180
+ <h2 className="mt-1 text-xl font-semibold text-slate-900">Profile snapshot</h2>
162
181
  </div>
163
-
164
- <form onSubmit={handleSubmit(onSubmit)} className="mt-6 space-y-6">
165
- <div className="grid gap-4 md:grid-cols-2">
166
- <Input
167
- label="First name"
168
- placeholder="Taylor"
169
- {...register('firstName')}
170
- error={errors.firstName?.message}
171
- />
172
- <Input
173
- label="Last name"
174
- placeholder="Reed"
175
- {...register('lastName')}
176
- error={errors.lastName?.message}
177
- />
178
- </div>
179
-
180
- <div className="relative">
181
- <Input
182
- type="email"
183
- label="Email address"
184
- placeholder="you@example.com"
185
- className="pl-10"
186
- {...register('email')}
187
- error={errors.email?.message}
188
- />
189
- <Mail className="absolute left-3 top-[38px] h-4 w-4 text-slate-400" />
190
- </div>
191
-
192
- <div className="relative">
193
- <Input
194
- type="tel"
195
- label="Phone number"
196
- placeholder="+1 (555) 123-4567"
197
- className="pl-10"
198
- {...register('phone')}
199
- error={errors.phone?.message}
200
- />
201
- <Phone className="absolute left-3 top-[38px] h-4 w-4 text-slate-400" />
182
+ <Sparkles className="h-5 w-5 text-primary-500" />
183
+ </div>
184
+ <div className="mt-6 grid gap-4 md:grid-cols-2">
185
+ <div className="rounded-2xl border border-slate-200 bg-slate-50/70 p-5">
186
+ <p className="text-xs font-semibold uppercase tracking-[0.28em] text-slate-500">
187
+ Contact
188
+ </p>
189
+ <div className="mt-3 space-y-2 text-sm text-slate-600">
190
+ <p className="flex items-center gap-2 font-semibold text-slate-900">
191
+ <User className="h-4 w-4 text-primary-500" />
192
+ {user.firstname} {user.lastname}
193
+ </p>
194
+ <p className="flex items-center gap-2">
195
+ <Mail className="h-4 w-4 text-primary-500" />
196
+ {user.email}
197
+ </p>
198
+ <p className="flex items-center gap-2">
199
+ <Phone className="h-4 w-4 text-primary-500" />
200
+ {user.phoneNumber || 'Not provided'}
201
+ </p>
202
202
  </div>
203
+ <Button
204
+ variant="outline"
205
+ size="md"
206
+ className="mt-4 w-full border-slate-300 text-slate-800 hover:bg-white"
207
+ onClick={() => router.push(buildPath('/account/edit'))}
208
+ >
209
+ Update information
210
+ </Button>
211
+ </div>
203
212
 
204
- <div className="flex flex-wrap gap-4">
205
- <Button
206
- type="submit"
207
- size="lg"
208
- isLoading={isSubmitting}
209
- >
210
- Save changes
211
- </Button>
212
- <Button
213
- type="button"
214
- variant="outline"
215
- size="lg"
216
- onClick={() => router.push(buildPath('/orders'))}
217
- >
218
- View recent orders
219
- </Button>
213
+ <div className="rounded-2xl border border-slate-200 bg-slate-50/70 p-5">
214
+ <p className="text-xs font-semibold uppercase tracking-[0.28em] text-slate-500">
215
+ Security
216
+ </p>
217
+ <div className="mt-3 space-y-2 text-sm text-slate-600">
218
+ <p className="flex items-center gap-2 font-semibold text-slate-900">
219
+ <ShieldCheck className="h-4 w-4 text-primary-500" />
220
+ Multi-factor ready
221
+ </p>
222
+ <p>Protect your account with a fresh password and keep notifications on.</p>
220
223
  </div>
221
- </form>
222
- </section>
224
+ <Button
225
+ variant="primary"
226
+ size="md"
227
+ className="mt-4 w-full"
228
+ onClick={() => router.push(buildPath('/account/change-password'))}
229
+ >
230
+ Change password
231
+ </Button>
232
+ </div>
233
+ </div>
234
+ </section>
235
+ </motion.div>
223
236
 
224
- <section className="grid gap-4 md:grid-cols-2">
237
+ <motion.aside
238
+ initial={{ opacity: 0, y: 18 }}
239
+ animate={{ opacity: 1, y: 0 }}
240
+ transition={{ delay: 0.05 }}
241
+ className="space-y-4 h-full"
242
+ >
243
+ <section className="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm min-h-[420px] h-full flex flex-col">
244
+ <div className="flex items-center justify-between">
245
+ <div>
246
+ <p className="text-xs font-semibold uppercase tracking-[0.3em] text-slate-400">
247
+ Quick links
248
+ </p>
249
+ <h3 className="mt-1 text-lg font-semibold text-slate-900">Next steps</h3>
250
+ </div>
251
+ <Heart className="h-5 w-5 text-primary-500" />
252
+ </div>
253
+ <div className="mt-5 grid gap-3">
225
254
  {quickLinks.map((item) => (
226
255
  <Link
227
256
  key={item.href}
228
257
  href={item.href}
229
- className="group rounded-3xl border border-slate-100 bg-white p-6 shadow-sm transition hover:-translate-y-1 hover:shadow-lg"
258
+ className="group relative flex items-start gap-3 rounded-2xl border border-slate-200 bg-slate-50/70 p-4 transition duration-200 hover:-translate-y-0.5 hover:border-primary-200 hover:bg-white hover:shadow-md"
230
259
  >
231
- <div className="flex items-center gap-3">
232
- <span className="rounded-2xl bg-primary-50 p-3 text-primary-600">
233
- <item.icon className="h-5 w-5" />
234
- </span>
235
- <div>
236
- <p className="text-base font-semibold text-slate-900 group-hover:text-primary-600">
237
- {item.label}
238
- </p>
239
- <p className="text-sm text-slate-500">{item.description}</p>
240
- </div>
260
+ <span className="flex h-11 w-11 items-center justify-center rounded-xl bg-white text-primary-600 shadow-sm group-hover:bg-primary-600 group-hover:text-white">
261
+ <item.icon className="h-5 w-5" />
262
+ </span>
263
+ <div className="flex-1">
264
+ <p className="flex items-center gap-2 text-base font-semibold text-slate-900 group-hover:text-primary-700">
265
+ {item.label}
266
+ <ArrowRight className="h-4 w-4 opacity-0 transition group-hover:opacity-100" />
267
+ </p>
268
+ <p className="text-sm text-slate-500">{item.description}</p>
241
269
  </div>
242
270
  </Link>
243
271
  ))}
244
- </section>
245
- </motion.div>
246
-
247
- <motion.aside
248
- initial={{ opacity: 0, y: 24 }}
249
- animate={{ opacity: 1, y: 0 }}
250
- transition={{ delay: 0.1 }}
251
- className="space-y-6"
252
- >
253
- <div className="rounded-3xl border border-slate-100 bg-white p-6 shadow-lg shadow-primary-50">
254
- <h3 className="text-lg font-semibold text-slate-900">Care preferences</h3>
255
- <p className="mt-3 text-sm text-slate-600">
256
- Customize how we support you. Set refill reminders or manage communication
257
- preferences to stay aligned with your wellness goals.
258
- </p>
259
- <Button
260
- variant="outline"
261
- className="mt-4 w-full"
262
- onClick={() => router.push(buildPath('/account/preferences'))}
263
- >
264
- Manage preferences
265
- </Button>
266
272
  </div>
267
-
268
- <div className="rounded-3xl border border-primary-100 bg-primary-50/70 p-6 text-sm text-primary-700 shadow-sm">
269
- <p className="font-semibold uppercase tracking-[0.3em]">Pharmacist tip</p>
270
- <p className="mt-3 leading-relaxed">
271
- Keep your phone number current so pharmacists can reach you quickly with dosage
272
- advice or time-sensitive updates about your order.
273
- </p>
274
- </div>
275
-
276
- <button
277
- onClick={handleLogout}
278
- className="flex w-full items-center justify-center gap-2 rounded-3xl border border-red-200 bg-red-50 px-4 py-3 text-sm font-semibold text-red-600 transition hover:bg-red-100"
279
- >
280
- <LogOut className="h-4 w-4" />
281
- Log out
282
- </button>
283
- </motion.aside>
284
- </div>
273
+ </section>
274
+ </motion.aside>
285
275
  </div>
286
276
  </div>
287
277
  </div>