@codesinger0/shared-components 1.1.37 → 1.1.39
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import ClubMembershipModal from '../clubMembership/ClubMembershipModal'
|
|
3
3
|
import ClubPromoModal from '../clubMembership/ClubPromoModal'
|
|
4
4
|
import { ArrowRight, Truck, Store, Crown, AlertCircle, Loader2, Calendar } from 'lucide-react';
|
|
@@ -42,7 +42,8 @@ const OrderForm = ({
|
|
|
42
42
|
addToast, // Inject toast function
|
|
43
43
|
cartItems,
|
|
44
44
|
totalPrice,
|
|
45
|
-
paymentLinkCreationURL
|
|
45
|
+
paymentLinkCreationURL,
|
|
46
|
+
scrollContainerRef
|
|
46
47
|
}) => {
|
|
47
48
|
|
|
48
49
|
const [nameError, setNameError] = useState('');
|
|
@@ -83,6 +84,30 @@ const OrderForm = ({
|
|
|
83
84
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
84
85
|
const [errors, setErrors] = useState({});
|
|
85
86
|
|
|
87
|
+
const scrollToAnchor = (fieldName) => {
|
|
88
|
+
// Small delay to ensure error messages are rendered
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
const element = document.getElementById(`${fieldName}`);
|
|
91
|
+
const container = scrollContainerRef.current;
|
|
92
|
+
|
|
93
|
+
if (element && container) {
|
|
94
|
+
// Get element position relative to container
|
|
95
|
+
const elementTop = element.offsetTop;
|
|
96
|
+
const containerHeight = container.clientHeight;
|
|
97
|
+
const elementHeight = element.offsetHeight;
|
|
98
|
+
|
|
99
|
+
// Calculate scroll position to center the element
|
|
100
|
+
const scrollTo = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
|
101
|
+
|
|
102
|
+
// Smooth scroll within the container
|
|
103
|
+
container.scrollTo({
|
|
104
|
+
top: Math.max(0, scrollTo),
|
|
105
|
+
behavior: 'smooth'
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}, 100);
|
|
109
|
+
};
|
|
110
|
+
|
|
86
111
|
const minDeliveryAmount = settings.minDeliveryAmount;
|
|
87
112
|
const deliveryFee = settings.deliveryFee;
|
|
88
113
|
|
|
@@ -199,26 +224,33 @@ const OrderForm = ({
|
|
|
199
224
|
|
|
200
225
|
const validateForm = () => {
|
|
201
226
|
const newErrors = {};
|
|
227
|
+
let firstErrorField = null;
|
|
202
228
|
|
|
203
229
|
if (!formData.name.trim()) {
|
|
204
230
|
newErrors.name = 'שם מלא חובה';
|
|
231
|
+
if (!firstErrorField) firstErrorField = 'name';
|
|
205
232
|
} else {
|
|
206
233
|
const words = formData.name.trim().split(/\s+/);
|
|
207
234
|
if (words.length < 2) {
|
|
208
235
|
newErrors.name = 'יש להזין שם פרטי ומשפחה';
|
|
236
|
+
if (!firstErrorField) firstErrorField = 'name';
|
|
209
237
|
}
|
|
210
238
|
}
|
|
211
239
|
|
|
212
240
|
if (!formData.email.trim()) {
|
|
213
241
|
newErrors.email = 'כתובת אימייל חובה';
|
|
242
|
+
if (!firstErrorField) firstErrorField = 'email';
|
|
214
243
|
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
|
215
244
|
newErrors.email = 'כתובת אימייל לא תקינה';
|
|
245
|
+
if (!firstErrorField) firstErrorField = 'email';
|
|
216
246
|
}
|
|
217
247
|
|
|
218
248
|
if (!formData.phone.trim()) {
|
|
219
249
|
newErrors.phone = 'מספר טלפון חובה';
|
|
250
|
+
if (!firstErrorField) firstErrorField = 'phone';
|
|
220
251
|
} else if (!/^(05\d{8}|5\d{8})$/.test(formData.phone.replace(/\D/g, ''))) {
|
|
221
252
|
newErrors.phone = 'מספר טלפון לא תקין';
|
|
253
|
+
if (!firstErrorField) firstErrorField = 'phone';
|
|
222
254
|
}
|
|
223
255
|
|
|
224
256
|
// Only validate delivery for physical delivery method
|
|
@@ -229,17 +261,23 @@ const OrderForm = ({
|
|
|
229
261
|
|
|
230
262
|
if (currentTotal < minDeliveryAmount) {
|
|
231
263
|
newErrors.delivery = `מינימום הזמנה למשלוח הוא ₪${minDeliveryAmount}`;
|
|
264
|
+
if (!firstErrorField) firstErrorField = 'delivery';
|
|
232
265
|
}
|
|
233
266
|
if (!formData.deliveryCity) {
|
|
234
267
|
newErrors.deliveryCity = 'בחירת עיר חובה';
|
|
268
|
+
if (!firstErrorField) firstErrorField = 'deliveryCity';
|
|
235
269
|
}
|
|
236
270
|
if (!formData.deliveryAddress.trim()) {
|
|
237
271
|
newErrors.deliveryAddress = 'כתובת משלוח חובה';
|
|
272
|
+
if (!firstErrorField) firstErrorField = 'deliveryAddress';
|
|
238
273
|
}
|
|
239
274
|
}
|
|
240
275
|
|
|
241
276
|
setErrors(newErrors);
|
|
242
|
-
return
|
|
277
|
+
return {
|
|
278
|
+
isValid: Object.keys(newErrors).length === 0,
|
|
279
|
+
firstErrorField
|
|
280
|
+
};
|
|
243
281
|
};
|
|
244
282
|
|
|
245
283
|
const grantCourseAccess = async (userId, courseIds) => {
|
|
@@ -297,11 +335,12 @@ const OrderForm = ({
|
|
|
297
335
|
|
|
298
336
|
const handleSubmit = async (e) => {
|
|
299
337
|
e.preventDefault();
|
|
300
|
-
|
|
338
|
+
debugger
|
|
301
339
|
// Validate name before submission
|
|
302
340
|
const nameValidationError = validateFullName(formData.name);
|
|
303
341
|
if (nameValidationError) {
|
|
304
342
|
setNameError(nameValidationError);
|
|
343
|
+
scrollToAnchor('name-field');
|
|
305
344
|
return;
|
|
306
345
|
}
|
|
307
346
|
|
|
@@ -309,7 +348,14 @@ const OrderForm = ({
|
|
|
309
348
|
setNameError('');
|
|
310
349
|
setDeliveryError('');
|
|
311
350
|
|
|
312
|
-
|
|
351
|
+
const validation = validateForm();
|
|
352
|
+
if (!validation.isValid) {
|
|
353
|
+
// Scroll to the first field with an error
|
|
354
|
+
if (validation.firstErrorField) {
|
|
355
|
+
scrollToAnchor(`${validation.firstErrorField}-field`);
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
313
359
|
|
|
314
360
|
const subtotal = (isCoursePurchase || isWorkshopPurchase) ? itemsTotal : totalPrice;
|
|
315
361
|
const clubDiscount = isUserClubMember ? (subtotal * (clubDiscountPercentage / 100)) : 0;
|
|
@@ -498,7 +544,7 @@ const OrderForm = ({
|
|
|
498
544
|
<h2 className="subtitle font-semibold">פרטים אישיים</h2>
|
|
499
545
|
|
|
500
546
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
501
|
-
<div>
|
|
547
|
+
<div id="name-field">
|
|
502
548
|
<label className="block subtitle font-medium mb-2">שם מלא *</label>
|
|
503
549
|
<input
|
|
504
550
|
type="text"
|
|
@@ -522,7 +568,7 @@ const OrderForm = ({
|
|
|
522
568
|
)}
|
|
523
569
|
</div>
|
|
524
570
|
|
|
525
|
-
<div>
|
|
571
|
+
<div id="phone-field">
|
|
526
572
|
<label className="block subtitle font-medium mb-2">טלפון *</label>
|
|
527
573
|
<input
|
|
528
574
|
type="tel"
|
|
@@ -542,7 +588,7 @@ const OrderForm = ({
|
|
|
542
588
|
</div>
|
|
543
589
|
</div>
|
|
544
590
|
|
|
545
|
-
<div>
|
|
591
|
+
<div id="email-field">
|
|
546
592
|
<label className="block subtitle font-medium mb-2">אימייל *</label>
|
|
547
593
|
<input
|
|
548
594
|
type="email"
|
|
@@ -569,8 +615,8 @@ const OrderForm = ({
|
|
|
569
615
|
|
|
570
616
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
571
617
|
<label className={`glass-card p-4 cursor-pointer border-2 transition-colors duration-200 ${formData.deliveryMethod === 'delivery'
|
|
572
|
-
? 'border-
|
|
573
|
-
: 'border-transparent hover:border-gray-
|
|
618
|
+
? 'border-gray-300 bg-opacity-20'
|
|
619
|
+
: 'border-transparent hover:border-gray-100'
|
|
574
620
|
}`}>
|
|
575
621
|
<input
|
|
576
622
|
type="radio"
|
|
@@ -590,8 +636,8 @@ const OrderForm = ({
|
|
|
590
636
|
</label>
|
|
591
637
|
|
|
592
638
|
<label className={`glass-card p-4 cursor-pointer border-2 transition-colors duration-200 ${formData.deliveryMethod === 'pickup'
|
|
593
|
-
? 'border-
|
|
594
|
-
: 'border-transparent hover:border-gray-
|
|
639
|
+
? 'border-gray-300 bg-opacity-20'
|
|
640
|
+
: 'border-transparent hover:border-gray-100'
|
|
595
641
|
}`}>
|
|
596
642
|
<input
|
|
597
643
|
type="radio"
|
|
@@ -631,7 +677,7 @@ const OrderForm = ({
|
|
|
631
677
|
<h2 className="subtitle font-semibold">כתובת</h2>
|
|
632
678
|
|
|
633
679
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
634
|
-
<div>
|
|
680
|
+
<div id="deliveryCity-field">
|
|
635
681
|
<label className="block subtitle font-medium mb-2">עיר *</label>
|
|
636
682
|
{(isCoursePurchase || isWorkshopPurchase) ? (
|
|
637
683
|
<input
|
|
@@ -663,7 +709,7 @@ const OrderForm = ({
|
|
|
663
709
|
)}
|
|
664
710
|
</div>
|
|
665
711
|
|
|
666
|
-
<div>
|
|
712
|
+
<div id="deliveryAddress-field">
|
|
667
713
|
<label className="block subtitle font-medium mb-2">רחוב ומספר בית *</label>
|
|
668
714
|
<input
|
|
669
715
|
type="text"
|
|
@@ -16,6 +16,8 @@ const ShoppingCartModal = ({ CheckoutComponent }) => {
|
|
|
16
16
|
removeFromCart
|
|
17
17
|
} = useCart();
|
|
18
18
|
|
|
19
|
+
const orderFormScrollContainerRef = useRef(null);
|
|
20
|
+
|
|
19
21
|
// local visible state that controls AnimatePresence mounting
|
|
20
22
|
const [localOpen, setLocalOpen] = useState(Boolean(isCartOpen));
|
|
21
23
|
const [showOrderForm, setShowOrderForm] = useState(false);
|
|
@@ -97,12 +99,13 @@ const ShoppingCartModal = ({ CheckoutComponent }) => {
|
|
|
97
99
|
>
|
|
98
100
|
{showOrderForm ? (
|
|
99
101
|
// Order Form View
|
|
100
|
-
<div className="h-full overflow-y-auto">
|
|
102
|
+
<div ref={orderFormScrollContainerRef} className="h-full overflow-y-auto">
|
|
101
103
|
{CheckoutComponent ? (
|
|
102
104
|
<CheckoutComponent
|
|
103
105
|
onBack={handleBackToCart}
|
|
104
106
|
cartItems={cartItems}
|
|
105
107
|
totalPrice={totalPrice}
|
|
108
|
+
scrollContainerRef={orderFormScrollContainerRef}
|
|
106
109
|
/>
|
|
107
110
|
) : (
|
|
108
111
|
<div className="p-6">
|