hey-pharmacist-ecommerce 1.1.41 → 1.1.43
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 +70 -8
- package/dist/index.d.mts +380 -2364
- package/dist/index.d.ts +380 -2364
- package/dist/index.js +576 -615
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +576 -612
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/AccountAddressesTab.tsx +9 -9
- package/src/components/AccountOrdersTab.tsx +11 -11
- package/src/components/AccountOverviewTab.tsx +26 -26
- package/src/components/AccountPaymentTab.tsx +2 -2
- package/src/components/AccountReviewsTab.tsx +1 -1
- package/src/components/AccountSettingsTab.tsx +6 -6
- package/src/components/AddressFormModal.tsx +2 -2
- package/src/components/CartItem.tsx +15 -15
- package/src/components/FilterChips.tsx +7 -7
- package/src/components/Footer.tsx +9 -9
- package/src/components/Header.tsx +7 -7
- package/src/components/Notification.tsx +3 -3
- package/src/components/NotificationBell.tsx +3 -3
- package/src/components/NotificationDrawer.tsx +1 -1
- package/src/components/NotificationModal.tsx +1 -1
- package/src/components/OrderCard.tsx +2 -2
- package/src/components/ProductCard.tsx +13 -13
- package/src/components/QuickViewModal.tsx +28 -28
- package/src/components/ReviewCard.tsx +6 -6
- package/src/components/TabNavigation.tsx +2 -2
- package/src/components/ui/Badge.tsx +2 -2
- package/src/components/ui/Button.tsx +3 -3
- package/src/components/ui/ConfirmModal.tsx +3 -3
- package/src/components/ui/Input.tsx +1 -1
- package/src/lib/Apis/api.ts +0 -1
- package/src/lib/Apis/models/group-with-no-users-dto.ts +0 -6
- package/src/lib/Apis/models/group-with-users-dto.ts +0 -6
- package/src/lib/Apis/models/index.ts +0 -35
- package/src/lib/Apis/models/order.ts +0 -6
- package/src/lib/Apis/models/populated-order.ts +0 -6
- package/src/lib/Apis/models/preference-update-item.ts +1 -0
- package/src/lib/Apis/models/update-user-dto.ts +0 -6
- package/src/lib/Apis/models/user-group.ts +0 -6
- package/src/lib/Apis/models/user-with-no-id.ts +0 -6
- package/src/lib/Apis/sharedConfig.ts +1 -1
- package/src/providers/ThemeProvider.tsx +2 -2
- package/src/screens/AddressesScreen.tsx +6 -6
- package/src/screens/CartScreen.tsx +23 -23
- package/src/screens/ChangePasswordScreen.tsx +2 -2
- package/src/screens/CheckoutScreen.tsx +28 -28
- package/src/screens/CurrentOrdersScreen.tsx +4 -4
- package/src/screens/EditProfileScreen.tsx +1 -1
- package/src/screens/ForgotPasswordScreen.tsx +11 -11
- package/src/screens/LoginScreen.tsx +12 -12
- package/src/screens/OrderDetailScreen.tsx +30 -30
- package/src/screens/OrdersScreen.tsx +3 -3
- package/src/screens/ProductDetailScreen.tsx +59 -59
- package/src/screens/ProfileScreen.tsx +2 -2
- package/src/screens/RegisterScreen.tsx +15 -15
- package/src/screens/ResetPasswordScreen.tsx +14 -14
- package/src/screens/SearchResultsScreen.tsx +7 -7
- package/src/screens/ShopScreen.tsx +55 -55
- package/src/screens/WishlistScreen.tsx +22 -22
- package/src/styles/globals.css +43 -43
- package/src/lib/Apis/apis/marketing-api.ts +0 -3099
- package/src/lib/Apis/models/add-contact-to-list-dto.ts +0 -33
- package/src/lib/Apis/models/browser-stats-response-dto.ts +0 -40
- package/src/lib/Apis/models/campaign-content-response-dto.ts +0 -40
- package/src/lib/Apis/models/campaign-draft-dto.ts +0 -175
- package/src/lib/Apis/models/campaign-draft-response-dto.ts +0 -40
- package/src/lib/Apis/models/campaign-draft-schedule-dto.ts +0 -49
- package/src/lib/Apis/models/campaign-draft-schedule-response-dto.ts +0 -40
- package/src/lib/Apis/models/campaign-draft-sending-dto.ts +0 -43
- package/src/lib/Apis/models/campaign-draft-sending-response-dto.ts +0 -40
- package/src/lib/Apis/models/contact-aggregated-stats-response-dto.ts +0 -40
- package/src/lib/Apis/models/contact-full-dto.ts +0 -93
- package/src/lib/Apis/models/contact-full-response-dto.ts +0 -40
- package/src/lib/Apis/models/contact-list-stats-response-dto.ts +0 -40
- package/src/lib/Apis/models/contact-lists-response-dto.ts +0 -40
- package/src/lib/Apis/models/country-stats-response-dto.ts +0 -40
- package/src/lib/Apis/models/create-contact-dto.ts +0 -39
- package/src/lib/Apis/models/create-contact-list-dto.ts +0 -27
- package/src/lib/Apis/models/create-email-template-dto.ts +0 -51
- package/src/lib/Apis/models/create-marketing-campaign-dto.ts +0 -81
- package/src/lib/Apis/models/email-template-response-dto.ts +0 -117
- package/src/lib/Apis/models/general-stats-response-dto.ts +0 -40
- package/src/lib/Apis/models/link-stats-response-dto.ts +0 -40
- package/src/lib/Apis/models/marketing-campaign-content-dto.ts +0 -27
- package/src/lib/Apis/models/marketing-list-contact-dto.ts +0 -51
- package/src/lib/Apis/models/schedule-campaign-draft-dto.ts +0 -27
- package/src/lib/Apis/models/send-test-email-dto.ts +0 -28
- package/src/lib/Apis/models/single-browser-stats-dto.ts +0 -45
- package/src/lib/Apis/models/single-contact-aggregated-stats-dto.ts +0 -129
- package/src/lib/Apis/models/single-contact-list-stats-dto.ts +0 -117
- package/src/lib/Apis/models/single-country-stats-dto.ts +0 -39
- package/src/lib/Apis/models/single-general-stats.ts +0 -153
- package/src/lib/Apis/models/single-link-stats-dto.ts +0 -39
- package/src/lib/Apis/models/single-recipient-dto.ts +0 -33
- package/src/lib/Apis/models/update-campaign-draft-content-dto.ts +0 -27
- package/src/lib/Apis/models/update-marketing-camp-draft-dto.ts +0 -81
|
@@ -81,9 +81,9 @@ export function LoginScreen() {
|
|
|
81
81
|
>
|
|
82
82
|
<div className="w-full max-w-lg space-y-10 text-center">
|
|
83
83
|
<div className="space-y-2">
|
|
84
|
-
<Lock strokeWidth={2} className='h-16 w-16 mx-auto text-white rounded-full bg-
|
|
85
|
-
<h2 className="text-4xl text-
|
|
86
|
-
<p className="text-sm text-
|
|
84
|
+
<Lock strokeWidth={2} className='h-16 w-16 mx-auto text-white rounded-full bg-hsecondary m-2 mb-4 px-4' />
|
|
85
|
+
<h2 className="text-4xl text-hsecondary">Welcome Back</h2>
|
|
86
|
+
<p className="text-sm text-hmuted">Sign in to access your patient portal
|
|
87
87
|
</p>
|
|
88
88
|
</div>
|
|
89
89
|
|
|
@@ -103,19 +103,19 @@ export function LoginScreen() {
|
|
|
103
103
|
</div>
|
|
104
104
|
)}
|
|
105
105
|
|
|
106
|
-
<div className='text-start text-
|
|
107
|
-
<h2 className="text-sm text-
|
|
106
|
+
<div className='text-start text-hsecondary'>
|
|
107
|
+
<h2 className="text-sm text-hsecondary mb-3">Email Address <span className='text-hprimary-500'>*</span></h2>
|
|
108
108
|
<Input
|
|
109
109
|
type="email"
|
|
110
110
|
// label="Email address"
|
|
111
111
|
placeholder="you@example.com"
|
|
112
112
|
{...register('email')}
|
|
113
113
|
error={errors.email?.message}
|
|
114
|
-
className='text-
|
|
114
|
+
className='text-hsecondary'
|
|
115
115
|
/>
|
|
116
116
|
</div>
|
|
117
|
-
<div className="relative text-start text-
|
|
118
|
-
<h2 className="text-sm text-
|
|
117
|
+
<div className="relative text-start text-hsecondary">
|
|
118
|
+
<h2 className="text-sm text-hsecondary mb-3">Password <span className='text-hprimary-500'>*</span></h2>
|
|
119
119
|
|
|
120
120
|
<Input
|
|
121
121
|
type={showPassword ? 'text' : 'password'}
|
|
@@ -135,7 +135,7 @@ export function LoginScreen() {
|
|
|
135
135
|
<div className="flex items-center justify-end text-sm">
|
|
136
136
|
<Link
|
|
137
137
|
href={buildPath('/forgot-password')}
|
|
138
|
-
className="font-medium text-
|
|
138
|
+
className="font-medium text-hprimary transition hover:opacity-80"
|
|
139
139
|
>
|
|
140
140
|
Forgot password?
|
|
141
141
|
</Link>
|
|
@@ -144,20 +144,20 @@ export function LoginScreen() {
|
|
|
144
144
|
<button
|
|
145
145
|
type="submit"
|
|
146
146
|
disabled={isSubmitting}
|
|
147
|
-
className="w-full bg-
|
|
147
|
+
className="w-full bg-hsecondary hover:opacity-80 text-white font-medium py-3 px-4 rounded-lg transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
|
|
148
148
|
>
|
|
149
149
|
{isSubmitting ? 'Signing in...' : 'Sign in'}
|
|
150
150
|
</button>
|
|
151
151
|
</form>
|
|
152
152
|
|
|
153
153
|
<div className="mt-4">
|
|
154
|
-
<p className="text-
|
|
154
|
+
<p className="text-hmuted">Don't have an account? <Link href={buildPath('/register')} className="font-medium text-hprimary transition hover:opacity-90">Sign up</Link></p>
|
|
155
155
|
</div>
|
|
156
156
|
|
|
157
157
|
|
|
158
158
|
{/* <div className="rounded-3xl border border-slate-100 bg-slate-50 p-6 text-sm text-slate-600">
|
|
159
159
|
<div className="flex items-start gap-3">
|
|
160
|
-
<Lock className="mt-0.5 h-5 w-5 text-
|
|
160
|
+
<Lock className="mt-0.5 h-5 w-5 text-hprimary-500" />
|
|
161
161
|
<div>
|
|
162
162
|
<p className="font-semibold text-slate-800">Secure by design</p>
|
|
163
163
|
<p>
|
|
@@ -59,8 +59,8 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
59
59
|
if (isLoading) {
|
|
60
60
|
return (
|
|
61
61
|
<div className="min-h-screen bg-slate-50 flex flex-col items-center justify-center p-4">
|
|
62
|
-
<div className="w-16 h-16 border-4 border-
|
|
63
|
-
<p className="text-
|
|
62
|
+
<div className="w-16 h-16 border-4 border-hprimary-20 border-t-primary rounded-full animate-spin mb-4" />
|
|
63
|
+
<p className="text-hmuted font-medium animate-pulse">Retrieving order details...</p>
|
|
64
64
|
</div>
|
|
65
65
|
);
|
|
66
66
|
}
|
|
@@ -71,8 +71,8 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
71
71
|
<div className="p-4 bg-red-50 rounded-full mb-4">
|
|
72
72
|
<AlertCircle className="w-10 h-10 text-red-500" />
|
|
73
73
|
</div>
|
|
74
|
-
<h1 className="text-xl font-bold text-
|
|
75
|
-
<p className="text-
|
|
74
|
+
<h1 className="text-xl font-bold text-hsecondary mb-2">Order Not Found</h1>
|
|
75
|
+
<p className="text-hmuted mb-6">We couldn't find the order you're looking for.</p>
|
|
76
76
|
<Button onClick={() => router.push(buildPath('/account'))}>
|
|
77
77
|
Back to Account
|
|
78
78
|
</Button>
|
|
@@ -106,7 +106,7 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
106
106
|
<div className="container mx-auto px-4 pt-8 max-w-6xl">
|
|
107
107
|
<button
|
|
108
108
|
onClick={() => router.back()}
|
|
109
|
-
className="group flex items-center gap-2 text-
|
|
109
|
+
className="group flex items-center gap-2 text-hmuted hover:text-hsecondary transition-colors mb-6"
|
|
110
110
|
>
|
|
111
111
|
<div className="p-1.5 rounded-full bg-white shadow-xs group-hover:shadow-md transition-all">
|
|
112
112
|
<ChevronLeft className="w-4 h-4" />
|
|
@@ -117,16 +117,16 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
117
117
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-8">
|
|
118
118
|
<div>
|
|
119
119
|
<div className="flex items-center gap-3 mb-2">
|
|
120
|
-
<h1 className="text-3xl font-bold text-
|
|
120
|
+
<h1 className="text-3xl font-bold text-hsecondary">Order Details</h1>
|
|
121
121
|
<Badge variant={getStatusVariant(status)}>{status}</Badge>
|
|
122
|
-
<Badge variant="primary" className="bg-
|
|
122
|
+
<Badge variant="primary" className="bg-hprimary-100 text-hprimary-700 border-hprimary-200">
|
|
123
123
|
{order.orderType || 'Pickup'}
|
|
124
124
|
</Badge>
|
|
125
125
|
</div>
|
|
126
|
-
<div className="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-
|
|
126
|
+
<div className="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-hmuted">
|
|
127
127
|
<span className="flex items-center gap-1.5 font-medium">
|
|
128
128
|
<span className="opacity-60 text-xs uppercase tracking-widest font-bold">ID:</span>
|
|
129
|
-
<span className="text-
|
|
129
|
+
<span className="text-hsecondary font-mono tracking-tight">#{id.toUpperCase()}</span>
|
|
130
130
|
</span>
|
|
131
131
|
<span className="flex items-center gap-1.5 font-medium">
|
|
132
132
|
<Calendar className="w-4 h-4 opacity-60" />
|
|
@@ -136,7 +136,7 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
136
136
|
</div>
|
|
137
137
|
<div className="flex gap-3">
|
|
138
138
|
{order.payment?.hostedInvoiceUrl && (
|
|
139
|
-
<Button size="sm" onClick={() => window.open(order.payment?.hostedInvoiceUrl, '_blank')} className="bg-
|
|
139
|
+
<Button size="sm" onClick={() => window.open(order.payment?.hostedInvoiceUrl, '_blank')} className="bg-haccent hover:bg-haccent-dark border-none">
|
|
140
140
|
<ExternalLink className="w-4 h-4" />
|
|
141
141
|
Payment Details
|
|
142
142
|
</Button>
|
|
@@ -156,10 +156,10 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
156
156
|
>
|
|
157
157
|
<div className="p-6 border-b border-slate-100 bg-slate-50/50 flex items-center justify-between">
|
|
158
158
|
<div className="flex items-center gap-2">
|
|
159
|
-
<Package className="w-5 h-5 text-
|
|
160
|
-
<h2 className="font-bold text-
|
|
159
|
+
<Package className="w-5 h-5 text-hsecondary" />
|
|
160
|
+
<h2 className="font-bold text-hsecondary">Order Items</h2>
|
|
161
161
|
</div>
|
|
162
|
-
<span className="text-xs font-bold bg-slate-200 text-
|
|
162
|
+
<span className="text-xs font-bold bg-slate-200 text-hmuted px-2.5 py-1 rounded-full">
|
|
163
163
|
{items.length} {items.length === 1 ? 'Product' : 'Products'}
|
|
164
164
|
</span>
|
|
165
165
|
</div>
|
|
@@ -179,19 +179,19 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
179
179
|
</div>
|
|
180
180
|
<div className="flex-1 flex flex-col justify-between py-1">
|
|
181
181
|
<div>
|
|
182
|
-
<h3 className="font-bold text-
|
|
182
|
+
<h3 className="font-bold text-hsecondary text-lg group-hover:text-hprimary transition-colors leading-snug mb-1">
|
|
183
183
|
{item.productVariantData?.name}
|
|
184
184
|
</h3>
|
|
185
|
-
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm text-
|
|
185
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm text-hmuted">
|
|
186
186
|
<span className="flex items-center gap-1.5">
|
|
187
187
|
<span className="w-1.5 h-1.5 rounded-full bg-slate-300" />
|
|
188
|
-
Quantity: <span className="text-
|
|
188
|
+
Quantity: <span className="text-hsecondary font-bold">{item.quantity}</span>
|
|
189
189
|
</span>
|
|
190
190
|
</div>
|
|
191
191
|
</div>
|
|
192
192
|
<div className="flex items-center justify-between">
|
|
193
|
-
<span className="text-
|
|
194
|
-
<span className="font-black text-
|
|
193
|
+
<span className="text-hmuted text-sm">{formatPrice(item.productVariantData?.finalPrice || 0)} per unit</span>
|
|
194
|
+
<span className="font-black text-hsecondary">{formatPrice((item.productVariantData?.finalPrice || 0) * item.quantity)}</span>
|
|
195
195
|
</div>
|
|
196
196
|
</div>
|
|
197
197
|
</div>
|
|
@@ -203,12 +203,12 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
203
203
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
204
204
|
<div className="bg-white p-6 rounded-3xl border border-slate-200 shadow-xs">
|
|
205
205
|
<div className="flex items-center gap-2 mb-4">
|
|
206
|
-
<MapPin className="w-5 h-5 text-
|
|
207
|
-
<h3 className="font-bold text-
|
|
206
|
+
<MapPin className="w-5 h-5 text-haccent" />
|
|
207
|
+
<h3 className="font-bold text-hsecondary">{isDelivery ? 'Shipping Address' : 'Pickup Location'}</h3>
|
|
208
208
|
</div>
|
|
209
209
|
{activeAddress ? (
|
|
210
|
-
<div className="text-sm text-
|
|
211
|
-
<p className="font-bold text-
|
|
210
|
+
<div className="text-sm text-hmuted leading-relaxed">
|
|
211
|
+
<p className="font-bold text-hsecondary text-base mb-1">
|
|
212
212
|
{activeAddress.name}
|
|
213
213
|
</p>
|
|
214
214
|
<p>{activeAddress.street1}</p>
|
|
@@ -217,16 +217,16 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
217
217
|
<p>{activeAddress.country}</p>
|
|
218
218
|
</div>
|
|
219
219
|
) : (
|
|
220
|
-
<p className="text-sm text-
|
|
220
|
+
<p className="text-sm text-hmuted italic">No address recorded</p>
|
|
221
221
|
)}
|
|
222
222
|
</div>
|
|
223
223
|
<div className="bg-white p-6 rounded-3xl border border-slate-200 shadow-xs">
|
|
224
224
|
<div className="flex items-center gap-2 mb-4">
|
|
225
|
-
<CreditCard className="w-5 h-5 text-
|
|
226
|
-
<h3 className="font-bold text-
|
|
225
|
+
<CreditCard className="w-5 h-5 text-haccent" />
|
|
226
|
+
<h3 className="font-bold text-hsecondary">Payment Method</h3>
|
|
227
227
|
</div>
|
|
228
|
-
<div className="text-sm text-
|
|
229
|
-
<p className="font-bold text-
|
|
228
|
+
<div className="text-sm text-hmuted leading-relaxed">
|
|
229
|
+
<p className="font-bold text-hsecondary text-base mb-1">
|
|
230
230
|
{order.payment?.paymentMethod ? order.payment.paymentMethod.replace('_', ' ').toUpperCase() : 'N/A'}
|
|
231
231
|
</p>
|
|
232
232
|
<p className="flex items-center gap-2 mt-2">
|
|
@@ -250,7 +250,7 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
250
250
|
<motion.div
|
|
251
251
|
initial={{ opacity: 0, x: 20 }}
|
|
252
252
|
animate={{ opacity: 1, x: 0 }}
|
|
253
|
-
className="bg-
|
|
253
|
+
className="bg-hsecondary p-8 rounded-[2rem] text-white shadow-xl shadow-secondary-20 sticky top-8"
|
|
254
254
|
>
|
|
255
255
|
<h2 className="text-xl font-bold mb-6 flex items-center gap-2">
|
|
256
256
|
Summary View
|
|
@@ -270,7 +270,7 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
270
270
|
<span className="font-bold text-white">{formatPrice(order.tax || 0)}</span>
|
|
271
271
|
</div>
|
|
272
272
|
{order.discountedAmount !== undefined && order.discountedAmount > 0 && (
|
|
273
|
-
<div className="flex justify-between items-center text-
|
|
273
|
+
<div className="flex justify-between items-center text-hprimary">
|
|
274
274
|
<span className="text-sm font-medium">Discount</span>
|
|
275
275
|
<span className="font-bold">-{formatPrice(order.discountedAmount)}</span>
|
|
276
276
|
</div>
|
|
@@ -292,7 +292,7 @@ export function OrderDetailScreen({ id }: OrderDetailScreenProps) {
|
|
|
292
292
|
{order.orderStatus === 'Pending' && (
|
|
293
293
|
<div className="p-4 bg-white/5 rounded-2xl border border-white/10 mb-8">
|
|
294
294
|
<div className="flex items-start gap-3">
|
|
295
|
-
<Info className="w-5 h-5 text-
|
|
295
|
+
<Info className="w-5 h-5 text-hprimary shrink-0 mt-0.5" />
|
|
296
296
|
<div>
|
|
297
297
|
<p className="text-xs font-bold mb-1">Order is processing</p>
|
|
298
298
|
<p className="text-[11px] text-white/60 leading-relaxed">We've received your request and our pharmacists are reviewing it for safety and accuracy.</p>
|
|
@@ -64,9 +64,9 @@ export function OrdersScreen() {
|
|
|
64
64
|
className="space-y-6"
|
|
65
65
|
>
|
|
66
66
|
<div className="mb-8">
|
|
67
|
-
<h1 className="text-3xl font-black text-
|
|
68
|
-
<p className="text-sm font-medium text-
|
|
69
|
-
<span className="w-1.5 h-1.5 rounded-full bg-
|
|
67
|
+
<h1 className="text-3xl font-black text-hsecondary tracking-tight">Order History</h1>
|
|
68
|
+
<p className="text-sm font-medium text-hmuted mt-1 flex items-center gap-2">
|
|
69
|
+
<span className="w-1.5 h-1.5 rounded-full bg-haccent animate-pulse" />
|
|
70
70
|
{filteredOrders.length} {filteredOrders.length === 1 ? 'record found' : 'records found'} for your account
|
|
71
71
|
</p>
|
|
72
72
|
</div>
|
|
@@ -334,7 +334,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
334
334
|
<div className="min-h-screen bg-slate-50">
|
|
335
335
|
<div className="container mx-auto px-4 py-16">
|
|
336
336
|
<div className="rounded-3xl bg-white p-10 text-center shadow-xs">
|
|
337
|
-
<Sparkles className="mx-auto h-10 w-10 text-
|
|
337
|
+
<Sparkles className="mx-auto h-10 w-10 text-hprimary-500" />
|
|
338
338
|
<h1 className="mt-6 text-2xl font-semibold text-gray-900">Product not found</h1>
|
|
339
339
|
<p className="mt-2 text-gray-600">
|
|
340
340
|
It may have been removed or is temporarily unavailable. Discover other pharmacy
|
|
@@ -361,7 +361,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
361
361
|
<div className="max-w-[1400px] mx-auto px-8 md:px-12">
|
|
362
362
|
<button
|
|
363
363
|
onClick={() => router.push(buildPath('/shop'))}
|
|
364
|
-
className="flex items-center gap-2
|
|
364
|
+
className="flex items-center gap-2 text-[13px] text-hmuted hover:text-hprimary transition-colors"
|
|
365
365
|
>
|
|
366
366
|
<ChevronLeft className="size-4" />
|
|
367
367
|
Back to Shop
|
|
@@ -405,22 +405,22 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
405
405
|
|
|
406
406
|
<div className="absolute top-6 left-6 flex flex-col gap-3">
|
|
407
407
|
{discount > 0 && (
|
|
408
|
-
<div className="bg-
|
|
409
|
-
<span className="font-
|
|
408
|
+
<div className="bg-hsecondary text-white rounded-full px-4 py-2">
|
|
409
|
+
<span className="font-bold text-[12px] uppercase tracking-wide">
|
|
410
410
|
Save {discount}%
|
|
411
411
|
</span>
|
|
412
412
|
</div>
|
|
413
413
|
)}
|
|
414
414
|
{/* {product.bestseller && (
|
|
415
|
-
<div className="bg-
|
|
416
|
-
<span className="font-
|
|
415
|
+
<div className="bg-hsecondary text-white rounded-full px-4 py-2">
|
|
416
|
+
<span className="font-semibold text-[11px] uppercase tracking-wide">
|
|
417
417
|
Bestseller
|
|
418
418
|
</span>
|
|
419
419
|
</div>
|
|
420
420
|
)}
|
|
421
421
|
{product.newArrival && (
|
|
422
|
-
<div className="bg-
|
|
423
|
-
<span className="font-
|
|
422
|
+
<div className="bg-hprimary text-white rounded-full px-4 py-2">
|
|
423
|
+
<span className="font-semibold text-[11px] uppercase tracking-wide">
|
|
424
424
|
New Arrival
|
|
425
425
|
</span>
|
|
426
426
|
</div>
|
|
@@ -437,8 +437,8 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
437
437
|
type="button"
|
|
438
438
|
onClick={() => setActiveImageIndex(index)}
|
|
439
439
|
className={`relative aspect-square overflow-hidden rounded-lg border-2 transition-all ${activeImageIndex === index
|
|
440
|
-
? 'border-
|
|
441
|
-
: 'border-slate-200 hover:border-
|
|
440
|
+
? 'border-hprimary-50 ring-2 ring-hprimary-80 ring-offset-2 shadow-md'
|
|
441
|
+
: 'border-slate-200 hover:border-hprimary-50'
|
|
442
442
|
}`}
|
|
443
443
|
>
|
|
444
444
|
<Image
|
|
@@ -461,10 +461,10 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
461
461
|
<aside className="space-y-6 lg:sticky lg:top-24">
|
|
462
462
|
{/* Category Path */}
|
|
463
463
|
<div className="mb-4">
|
|
464
|
-
<p className="
|
|
464
|
+
<p className="text-[12px] text-hprimary uppercase tracking-wide font-medium mb-2">
|
|
465
465
|
{product.brand} • {product.categoryIds?.[0]?.name || 'Uncategorized'}
|
|
466
466
|
</p>
|
|
467
|
-
<h1 className="text-3xl font-
|
|
467
|
+
<h1 className="text-3xl font-semibold text-hsecondary tracking-[-1.5px] mb-3">
|
|
468
468
|
{selectedVariant?.name || product.name}
|
|
469
469
|
</h1>
|
|
470
470
|
{/* Rating */}
|
|
@@ -480,22 +480,22 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
480
480
|
/>
|
|
481
481
|
))}
|
|
482
482
|
</div>
|
|
483
|
-
<span className="
|
|
483
|
+
<span className="text-[14px] text-hmuted">
|
|
484
484
|
{reviewStats.averageRating} ({reviewStats.reviewCount} {reviewStats.reviewCount === 1 ? 'review' : 'reviews'})
|
|
485
485
|
</span>
|
|
486
486
|
</div>
|
|
487
487
|
{selectedVariant && (
|
|
488
488
|
<div className="flex items-center gap-3 mb-6 pb-6 border-b-2 border-gray-100">
|
|
489
|
-
<span className="font-
|
|
489
|
+
<span className="font-bold text-[40px] text-hsecondary">
|
|
490
490
|
${variantPrice.toFixed(2)}
|
|
491
491
|
</span>
|
|
492
492
|
{variantComparePrice && variantComparePrice > variantPrice && (
|
|
493
493
|
<>
|
|
494
|
-
<span className="
|
|
494
|
+
<span className="text-[24px] text-hmuted line-through">
|
|
495
495
|
${variantComparePrice.toFixed(2)}
|
|
496
496
|
</span>
|
|
497
|
-
<div className="px-3 py-1 rounded-full bg-
|
|
498
|
-
<span className="font-
|
|
497
|
+
<div className="px-3 py-1 rounded-full bg-hsecondary/10">
|
|
498
|
+
<span className="font-semibold text-[13px] text-hsecondary">
|
|
499
499
|
Save ${formatPrice(variantComparePrice - variantPrice)}
|
|
500
500
|
</span>
|
|
501
501
|
</div>
|
|
@@ -510,28 +510,28 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
510
510
|
{selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK ? (
|
|
511
511
|
<>
|
|
512
512
|
<div className="size-3 rounded-full bg-red-500" />
|
|
513
|
-
<span className="
|
|
513
|
+
<span className="text-[13px] text-red-600 font-medium">
|
|
514
514
|
Out of Stock
|
|
515
515
|
</span>
|
|
516
516
|
</>
|
|
517
517
|
) : selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.LOWSTOCK || selectedVariant.inventoryCount <= 10 ? (
|
|
518
518
|
<>
|
|
519
|
-
<div className="size-3 rounded-full bg-
|
|
520
|
-
<span className="
|
|
519
|
+
<div className="size-3 rounded-full bg-hprimary animate-pulse" />
|
|
520
|
+
<span className="text-[13px] text-hprimary font-medium">
|
|
521
521
|
Only {selectedVariant.inventoryCount} left in stock - Order soon!
|
|
522
522
|
</span>
|
|
523
523
|
</>
|
|
524
524
|
) : (
|
|
525
525
|
<>
|
|
526
526
|
<div className="size-3 rounded-full bg-green-500" />
|
|
527
|
-
<span className="
|
|
527
|
+
<span className="text-[13px] text-green-600 font-medium">
|
|
528
528
|
In Stock
|
|
529
529
|
</span>
|
|
530
530
|
</>
|
|
531
531
|
)}
|
|
532
532
|
|
|
533
533
|
</div>
|
|
534
|
-
<p className="
|
|
534
|
+
<p className="text-[12px] text-hmuted">
|
|
535
535
|
SKU: {selectedVariant?.sku || product?.sku}
|
|
536
536
|
</p>
|
|
537
537
|
</div>
|
|
@@ -540,7 +540,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
540
540
|
{/* Description */}
|
|
541
541
|
{product.description && (
|
|
542
542
|
<div
|
|
543
|
-
className="
|
|
543
|
+
className="text-[14px] text-hmuted leading-[1.7] mb-8 max-w-full overflow-hidden break-words"
|
|
544
544
|
dangerouslySetInnerHTML={{ __html: product.description }}
|
|
545
545
|
/>
|
|
546
546
|
)}
|
|
@@ -548,7 +548,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
548
548
|
{/* Variant Selector with Images */}
|
|
549
549
|
{product?.variants && product.variants.length > 0 && (
|
|
550
550
|
<div className="mb-6">
|
|
551
|
-
<h3 className="font-
|
|
551
|
+
<h3 className="font-semibold text-[14px] text-hsecondary mb-3">
|
|
552
552
|
Select Variant
|
|
553
553
|
</h3>
|
|
554
554
|
<div className="flex flex-wrap gap-3">
|
|
@@ -564,11 +564,11 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
564
564
|
type="button"
|
|
565
565
|
onClick={() => handleVariantSelect(variant)}
|
|
566
566
|
className={`flex items-start gap-3 px-4 py-2.5 rounded-xl border-2 transition-all ${isSelected
|
|
567
|
-
? 'border-
|
|
568
|
-
: 'border-gray-200 hover:border-
|
|
567
|
+
? 'border-hprimary bg-hprimary-5'
|
|
568
|
+
: 'border-gray-200 hover:border-hprimary-50'
|
|
569
569
|
}`}
|
|
570
570
|
>
|
|
571
|
-
<div className={`relative h-12 w-12 shrink-0 overflow-hidden rounded-full border-2 ${isSelected ? 'border-
|
|
571
|
+
<div className={`relative h-12 w-12 shrink-0 overflow-hidden rounded-full border-2 ${isSelected ? 'border-hprimary' : 'border-slate-200'}`}>
|
|
572
572
|
<Image
|
|
573
573
|
src={variantImage}
|
|
574
574
|
alt={variant.name || 'Variant image'}
|
|
@@ -578,7 +578,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
578
578
|
/>
|
|
579
579
|
</div>
|
|
580
580
|
<div className="flex-1">
|
|
581
|
-
<p className={`text-start text-sm font-medium ${isSelected ? 'text-
|
|
581
|
+
<p className={`text-start text-sm font-medium ${isSelected ? 'text-hsecondary' : 'text-slate-900'}`}>
|
|
582
582
|
{variant.name}
|
|
583
583
|
</p>
|
|
584
584
|
{variant.sku && (
|
|
@@ -586,7 +586,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
586
586
|
)}
|
|
587
587
|
</div>
|
|
588
588
|
{isSelected && (
|
|
589
|
-
<Check className="h-5 w-5 text-
|
|
589
|
+
<Check className="h-5 w-5 text-hsecondary shrink-0" />
|
|
590
590
|
)}
|
|
591
591
|
</button>
|
|
592
592
|
);
|
|
@@ -598,7 +598,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
598
598
|
|
|
599
599
|
{/* Quantity Selector */}
|
|
600
600
|
<div className="mb-8">
|
|
601
|
-
<h3 className="font-
|
|
601
|
+
<h3 className="font-semibold text-[14px] text-hsecondary mb-3">
|
|
602
602
|
Quantity
|
|
603
603
|
</h3>
|
|
604
604
|
<div className="flex items-center gap-3">
|
|
@@ -608,18 +608,18 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
608
608
|
type="button"
|
|
609
609
|
onClick={() => setQuantity((current) => Math.max(1, current - 1))}
|
|
610
610
|
|
|
611
|
-
className="font-
|
|
611
|
+
className="font-bold text-[18px] text-hsecondary hover:text-hprimary transition-colors"
|
|
612
612
|
aria-label="Increase quantity"
|
|
613
613
|
>
|
|
614
614
|
-
|
|
615
615
|
</button>
|
|
616
|
-
<span className="font-
|
|
616
|
+
<span className="font-semibold text-[16px] text-hsecondary min-w-[30px] text-center">
|
|
617
617
|
{quantity}
|
|
618
618
|
</span>
|
|
619
619
|
<button
|
|
620
620
|
onClick={() => setQuantity(Math.min(selectedVariant?.inventoryCount || product.inventoryCount || 999, quantity + 1))}
|
|
621
621
|
disabled={quantity >= (selectedVariant?.inventoryCount || product.inventoryCount || 0)}
|
|
622
|
-
className="font-
|
|
622
|
+
className="font-bold text-[18px] text-hsecondary hover:text-hprimary transition-colors disabled:opacity-50"
|
|
623
623
|
>
|
|
624
624
|
+
|
|
625
625
|
</button>
|
|
@@ -635,7 +635,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
635
635
|
{/* Action Buttons */}
|
|
636
636
|
<div className="flex flex-col sm:flex-row gap-3 mb-8">
|
|
637
637
|
<button
|
|
638
|
-
className="flex-1 font-
|
|
638
|
+
className="flex-1 font-medium text-[14px] px-8 py-4 rounded-full transition-all duration-300 flex items-center justify-center gap-3 bg-hsecondary text-white hover:bg-hsecondary/80 hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
|
639
639
|
onClick={handleAddToCart}
|
|
640
640
|
disabled={!selectedVariant || selectedVariant.inventoryStatus === ProductVariantInventoryStatusEnum.OUTOFSTOCK || isAddingToCart}
|
|
641
641
|
>
|
|
@@ -660,64 +660,64 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
660
660
|
)
|
|
661
661
|
}
|
|
662
662
|
</button>
|
|
663
|
-
<button className="sm:w-auto px-6 py-4 rounded-full border-2 border-
|
|
663
|
+
<button className="sm:w-auto px-6 py-4 rounded-full border-2 border-hprimary hover:bg-hprimary-5 transition-all flex items-center justify-center"
|
|
664
664
|
onClick={handleToggleFavorite}>
|
|
665
|
-
<Heart className={`h-4 w-4 ${isFavorited ? 'fill-red-500 text-red-500' : 'text-
|
|
665
|
+
<Heart className={`h-4 w-4 ${isFavorited ? 'fill-red-500 text-red-500' : 'text-hprimary'}`} />
|
|
666
666
|
</button>
|
|
667
667
|
</div>
|
|
668
668
|
|
|
669
669
|
</div>
|
|
670
670
|
|
|
671
671
|
{/* Benefits */}
|
|
672
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 p-6 bg-linear-to-br from-
|
|
672
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 p-6 bg-linear-to-br from-hsecondary/5 to-hsecondary/5 rounded-[24px]">
|
|
673
673
|
<div className="flex items-start gap-3">
|
|
674
674
|
<div className="size-10 rounded-full bg-white flex items-center justify-center shrink-0">
|
|
675
|
-
<Truck className="size-5 text-
|
|
675
|
+
<Truck className="size-5 text-hprimary" />
|
|
676
676
|
</div>
|
|
677
677
|
<div>
|
|
678
|
-
<p className="font-
|
|
678
|
+
<p className="font-semibold text-[12px] text-hsecondary mb-1">
|
|
679
679
|
Free Shipping
|
|
680
680
|
</p>
|
|
681
|
-
<p className="
|
|
681
|
+
<p className="text-[11px] text-hmuted">
|
|
682
682
|
On all orders
|
|
683
683
|
</p>
|
|
684
684
|
</div>
|
|
685
685
|
</div>
|
|
686
686
|
<div className="flex items-start gap-3">
|
|
687
687
|
<div className="size-10 rounded-full bg-white flex items-center justify-center shrink-0">
|
|
688
|
-
<RotateCcw className="size-5 text-
|
|
688
|
+
<RotateCcw className="size-5 text-hprimary" />
|
|
689
689
|
</div>
|
|
690
690
|
<div>
|
|
691
|
-
<p className="font-
|
|
691
|
+
<p className="font-semibold text-[12px] text-hsecondary mb-1">
|
|
692
692
|
Easy Returns
|
|
693
693
|
</p>
|
|
694
|
-
<p className="
|
|
694
|
+
<p className="text-[11px] text-hmuted">
|
|
695
695
|
30-day return policy
|
|
696
696
|
</p>
|
|
697
697
|
</div>
|
|
698
698
|
</div>
|
|
699
699
|
<div className="flex items-start gap-3">
|
|
700
700
|
<div className="size-10 rounded-full bg-white flex items-center justify-center shrink-0">
|
|
701
|
-
<Shield className="size-5 text-
|
|
701
|
+
<Shield className="size-5 text-hprimary" />
|
|
702
702
|
</div>
|
|
703
703
|
<div>
|
|
704
|
-
<p className="font-
|
|
704
|
+
<p className="font-semibold text-[12px] text-hsecondary mb-1">
|
|
705
705
|
Secure Checkout
|
|
706
706
|
</p>
|
|
707
|
-
<p className="
|
|
707
|
+
<p className="text-[11px] text-hmuted">
|
|
708
708
|
Safe & protected
|
|
709
709
|
</p>
|
|
710
710
|
</div>
|
|
711
711
|
</div>
|
|
712
712
|
<div className="flex items-start gap-3">
|
|
713
713
|
<div className="size-10 rounded-full bg-white flex items-center justify-center shrink-0">
|
|
714
|
-
<Package className="size-5 text-
|
|
714
|
+
<Package className="size-5 text-hprimary" />
|
|
715
715
|
</div>
|
|
716
716
|
<div>
|
|
717
|
-
<p className="font-
|
|
717
|
+
<p className="font-semibold text-[12px] text-hsecondary mb-1">
|
|
718
718
|
Quality Guaranteed
|
|
719
719
|
</p>
|
|
720
|
-
<p className="
|
|
720
|
+
<p className="text-[11px] text-hmuted">
|
|
721
721
|
Premium materials
|
|
722
722
|
</p>
|
|
723
723
|
</div>
|
|
@@ -734,9 +734,9 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
734
734
|
<button
|
|
735
735
|
key={tab}
|
|
736
736
|
onClick={() => setActiveTab(tab)}
|
|
737
|
-
className={`font-
|
|
738
|
-
? 'text-
|
|
739
|
-
: 'text-
|
|
737
|
+
className={`font-medium text-[14px] px-6 py-4 transition-all ${activeTab === tab
|
|
738
|
+
? 'text-hprimary border-b-2 border-hprimary -mb-0.5'
|
|
739
|
+
: 'text-hmuted hover:text-hprimary'
|
|
740
740
|
}`}
|
|
741
741
|
>
|
|
742
742
|
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
|
@@ -747,29 +747,29 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
747
747
|
<div className="bg-white rounded-[24px] p-8 border-2 border-gray-100">
|
|
748
748
|
{activeTab === 'description' && (
|
|
749
749
|
<div>
|
|
750
|
-
<h3 className="font-
|
|
750
|
+
<h3 className="font-semibold text-hsecondary mb-4">
|
|
751
751
|
Product Description
|
|
752
752
|
</h3>
|
|
753
753
|
<div
|
|
754
|
-
className="
|
|
754
|
+
className="text-[14px] text-hmuted leading-[1.8] mb-4 max-w-full overflow-hidden break-words"
|
|
755
755
|
dangerouslySetInnerHTML={{ __html: product.description }}
|
|
756
756
|
/>
|
|
757
757
|
|
|
758
758
|
<div className="mt-6">
|
|
759
|
-
<h4 className="font-
|
|
759
|
+
<h4 className="font-semibold text-[13px] text-hsecondary mb-3">
|
|
760
760
|
Last updated:
|
|
761
761
|
</h4>
|
|
762
|
-
<p className="
|
|
762
|
+
<p className="text-[14px] text-hmuted">
|
|
763
763
|
{lastUpdatedLabel}
|
|
764
764
|
</p>
|
|
765
765
|
</div>
|
|
766
766
|
|
|
767
767
|
{/* shipped from */}
|
|
768
768
|
<div className="mt-6">
|
|
769
|
-
<h4 className="font-
|
|
769
|
+
<h4 className="font-semibold text-[13px] text-hsecondary mb-3">
|
|
770
770
|
Shipped from:
|
|
771
771
|
</h4>
|
|
772
|
-
<p className="
|
|
772
|
+
<p className="text-[14px] text-hmuted">
|
|
773
773
|
Local pharmacy distribution center
|
|
774
774
|
</p>
|
|
775
775
|
</div>
|
|
@@ -786,7 +786,7 @@ export function ProductDetailScreen({ productId }: ProductDetailScreenProps) {
|
|
|
786
786
|
{/* Related Products */}
|
|
787
787
|
{relatedProducts.length > 0 && (
|
|
788
788
|
<div>
|
|
789
|
-
<h2 className="font-
|
|
789
|
+
<h2 className="font-semibold text-hsecondary mb-8 text-2xl">
|
|
790
790
|
You May Also Like
|
|
791
791
|
</h2>
|
|
792
792
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|