@codesinger0/shared-components 1.1.38 → 1.1.40
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,33 @@ const OrderForm = ({
|
|
|
83
84
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
84
85
|
const [errors, setErrors] = useState({});
|
|
85
86
|
|
|
87
|
+
const localScrollContainerRef = useRef(null);
|
|
88
|
+
const activeScrollContainerRef = scrollContainerRef || localScrollContainerRef;
|
|
89
|
+
|
|
90
|
+
const scrollToAnchor = (fieldName) => {
|
|
91
|
+
// Small delay to ensure error messages are rendered
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
const element = document.getElementById(`${fieldName}`);
|
|
94
|
+
const container = activeScrollContainerRef.current;
|
|
95
|
+
|
|
96
|
+
if (element && container) {
|
|
97
|
+
// Get element position relative to container
|
|
98
|
+
const elementTop = element.offsetTop;
|
|
99
|
+
const containerHeight = container.clientHeight;
|
|
100
|
+
const elementHeight = element.offsetHeight;
|
|
101
|
+
|
|
102
|
+
// Calculate scroll position to center the element
|
|
103
|
+
const scrollTo = elementTop - (containerHeight / 2) + (elementHeight / 2);
|
|
104
|
+
|
|
105
|
+
// Smooth scroll within the container
|
|
106
|
+
container.scrollTo({
|
|
107
|
+
top: Math.max(0, scrollTo),
|
|
108
|
+
behavior: 'smooth'
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}, 100);
|
|
112
|
+
};
|
|
113
|
+
|
|
86
114
|
const minDeliveryAmount = settings.minDeliveryAmount;
|
|
87
115
|
const deliveryFee = settings.deliveryFee;
|
|
88
116
|
|
|
@@ -199,26 +227,33 @@ const OrderForm = ({
|
|
|
199
227
|
|
|
200
228
|
const validateForm = () => {
|
|
201
229
|
const newErrors = {};
|
|
230
|
+
let firstErrorField = null;
|
|
202
231
|
|
|
203
232
|
if (!formData.name.trim()) {
|
|
204
233
|
newErrors.name = 'שם מלא חובה';
|
|
234
|
+
if (!firstErrorField) firstErrorField = 'name';
|
|
205
235
|
} else {
|
|
206
236
|
const words = formData.name.trim().split(/\s+/);
|
|
207
237
|
if (words.length < 2) {
|
|
208
238
|
newErrors.name = 'יש להזין שם פרטי ומשפחה';
|
|
239
|
+
if (!firstErrorField) firstErrorField = 'name';
|
|
209
240
|
}
|
|
210
241
|
}
|
|
211
242
|
|
|
212
243
|
if (!formData.email.trim()) {
|
|
213
244
|
newErrors.email = 'כתובת אימייל חובה';
|
|
245
|
+
if (!firstErrorField) firstErrorField = 'email';
|
|
214
246
|
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
|
215
247
|
newErrors.email = 'כתובת אימייל לא תקינה';
|
|
248
|
+
if (!firstErrorField) firstErrorField = 'email';
|
|
216
249
|
}
|
|
217
250
|
|
|
218
251
|
if (!formData.phone.trim()) {
|
|
219
252
|
newErrors.phone = 'מספר טלפון חובה';
|
|
253
|
+
if (!firstErrorField) firstErrorField = 'phone';
|
|
220
254
|
} else if (!/^(05\d{8}|5\d{8})$/.test(formData.phone.replace(/\D/g, ''))) {
|
|
221
255
|
newErrors.phone = 'מספר טלפון לא תקין';
|
|
256
|
+
if (!firstErrorField) firstErrorField = 'phone';
|
|
222
257
|
}
|
|
223
258
|
|
|
224
259
|
// Only validate delivery for physical delivery method
|
|
@@ -229,17 +264,23 @@ const OrderForm = ({
|
|
|
229
264
|
|
|
230
265
|
if (currentTotal < minDeliveryAmount) {
|
|
231
266
|
newErrors.delivery = `מינימום הזמנה למשלוח הוא ₪${minDeliveryAmount}`;
|
|
267
|
+
if (!firstErrorField) firstErrorField = 'delivery';
|
|
232
268
|
}
|
|
233
269
|
if (!formData.deliveryCity) {
|
|
234
270
|
newErrors.deliveryCity = 'בחירת עיר חובה';
|
|
271
|
+
if (!firstErrorField) firstErrorField = 'deliveryCity';
|
|
235
272
|
}
|
|
236
273
|
if (!formData.deliveryAddress.trim()) {
|
|
237
274
|
newErrors.deliveryAddress = 'כתובת משלוח חובה';
|
|
275
|
+
if (!firstErrorField) firstErrorField = 'deliveryAddress';
|
|
238
276
|
}
|
|
239
277
|
}
|
|
240
278
|
|
|
241
279
|
setErrors(newErrors);
|
|
242
|
-
return
|
|
280
|
+
return {
|
|
281
|
+
isValid: Object.keys(newErrors).length === 0,
|
|
282
|
+
firstErrorField
|
|
283
|
+
};
|
|
243
284
|
};
|
|
244
285
|
|
|
245
286
|
const grantCourseAccess = async (userId, courseIds) => {
|
|
@@ -297,11 +338,12 @@ const OrderForm = ({
|
|
|
297
338
|
|
|
298
339
|
const handleSubmit = async (e) => {
|
|
299
340
|
e.preventDefault();
|
|
300
|
-
|
|
341
|
+
debugger
|
|
301
342
|
// Validate name before submission
|
|
302
343
|
const nameValidationError = validateFullName(formData.name);
|
|
303
344
|
if (nameValidationError) {
|
|
304
345
|
setNameError(nameValidationError);
|
|
346
|
+
scrollToAnchor('name-field');
|
|
305
347
|
return;
|
|
306
348
|
}
|
|
307
349
|
|
|
@@ -309,7 +351,14 @@ const OrderForm = ({
|
|
|
309
351
|
setNameError('');
|
|
310
352
|
setDeliveryError('');
|
|
311
353
|
|
|
312
|
-
|
|
354
|
+
const validation = validateForm();
|
|
355
|
+
if (!validation.isValid) {
|
|
356
|
+
// Scroll to the first field with an error
|
|
357
|
+
if (validation.firstErrorField) {
|
|
358
|
+
scrollToAnchor(`${validation.firstErrorField}-field`);
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
313
362
|
|
|
314
363
|
const subtotal = (isCoursePurchase || isWorkshopPurchase) ? itemsTotal : totalPrice;
|
|
315
364
|
const clubDiscount = isUserClubMember ? (subtotal * (clubDiscountPercentage / 100)) : 0;
|
|
@@ -452,7 +501,11 @@ const OrderForm = ({
|
|
|
452
501
|
const grandTotal = discountedSubtotal + deliveryCost;
|
|
453
502
|
|
|
454
503
|
return (
|
|
455
|
-
<div
|
|
504
|
+
<div
|
|
505
|
+
ref={scrollContainerRef ? null : localScrollContainerRef}
|
|
506
|
+
className={scrollContainerRef ? "min-h-screen bg-main py-12 px-4" : "h-screen overflow-y-auto bg-main py-12 px-4"}
|
|
507
|
+
dir="rtl"
|
|
508
|
+
>
|
|
456
509
|
<div className="max-w-2xl mx-auto">
|
|
457
510
|
|
|
458
511
|
{/* Back Button */}
|
|
@@ -498,7 +551,7 @@ const OrderForm = ({
|
|
|
498
551
|
<h2 className="subtitle font-semibold">פרטים אישיים</h2>
|
|
499
552
|
|
|
500
553
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
501
|
-
<div>
|
|
554
|
+
<div id="name-field">
|
|
502
555
|
<label className="block subtitle font-medium mb-2">שם מלא *</label>
|
|
503
556
|
<input
|
|
504
557
|
type="text"
|
|
@@ -522,7 +575,7 @@ const OrderForm = ({
|
|
|
522
575
|
)}
|
|
523
576
|
</div>
|
|
524
577
|
|
|
525
|
-
<div>
|
|
578
|
+
<div id="phone-field">
|
|
526
579
|
<label className="block subtitle font-medium mb-2">טלפון *</label>
|
|
527
580
|
<input
|
|
528
581
|
type="tel"
|
|
@@ -542,7 +595,7 @@ const OrderForm = ({
|
|
|
542
595
|
</div>
|
|
543
596
|
</div>
|
|
544
597
|
|
|
545
|
-
<div>
|
|
598
|
+
<div id="email-field">
|
|
546
599
|
<label className="block subtitle font-medium mb-2">אימייל *</label>
|
|
547
600
|
<input
|
|
548
601
|
type="email"
|
|
@@ -631,7 +684,7 @@ const OrderForm = ({
|
|
|
631
684
|
<h2 className="subtitle font-semibold">כתובת</h2>
|
|
632
685
|
|
|
633
686
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
634
|
-
<div>
|
|
687
|
+
<div id="deliveryCity-field">
|
|
635
688
|
<label className="block subtitle font-medium mb-2">עיר *</label>
|
|
636
689
|
{(isCoursePurchase || isWorkshopPurchase) ? (
|
|
637
690
|
<input
|
|
@@ -663,7 +716,7 @@ const OrderForm = ({
|
|
|
663
716
|
)}
|
|
664
717
|
</div>
|
|
665
718
|
|
|
666
|
-
<div>
|
|
719
|
+
<div id="deliveryAddress-field">
|
|
667
720
|
<label className="block subtitle font-medium mb-2">רחוב ומספר בית *</label>
|
|
668
721
|
<input
|
|
669
722
|
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">
|