hey-pharmacist-ecommerce 1.1.13 → 1.1.15

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,165 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { motion } from 'framer-motion';
5
+ import { useForm } from 'react-hook-form';
6
+ import { z } from 'zod';
7
+ import { zodResolver } from '@hookform/resolvers/zod';
8
+ import { useRouter } from 'next/navigation';
9
+ import { Mail, Phone, User } from 'lucide-react';
10
+ import { Input } from '@/components/ui/Input';
11
+ import { Button } from '@/components/ui/Button';
12
+ import { useAuth } from '@/providers/AuthProvider';
13
+ import { useBasePath } from '@/providers/BasePathProvider';
14
+
15
+ const profileSchema = z.object({
16
+ firstname: z.string().min(2, 'First name is required'),
17
+ lastname: z.string().min(2, 'Last name is required'),
18
+ email: z.string().email('Enter a valid email address'),
19
+ phoneNumber: z.string().optional(),
20
+ });
21
+
22
+ type ProfileFormData = z.infer<typeof profileSchema>;
23
+
24
+ export function EditProfileScreen() {
25
+ const router = useRouter();
26
+ const { user, updateUser } = useAuth();
27
+ const { buildPath } = useBasePath();
28
+ const [isSubmitting, setIsSubmitting] = useState(false);
29
+ const [status, setStatus] = useState<{ type: 'success' | 'error'; message: string } | null>(
30
+ null
31
+ );
32
+
33
+ const {
34
+ register,
35
+ handleSubmit,
36
+ formState: { errors },
37
+ } = useForm<ProfileFormData>({
38
+ resolver: zodResolver(profileSchema),
39
+ defaultValues: {
40
+ firstname: user?.firstname || '',
41
+ lastname: user?.lastname || '',
42
+ email: user?.email || '',
43
+ phoneNumber: user?.phoneNumber || '',
44
+ },
45
+ });
46
+
47
+ if (!user) {
48
+ router.push(buildPath('/login'));
49
+ return null;
50
+ }
51
+
52
+ const onSubmit = async (data: ProfileFormData) => {
53
+ setIsSubmitting(true);
54
+ setStatus(null);
55
+ try {
56
+ await updateUser(data);
57
+ setStatus({ type: 'success', message: 'Profile updated successfully' });
58
+ router.push(buildPath('/account'));
59
+ } catch (error: any) {
60
+ setStatus({
61
+ type: 'error',
62
+ message: error.response?.data?.message || 'Failed to update profile',
63
+ });
64
+ } finally {
65
+ setIsSubmitting(false);
66
+ }
67
+ };
68
+
69
+ return (
70
+ <div className="min-h-screen bg-slate-50 text-slate-900">
71
+ <div className="container mx-auto px-4 pb-16 pt-10">
72
+ <motion.div
73
+ initial={{ opacity: 0, y: 18 }}
74
+ animate={{ opacity: 1, y: 0 }}
75
+ className="mx-auto max-w-3xl rounded-3xl border border-slate-200 bg-white p-8 shadow-xl shadow-primary-50"
76
+ >
77
+ <div className="flex items-center gap-3">
78
+ <span className="flex h-11 w-11 items-center justify-center rounded-2xl bg-primary-50 text-primary-600">
79
+ <User className="h-5 w-5" />
80
+ </span>
81
+ <div>
82
+ <p className="text-xs font-semibold uppercase tracking-[0.32em] text-slate-500">
83
+ Profile
84
+ </p>
85
+ <h1 className="text-2xl font-semibold text-slate-900">Edit core information</h1>
86
+ </div>
87
+ </div>
88
+ <p className="mt-3 text-sm text-slate-600">
89
+ Update your name, email, and contact information so we can keep your deliveries and
90
+ pharmacist support aligned with your preferences.
91
+ </p>
92
+
93
+ {status && (
94
+ <div
95
+ className={`mt-4 flex items-start gap-2 rounded-2xl border px-4 py-3 text-sm ${
96
+ status.type === 'success'
97
+ ? 'border-green-200 bg-green-50 text-green-800'
98
+ : 'border-red-200 bg-red-50 text-red-700'
99
+ }`}
100
+ >
101
+ <span className="mt-[2px] text-base">{status.type === 'success' ? '✔' : '!'}</span>
102
+ <span>{status.message}</span>
103
+ </div>
104
+ )}
105
+
106
+ <form onSubmit={handleSubmit(onSubmit)} className="mt-8 space-y-6">
107
+ <div className="grid gap-4 md:grid-cols-2">
108
+ <Input
109
+ label="First name"
110
+ placeholder="Taylor"
111
+ {...register('firstname')}
112
+ error={errors.firstname?.message}
113
+ />
114
+ <Input
115
+ label="Last name"
116
+ placeholder="Reed"
117
+ {...register('lastname')}
118
+ error={errors.lastname?.message}
119
+ />
120
+ </div>
121
+
122
+ <div className="relative">
123
+ <Input
124
+ type="email"
125
+ label="Email address"
126
+ placeholder="you@example.com"
127
+ className="pl-10"
128
+ {...register('email')}
129
+ error={errors.email?.message}
130
+ />
131
+ <Mail className="absolute left-3 top-[38px] h-4 w-4 text-slate-400" />
132
+ </div>
133
+
134
+ <div className="relative">
135
+ <Input
136
+ type="tel"
137
+ label="Phone number"
138
+ placeholder="+1 (555) 123-4567"
139
+ className="pl-10"
140
+ {...register('phoneNumber')}
141
+ error={errors.phoneNumber?.message}
142
+ />
143
+ <Phone className="absolute left-3 top-[38px] h-4 w-4 text-slate-400" />
144
+ </div>
145
+
146
+ <div className="flex flex-wrap gap-3">
147
+ <Button type="submit" size="lg" isLoading={isSubmitting}>
148
+ Save changes
149
+ </Button>
150
+ <Button
151
+ type="button"
152
+ variant="outline"
153
+ size="lg"
154
+ className="border-slate-300 text-slate-800 hover:bg-slate-50"
155
+ onClick={() => router.push(buildPath('/account'))}
156
+ >
157
+ Cancel
158
+ </Button>
159
+ </div>
160
+ </form>
161
+ </motion.div>
162
+ </div>
163
+ </div>
164
+ );
165
+ }
@@ -19,7 +19,6 @@ import {
19
19
  import { Input } from '@/components/ui/Input';
20
20
  import { Button } from '@/components/ui/Button';
21
21
  import { useAuth } from '@/providers/AuthProvider';
22
- import { toast } from 'sonner';
23
22
  import { useBasePath } from '@/providers/BasePathProvider';
24
23
 
25
24
  const loginSchema = z.object({
@@ -34,9 +33,12 @@ export function LoginScreen() {
34
33
  const { buildPath } = useBasePath();
35
34
  const searchParams = useSearchParams();
36
35
  const redirectUrl = searchParams?.get('redirect') || buildPath('/');
37
- const { login } = useAuth();
36
+ const { login, user, isLoading } = useAuth();
38
37
  const [showPassword, setShowPassword] = useState(false);
39
38
  const [isSubmitting, setIsSubmitting] = useState(false);
39
+ const [status, setStatus] = useState<{ type: 'success' | 'error'; message: string } | null>(
40
+ null
41
+ );
40
42
 
41
43
  const {
42
44
  register,
@@ -48,95 +50,76 @@ export function LoginScreen() {
48
50
 
49
51
  const onSubmit = async (data: LoginFormData) => {
50
52
  setIsSubmitting(true);
53
+ setStatus(null);
51
54
  try {
52
55
  await login(data);
53
- toast.success('Welcome back!');
56
+ setStatus({ type: 'success', message: 'Welcome back!' });
54
57
  router.push(redirectUrl);
55
58
  } catch (error: any) {
56
- toast.error(error.response?.data?.message || 'Invalid credentials');
59
+ setStatus({
60
+ type: 'error',
61
+ message: error?.response?.data?.message || 'Invalid credentials',
62
+ });
57
63
  } finally {
58
64
  setIsSubmitting(false);
59
65
  }
60
66
  };
61
67
 
62
- return (
63
- <div className="min-h-screen bg-slate-50">
64
- <div className="grid min-h-screen overflow-hidden bg-white lg:grid-cols-[1.1fr_0.9fr]">
65
- <motion.section
66
- initial={{ opacity: 0, x: -24 }}
67
- animate={{ opacity: 1, x: 0 }}
68
- transition={{ duration: 0.4 }}
69
- className="relative flex flex-col justify-between bg-gradient-to-br from-slate-700 via-slate-600 to-slate-700 px-10 py-14 text-white"
70
- >
71
- <div className="space-y-6">
72
- <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">
73
- <HeartPulse className="h-4 w-4" />
74
- Hey Pharmacist
75
- </span>
76
- <h1 className="text-4xl font-bold leading-tight lg:text-5xl">
77
- Pharmacy-grade care for your household
78
- </h1>
79
- <p className="max-w-xl text-white/80">
80
- Log in to unlock personalized regimens, pharmacist support, and fast delivery on
81
- wellness essentials curated just for you.
82
- </p>
83
- </div>
84
-
85
- <div className="grid gap-4 rounded-3xl bg-white/10 p-6 backdrop-blur">
86
- <div className="flex items-center gap-3">
87
- <ShieldCheck className="h-5 w-5 text-white" />
88
- <p className="text-sm text-white/80">
89
- HIPAA-compliant security keeps your health information protected.
90
- </p>
91
- </div>
92
- <div className="flex items-center gap-3">
93
- <Sparkles className="h-5 w-5 text-white" />
94
- <p className="text-sm text-white/80">
95
- Pharmacists ready to chat in under 10 minutes for medication support.
96
- </p>
97
- </div>
98
- </div>
99
-
100
- <div className="flex items-center gap-6 text-sm text-white/80">
101
- <span>Need an account?</span>
102
- <Link
103
- href={buildPath('/register')}
104
- className="inline-flex items-center gap-2 rounded-full bg-white/15 px-4 py-2 font-semibold transition hover:bg-white/25"
105
- >
106
- Create one now
107
- <ArrowRight className="h-4 w-4" />
108
- </Link>
109
- </div>
110
- </motion.section>
68
+ if (!isLoading && user) {
69
+ router.push(buildPath('/account'));
70
+ return null;
71
+ }
111
72
 
73
+ return (
74
+ <div className="min-h-screen bg-gradient-to-b from-[#F8FAFC] to-[#EBF4FB]">
75
+ <div className="grid min-h-screen overflow-hidden pb-12">
112
76
  <motion.section
113
77
  initial={{ opacity: 0, x: 24 }}
114
78
  animate={{ opacity: 1, x: 0 }}
115
79
  transition={{ duration: 0.4 }}
116
80
  className="flex items-center justify-center px-6 py-12 lg:px-16"
117
81
  >
118
- <div className="w-full max-w-md space-y-10">
82
+ <div className="w-full max-w-lg space-y-10 text-center">
119
83
  <div className="space-y-2">
120
- <h2 className="text-3xl font-bold text-slate-900">Sign in</h2>
121
- <p className="text-sm text-slate-500">
122
- Welcome back! Enter your details to continue your personalized care plan.
84
+ <Lock strokeWidth={2} className='h-16 w-16 mx-auto text-white rounded-full bg-secondary m-2 mb-4 px-4' />
85
+ <h2 className="text-4xl text-secondary">Welcome Back</h2>
86
+ <p className="text-sm text-muted">Sign in to access your patient portal
123
87
  </p>
124
88
  </div>
125
89
 
126
- <form onSubmit={handleSubmit(onSubmit)} className="space-y-6 rounded-3xl border border-slate-100 bg-white p-8 shadow-lg shadow-primary-50">
127
- <div>
90
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-6 rounded-3xl border bg-white p-8"
91
+ style={{
92
+ boxShadow: '0px 4px 6px -4px #0000001A, 0px 10px 15px -3px #0000001A',
93
+ }}>
94
+ {status && (
95
+ <div
96
+ className={`flex items-start gap-2 rounded-2xl border px-4 py-3 text-sm ${
97
+ status.type === 'success'
98
+ ? 'border-green-200 bg-green-50 text-green-800'
99
+ : 'border-red-200 bg-red-50 text-red-700'
100
+ }`}
101
+ >
102
+ <span className="mt-[2px] text-base">{status.type === 'success' ? '✔' : '!'}</span>
103
+ <span>{status.message}</span>
104
+ </div>
105
+ )}
106
+
107
+ <div className='text-start text-secondary'>
108
+ <h2 className="text-sm text-secondary mb-3">Email Address <span className='text-primary-500'>*</span></h2>
128
109
  <Input
129
110
  type="email"
130
- label="Email address"
111
+ // label="Email address"
131
112
  placeholder="you@example.com"
132
113
  {...register('email')}
133
114
  error={errors.email?.message}
115
+ className='text-secondary'
134
116
  />
135
117
  </div>
136
- <div className="relative">
118
+ <div className="relative text-start text-secondary">
119
+ <h2 className="text-sm text-secondary mb-3">Password <span className='text-primary-500'>*</span></h2>
120
+
137
121
  <Input
138
122
  type={showPassword ? 'text' : 'password'}
139
- label="Password"
140
123
  placeholder="••••••••"
141
124
  {...register('password')}
142
125
  error={errors.password?.message}
@@ -160,23 +143,27 @@ export function LoginScreen() {
160
143
  </label>
161
144
  <Link
162
145
  href={buildPath('/forgot-password')}
163
- className="font-medium text-primary-600 transition hover:text-primary-700"
146
+ className="font-medium text-primary transition hover:opacity-80"
164
147
  >
165
148
  Forgot password?
166
149
  </Link>
167
150
  </div>
168
151
 
169
- <Button
152
+ <button
170
153
  type="submit"
171
- size="lg"
172
- isLoading={isSubmitting}
173
- className="w-full"
154
+ disabled={isSubmitting}
155
+ className="w-full bg-secondary hover:opacity-80 text-white font-medium py-3 px-4 rounded-lg transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
174
156
  >
175
- Sign in securely
176
- </Button>
157
+ {isSubmitting ? 'Signing in...' : 'Sign in'}
158
+ </button>
177
159
  </form>
178
160
 
179
- <div className="rounded-3xl border border-slate-100 bg-slate-50 p-6 text-sm text-slate-600">
161
+ <div className="mt-4">
162
+ <p className="text-muted">Don't have an account? <Link href={buildPath('/register')} className="font-medium text-primary transition hover:opacity-90">Sign up</Link></p>
163
+ </div>
164
+
165
+
166
+ {/* <div className="rounded-3xl border border-slate-100 bg-slate-50 p-6 text-sm text-slate-600">
180
167
  <div className="flex items-start gap-3">
181
168
  <Lock className="mt-0.5 h-5 w-5 text-primary-500" />
182
169
  <div>
@@ -187,10 +174,10 @@ export function LoginScreen() {
187
174
  </p>
188
175
  </div>
189
176
  </div>
190
- </div>
177
+ </div> */}
191
178
  </div>
192
179
  </motion.section>
193
180
  </div>
194
181
  </div>
195
182
  );
196
- }
183
+ }
@@ -9,12 +9,13 @@ import { Input } from '@/components/ui/Input';
9
9
  import { addressSchema, type AddressFormData } from '@/lib/validations/address';
10
10
  import { AddressesApi } from '@/lib/Apis/apis/addresses-api';
11
11
  import { AXIOS_CONFIG } from '@/lib/Apis/wrapper';
12
- import { toast } from 'sonner';
13
12
  import { ArrowLeft, MapPin } from 'lucide-react';
13
+ import { useNotification } from '@/providers/NotificationProvider';
14
14
 
15
15
  export default function NewAddressPage() {
16
16
  const router = useRouter();
17
17
  const [isSubmitting, setIsSubmitting] = useState(false);
18
+ const notification = useNotification();
18
19
 
19
20
  const {
20
21
  register,
@@ -43,7 +44,10 @@ export default function NewAddressPage() {
43
44
  phone: data.phone,
44
45
  });
45
46
 
46
- toast.success('Address added successfully');
47
+ notification.success(
48
+ 'Address added',
49
+ 'Your new address has been saved to your account.'
50
+ );
47
51
  router.back();
48
52
 
49
53
  } catch (error: any) {
@@ -66,17 +70,19 @@ export default function NewAddressPage() {
66
70
  errorMessage = 'Unable to connect to the server. Please check your internet connection.';
67
71
  }
68
72
 
69
- toast.error('Unable to save address', {
70
- description: errorMessage,
71
- duration: 5000
72
- });
73
+ notification.error(
74
+ 'Unable to save address',
75
+ errorMessage,
76
+ 5000
77
+ );
73
78
 
74
79
  // Show additional guidance for certain error types
75
80
  if (error.response?.status === 422) {
76
- toast.info('Address validation failed', {
77
- description: 'Make sure your address is complete and formatted correctly.',
78
- duration: 6000
79
- });
81
+ notification.info(
82
+ 'Address validation failed',
83
+ 'Make sure your address is complete and formatted correctly.',
84
+ 6000
85
+ );
80
86
  }
81
87
  } finally {
82
88
  setIsSubmitting(false);