hey-pharmacist-ecommerce 1.0.4 → 1.0.6
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.
- package/README.md +107 -1
- package/dist/index.d.mts +3636 -316
- package/dist/index.d.ts +3636 -316
- package/dist/index.js +6802 -3865
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6756 -3817
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -14
- package/src/components/AddressFormModal.tsx +171 -0
- package/src/components/CartItem.tsx +17 -12
- package/src/components/FilterChips.tsx +195 -0
- package/src/components/Header.tsx +121 -71
- package/src/components/OrderCard.tsx +18 -25
- package/src/components/ProductCard.tsx +209 -72
- package/src/components/ui/Button.tsx +13 -5
- package/src/components/ui/Card.tsx +46 -0
- package/src/hooks/useAddresses.ts +83 -0
- package/src/hooks/useOrders.ts +37 -19
- package/src/hooks/useProducts.ts +55 -63
- package/src/hooks/useWishlistProducts.ts +75 -0
- package/src/index.ts +3 -19
- package/src/lib/Apis/api.ts +1 -0
- package/src/lib/Apis/apis/cart-api.ts +3 -3
- package/src/lib/Apis/apis/inventory-api.ts +0 -108
- package/src/lib/Apis/apis/stores-api.ts +70 -0
- package/src/lib/Apis/apis/wishlist-api.ts +447 -0
- package/src/lib/Apis/models/cart-item-populated.ts +0 -1
- package/src/lib/Apis/models/create-single-variant-product-dto.ts +3 -10
- package/src/lib/Apis/models/create-variant-dto.ts +26 -33
- package/src/lib/Apis/models/extended-product-dto.ts +20 -24
- package/src/lib/Apis/models/index.ts +2 -1
- package/src/lib/Apis/models/order-time-line-dto.ts +49 -0
- package/src/lib/Apis/models/order.ts +3 -8
- package/src/lib/Apis/models/populated-order.ts +3 -8
- package/src/lib/Apis/models/product-variant.ts +29 -0
- package/src/lib/Apis/models/update-product-variant-dto.ts +16 -23
- package/src/lib/Apis/models/wishlist.ts +51 -0
- package/src/lib/Apis/wrapper.ts +18 -7
- package/src/lib/api-adapter/index.ts +0 -12
- package/src/lib/types/index.ts +16 -61
- package/src/lib/utils/colors.ts +7 -4
- package/src/lib/utils/format.ts +1 -1
- package/src/lib/validations/address.ts +14 -0
- package/src/providers/AuthProvider.tsx +61 -31
- package/src/providers/CartProvider.tsx +18 -28
- package/src/providers/EcommerceProvider.tsx +7 -0
- package/src/providers/FavoritesProvider.tsx +86 -0
- package/src/providers/ThemeProvider.tsx +16 -1
- package/src/providers/WishlistProvider.tsx +174 -0
- package/src/screens/AddressesScreen.tsx +484 -0
- package/src/screens/CartScreen.tsx +120 -84
- package/src/screens/CategoriesScreen.tsx +120 -0
- package/src/screens/CheckoutScreen.tsx +919 -241
- package/src/screens/CurrentOrdersScreen.tsx +125 -61
- package/src/screens/HomeScreen.tsx +209 -0
- package/src/screens/LoginScreen.tsx +133 -88
- package/src/screens/NewAddressScreen.tsx +187 -0
- package/src/screens/OrdersScreen.tsx +162 -50
- package/src/screens/ProductDetailScreen.tsx +641 -190
- package/src/screens/ProfileScreen.tsx +192 -116
- package/src/screens/RegisterScreen.tsx +193 -144
- package/src/screens/SearchResultsScreen.tsx +165 -0
- package/src/screens/ShopScreen.tsx +1110 -146
- package/src/screens/WishlistScreen.tsx +428 -0
- package/src/lib/Apis/models/inventory-paginated-response.ts +0 -75
- package/src/lib/api/auth.ts +0 -81
- package/src/lib/api/cart.ts +0 -42
- package/src/lib/api/orders.ts +0 -53
- package/src/lib/api/products.ts +0 -51
- package/src/lib/api-adapter/auth-adapter.ts +0 -196
- package/src/lib/api-adapter/cart-adapter.ts +0 -193
- package/src/lib/api-adapter/mappers.ts +0 -147
- package/src/lib/api-adapter/orders-adapter.ts +0 -195
- package/src/lib/api-adapter/products-adapter.ts +0 -194
|
@@ -5,19 +5,30 @@ import { motion } from 'framer-motion';
|
|
|
5
5
|
import { useForm } from 'react-hook-form';
|
|
6
6
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
-
import
|
|
8
|
+
import Link from 'next/link';
|
|
9
|
+
import { useRouter } from 'next/navigation';
|
|
10
|
+
import {
|
|
11
|
+
Heart,
|
|
12
|
+
HeartPulse,
|
|
13
|
+
LogOut,
|
|
14
|
+
Mail,
|
|
15
|
+
MapPin,
|
|
16
|
+
Package,
|
|
17
|
+
Phone,
|
|
18
|
+
ShieldCheck,
|
|
19
|
+
Sparkles,
|
|
20
|
+
User,
|
|
21
|
+
} from 'lucide-react';
|
|
9
22
|
import { Button } from '@/components/ui/Button';
|
|
10
23
|
import { Input } from '@/components/ui/Input';
|
|
11
24
|
import { useAuth } from '@/providers/AuthProvider';
|
|
12
|
-
import { useRouter } from 'next/navigation';
|
|
13
25
|
import { toast } from 'sonner';
|
|
14
26
|
import { getInitials } from '@/lib/utils/format';
|
|
15
|
-
import Link from 'next/link';
|
|
16
27
|
|
|
17
28
|
const profileSchema = z.object({
|
|
18
29
|
firstName: z.string().min(2, 'First name is required'),
|
|
19
30
|
lastName: z.string().min(2, 'Last name is required'),
|
|
20
|
-
email: z.string().email('
|
|
31
|
+
email: z.string().email('Enter a valid email address'),
|
|
21
32
|
phone: z.string().optional(),
|
|
22
33
|
});
|
|
23
34
|
|
|
@@ -35,10 +46,10 @@ export function ProfileScreen() {
|
|
|
35
46
|
} = useForm<ProfileFormData>({
|
|
36
47
|
resolver: zodResolver(profileSchema),
|
|
37
48
|
defaultValues: {
|
|
38
|
-
firstName: user?.
|
|
39
|
-
lastName: user?.
|
|
49
|
+
firstName: user?.firstname || '',
|
|
50
|
+
lastName: user?.lastname || '',
|
|
40
51
|
email: user?.email || '',
|
|
41
|
-
phone: user?.
|
|
52
|
+
phone: user?.phoneNumber || '',
|
|
42
53
|
},
|
|
43
54
|
});
|
|
44
55
|
|
|
@@ -65,147 +76,212 @@ export function ProfileScreen() {
|
|
|
65
76
|
return null;
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
const
|
|
79
|
+
const quickLinks = [
|
|
69
80
|
{
|
|
70
81
|
icon: Package,
|
|
71
|
-
label: '
|
|
82
|
+
label: 'Order history',
|
|
83
|
+
description: 'Track shipments and download invoices',
|
|
72
84
|
href: '/orders',
|
|
73
|
-
description: 'View and track your orders',
|
|
74
85
|
},
|
|
75
86
|
{
|
|
76
87
|
icon: Heart,
|
|
77
88
|
label: 'Wishlist',
|
|
89
|
+
description: 'Curate go-to remedies and favorites',
|
|
78
90
|
href: '/wishlist',
|
|
79
|
-
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
icon: MapPin,
|
|
94
|
+
label: 'Delivery addresses',
|
|
95
|
+
description: 'Manage saved delivery locations',
|
|
96
|
+
href: '/account/addresses',
|
|
80
97
|
},
|
|
81
98
|
];
|
|
82
99
|
|
|
83
100
|
return (
|
|
84
|
-
<div className="min-h-screen bg-
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
<div className="min-h-screen bg-slate-50">
|
|
102
|
+
<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">
|
|
103
|
+
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(255,255,255,0.35),_transparent_60%)]" />
|
|
104
|
+
<div className="relative container mx-auto px-4 py-16">
|
|
105
|
+
<motion.div
|
|
106
|
+
initial={{ opacity: 0, y: 24 }}
|
|
107
|
+
animate={{ opacity: 1, y: 0 }}
|
|
108
|
+
className="flex flex-col gap-8 md:flex-row md:items-center md:justify-between"
|
|
109
|
+
>
|
|
110
|
+
<div className="space-y-5">
|
|
111
|
+
<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">
|
|
112
|
+
<HeartPulse className="h-4 w-4" />
|
|
113
|
+
My account
|
|
114
|
+
</span>
|
|
115
|
+
<h1 className="text-4xl font-bold md:text-5xl">
|
|
116
|
+
Hello, {user.firstname} {user.lastname}
|
|
117
|
+
</h1>
|
|
118
|
+
<p className="max-w-2xl text-white/80 md:text-lg">
|
|
119
|
+
Manage profile details, shipping preferences, and personalized recommendations. Our
|
|
120
|
+
pharmacists keep your care plan up to date.
|
|
121
|
+
</p>
|
|
122
|
+
<div className="flex flex-wrap items-center gap-4 text-sm text-white/80">
|
|
123
|
+
<span className="inline-flex items-center gap-2 rounded-full bg-white/15 px-4 py-2">
|
|
124
|
+
<ShieldCheck className="h-4 w-4" />
|
|
125
|
+
Account secured with multi-factor ready login
|
|
126
|
+
</span>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
<div className="flex flex-col items-center gap-4 rounded-3xl bg-white/15 p-6 text-center backdrop-blur">
|
|
130
|
+
<div className="flex h-24 w-24 items-center justify-center rounded-full bg-white/20 text-3xl font-bold text-white">
|
|
131
|
+
{getInitials(user?.firstname || '', user?.lastname || '') || ''}
|
|
132
|
+
</div>
|
|
133
|
+
<p className="text-sm text-white/80">{user.email}</p>
|
|
134
|
+
<Button
|
|
135
|
+
variant="ghost"
|
|
136
|
+
className="text-white hover:bg-white/20"
|
|
137
|
+
onClick={() => router.push('/account/change-password')}
|
|
138
|
+
>
|
|
139
|
+
Change password
|
|
140
|
+
</Button>
|
|
141
|
+
</div>
|
|
142
|
+
</motion.div>
|
|
143
|
+
</div>
|
|
144
|
+
</section>
|
|
145
|
+
|
|
146
|
+
<div className="relative -mt-16 pb-20">
|
|
147
|
+
<div className="container mx-auto px-4">
|
|
148
|
+
<div className="grid gap-10 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
|
|
99
149
|
<motion.div
|
|
100
|
-
initial={{ opacity: 0, y:
|
|
150
|
+
initial={{ opacity: 0, y: 24 }}
|
|
101
151
|
animate={{ opacity: 1, y: 0 }}
|
|
102
|
-
|
|
103
|
-
className="bg-white rounded-2xl p-6 shadow-sm"
|
|
152
|
+
className="space-y-6"
|
|
104
153
|
>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
154
|
+
<section className="rounded-3xl border border-slate-100 bg-white p-8 shadow-lg shadow-primary-50">
|
|
155
|
+
<div className="flex items-center justify-between">
|
|
156
|
+
<h2 className="text-xl font-semibold text-slate-900">
|
|
157
|
+
Personal information
|
|
158
|
+
</h2>
|
|
159
|
+
<Sparkles className="h-5 w-5 text-primary-500" />
|
|
109
160
|
</div>
|
|
110
|
-
<h3 className="text-xl font-bold text-gray-900">
|
|
111
|
-
{user.firstName} {user.lastName}
|
|
112
|
-
</h3>
|
|
113
|
-
<p className="text-gray-600">{user.email}</p>
|
|
114
|
-
</div>
|
|
115
161
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
162
|
+
<form onSubmit={handleSubmit(onSubmit)} className="mt-6 space-y-6">
|
|
163
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
164
|
+
<Input
|
|
165
|
+
label="First name"
|
|
166
|
+
placeholder="Taylor"
|
|
167
|
+
{...register('firstName')}
|
|
168
|
+
error={errors.firstName?.message}
|
|
169
|
+
/>
|
|
170
|
+
<Input
|
|
171
|
+
label="Last name"
|
|
172
|
+
placeholder="Reed"
|
|
173
|
+
{...register('lastName')}
|
|
174
|
+
error={errors.lastName?.message}
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<div className="relative">
|
|
179
|
+
<Input
|
|
180
|
+
type="email"
|
|
181
|
+
label="Email address"
|
|
182
|
+
placeholder="you@example.com"
|
|
183
|
+
className="pl-10"
|
|
184
|
+
{...register('email')}
|
|
185
|
+
error={errors.email?.message}
|
|
186
|
+
/>
|
|
187
|
+
<Mail className="absolute left-3 top-[38px] h-4 w-4 text-slate-400" />
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div className="relative">
|
|
191
|
+
<Input
|
|
192
|
+
type="tel"
|
|
193
|
+
label="Phone number"
|
|
194
|
+
placeholder="+1 (555) 123-4567"
|
|
195
|
+
className="pl-10"
|
|
196
|
+
{...register('phone')}
|
|
197
|
+
error={errors.phone?.message}
|
|
198
|
+
/>
|
|
199
|
+
<Phone className="absolute left-3 top-[38px] h-4 w-4 text-slate-400" />
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div className="flex flex-wrap gap-4">
|
|
203
|
+
<Button
|
|
204
|
+
type="submit"
|
|
205
|
+
size="lg"
|
|
206
|
+
isLoading={isSubmitting}
|
|
207
|
+
>
|
|
208
|
+
Save changes
|
|
209
|
+
</Button>
|
|
210
|
+
<Button
|
|
211
|
+
type="button"
|
|
212
|
+
variant="outline"
|
|
213
|
+
size="lg"
|
|
214
|
+
onClick={() => router.push('/orders')}
|
|
215
|
+
>
|
|
216
|
+
View recent orders
|
|
217
|
+
</Button>
|
|
218
|
+
</div>
|
|
219
|
+
</form>
|
|
220
|
+
</section>
|
|
221
|
+
|
|
222
|
+
<section className="grid gap-4 md:grid-cols-2">
|
|
223
|
+
{quickLinks.map((item) => (
|
|
119
224
|
<Link
|
|
120
225
|
key={item.href}
|
|
121
226
|
href={item.href}
|
|
122
|
-
className="
|
|
227
|
+
className="group rounded-3xl border border-slate-100 bg-white p-6 shadow-sm transition hover:-translate-y-1 hover:shadow-lg"
|
|
123
228
|
>
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
229
|
+
<div className="flex items-center gap-3">
|
|
230
|
+
<span className="rounded-2xl bg-primary-50 p-3 text-primary-600">
|
|
231
|
+
<item.icon className="h-5 w-5" />
|
|
232
|
+
</span>
|
|
233
|
+
<div>
|
|
234
|
+
<p className="text-base font-semibold text-slate-900 group-hover:text-primary-600">
|
|
235
|
+
{item.label}
|
|
236
|
+
</p>
|
|
237
|
+
<p className="text-sm text-slate-500">{item.description}</p>
|
|
238
|
+
</div>
|
|
130
239
|
</div>
|
|
131
240
|
</Link>
|
|
132
241
|
))}
|
|
133
|
-
</
|
|
134
|
-
|
|
135
|
-
{/* Logout Button */}
|
|
136
|
-
<button
|
|
137
|
-
onClick={handleLogout}
|
|
138
|
-
className="w-full mt-6 flex items-center justify-center gap-2 p-3 text-red-600 hover:bg-red-50 rounded-lg transition-colors font-medium"
|
|
139
|
-
>
|
|
140
|
-
<LogOut className="w-5 h-5" />
|
|
141
|
-
Logout
|
|
142
|
-
</button>
|
|
242
|
+
</section>
|
|
143
243
|
</motion.div>
|
|
144
|
-
</div>
|
|
145
244
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<motion.div
|
|
149
|
-
initial={{ opacity: 0, y: 20 }}
|
|
245
|
+
<motion.aside
|
|
246
|
+
initial={{ opacity: 0, y: 24 }}
|
|
150
247
|
animate={{ opacity: 1, y: 0 }}
|
|
151
|
-
transition={{ delay: 0.
|
|
152
|
-
className="
|
|
248
|
+
transition={{ delay: 0.1 }}
|
|
249
|
+
className="space-y-6"
|
|
153
250
|
>
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
error={errors.lastName?.message}
|
|
169
|
-
/>
|
|
170
|
-
</div>
|
|
251
|
+
<div className="rounded-3xl border border-slate-100 bg-white p-6 shadow-lg shadow-primary-50">
|
|
252
|
+
<h3 className="text-lg font-semibold text-slate-900">Care preferences</h3>
|
|
253
|
+
<p className="mt-3 text-sm text-slate-600">
|
|
254
|
+
Customize how we support you. Set refill reminders or manage communication
|
|
255
|
+
preferences to stay aligned with your wellness goals.
|
|
256
|
+
</p>
|
|
257
|
+
<Button
|
|
258
|
+
variant="outline"
|
|
259
|
+
className="mt-4 w-full"
|
|
260
|
+
onClick={() => router.push('/account/preferences')}
|
|
261
|
+
>
|
|
262
|
+
Manage preferences
|
|
263
|
+
</Button>
|
|
264
|
+
</div>
|
|
171
265
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
/>
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
type="submit"
|
|
189
|
-
size="lg"
|
|
190
|
-
isLoading={isSubmitting}
|
|
191
|
-
>
|
|
192
|
-
Save Changes
|
|
193
|
-
</Button>
|
|
194
|
-
<Button
|
|
195
|
-
type="button"
|
|
196
|
-
variant="outline"
|
|
197
|
-
size="lg"
|
|
198
|
-
onClick={() => router.push('/account/change-password')}
|
|
199
|
-
>
|
|
200
|
-
Change Password
|
|
201
|
-
</Button>
|
|
202
|
-
</div>
|
|
203
|
-
</form>
|
|
204
|
-
</motion.div>
|
|
266
|
+
<div className="rounded-3xl border border-primary-100 bg-primary-50/70 p-6 text-sm text-primary-700 shadow-sm">
|
|
267
|
+
<p className="font-semibold uppercase tracking-[0.3em]">Pharmacist tip</p>
|
|
268
|
+
<p className="mt-3 leading-relaxed">
|
|
269
|
+
Keep your phone number current so pharmacists can reach you quickly with dosage
|
|
270
|
+
advice or time-sensitive updates about your order.
|
|
271
|
+
</p>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<button
|
|
275
|
+
onClick={handleLogout}
|
|
276
|
+
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"
|
|
277
|
+
>
|
|
278
|
+
<LogOut className="h-4 w-4" />
|
|
279
|
+
Log out
|
|
280
|
+
</button>
|
|
281
|
+
</motion.aside>
|
|
205
282
|
</div>
|
|
206
283
|
</div>
|
|
207
284
|
</div>
|
|
208
285
|
</div>
|
|
209
286
|
);
|
|
210
287
|
}
|
|
211
|
-
|