@codesinger0/shared-components 1.1.34 → 1.1.35

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.
@@ -0,0 +1,900 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import ClubMembershipModal from '../clubMembership/ClubMembershipModal'
3
+ import ClubPromoModal from '../clubMembership/ClubPromoModal'
4
+ import { ArrowRight, Truck, Store, Crown, AlertCircle, Loader2, Calendar } from 'lucide-react';
5
+ import { BookOpen } from 'lucide-react';
6
+
7
+ const AVAILABLE_CITIES = [
8
+ 'פתח תקווה',
9
+ 'רמת גן',
10
+ 'גבעתיים',
11
+ 'בני ברק',
12
+ 'תל אביב',
13
+ 'ראש העין',
14
+ 'קרית אונו',
15
+ 'אור יהודה',
16
+ 'יהוד מונסון'
17
+ ];
18
+
19
+ const validateFullName = (name) => {
20
+ if (!name || !name.trim()) {
21
+ return 'שם מלא חובה';
22
+ }
23
+
24
+ const words = name.trim().split(/\s+/);
25
+ if (words.length < 2) {
26
+ return 'יש להזין שם פרטי ומשפחה';
27
+ }
28
+
29
+ return null;
30
+ };
31
+
32
+ const OrderForm = ({
33
+ onBack,
34
+ courseData,
35
+ workshopData,
36
+ PaymentModalComponent,
37
+ isAuthenticated, // useAuth hook provides this
38
+ currentUserData, updateUserProfile, updateClubMembership, //useUserContext provides this
39
+ createOrder, updateOrder, // useOrders hook provides this
40
+ settings, settingsLoading, // useSettings hook provides this
41
+ onNavigateToLogin,
42
+ addToast, // Inject toast function
43
+ cartItems,
44
+ totalPrice,
45
+ }) => {
46
+
47
+ const [nameError, setNameError] = useState('');
48
+ const [deliveryError, setDeliveryError] = useState('');
49
+ const [clubPromoModalOpen, setClubPromoModalOpen] = useState(false);
50
+ const [clubMembershipModalOpen, setClubMembershipModalOpen] = useState(false);
51
+ const [clubInfoVisible, setClubInfoVisible] = useState(false);
52
+
53
+ // Course and Workshop detection
54
+ const isCoursePurchase = !!courseData;
55
+ const isWorkshopPurchase = !!workshopData;
56
+ const isDigitalPurchase = isCoursePurchase; // Only courses are digital
57
+ const isUserClubMember = currentUserData?.isClubMember || false;
58
+ const shouldShowClubMembership = !isUserClubMember;
59
+ const clubDiscountPercentage = settings?.clubDiscountPercentage || 10;
60
+
61
+ const items = isCoursePurchase ? [courseData] : isWorkshopPurchase ? [workshopData] : cartItems;
62
+ const itemsTotal = isCoursePurchase
63
+ ? (courseData.discountPrice || courseData.price)
64
+ : isWorkshopPurchase
65
+ ? (workshopData.discountPrice || workshopData.price)
66
+ : totalPrice;
67
+
68
+
69
+ const [paymentModalOpen, setPaymentModalOpen] = useState(false);
70
+ const [paymentData, setPaymentData] = useState(null);
71
+ const [createdOrderId, setCreatedOrderId] = useState(null);
72
+ const [submitError, setSubmitError] = useState('');
73
+ const [formData, setFormData] = useState({
74
+ name: '',
75
+ email: '',
76
+ phone: '',
77
+ deliveryMethod: isDigitalPurchase || isWorkshopPurchase ? 'digital' : 'delivery',
78
+ deliveryCity: '',
79
+ deliveryAddress: ''
80
+ });
81
+
82
+ const [isSubmitting, setIsSubmitting] = useState(false);
83
+ const [errors, setErrors] = useState({});
84
+
85
+ const minDeliveryAmount = settings.minDeliveryAmount;
86
+ const deliveryFee = settings.deliveryFee;
87
+
88
+ useEffect(() => {
89
+ if (currentUserData) {
90
+ setFormData(prev => ({
91
+ ...prev,
92
+ name: currentUserData.getFullName() || '',
93
+ email: currentUserData.email || '',
94
+ phone: currentUserData.phone || '',
95
+ deliveryCity: currentUserData.address ? '' : prev.deliveryCity,
96
+ deliveryMethod: isCoursePurchase ? 'digital' : prev.deliveryMethod
97
+ }));
98
+ }
99
+ }, [currentUserData, isCoursePurchase]);
100
+
101
+
102
+ const generateSimpleOrderNumber = () => {
103
+ return Math.floor(Math.random() * 9000000) + 1000000; // 7-digit number
104
+ };
105
+
106
+ const createPaymentLink = async (orderData) => {
107
+ const finalTotal = orderData.totalAmount;
108
+
109
+ // Map cart items to products data format
110
+ const productsData = orderData.items.map((item) => ({
111
+ catalogNumber: item.id,
112
+ name: item.name,
113
+ price: isUserClubMember ? item.price * (1 - clubDiscountPercentage / 100) : item.price,
114
+ quantity: item.quantity,
115
+ minQuantity: item.quantity,
116
+ ...(item.image_url && { url: encodeURIComponent(item.image_url) })
117
+ }));
118
+
119
+ // Add delivery fee as separate product if applicable
120
+ if (orderData.deliveryMethod === 'delivery' && deliveryFee > 0) {
121
+ productsData.push({
122
+ catalogNumber: 'delivery_fee',
123
+ name: 'דמי משלוח',
124
+ price: orderData.deliveryFee,
125
+ quantity: 1,
126
+ minQuantity: 1
127
+ });
128
+ }
129
+
130
+ const response = await fetch('https://us-central1-beauty-salon-b2e61.cloudfunctions.net/confectioneryShopPayment', {
131
+ method: 'POST',
132
+ headers: {
133
+ 'Content-Type': 'application/json'
134
+ },
135
+ body: JSON.stringify({
136
+ operation: 'createPaymentLink',
137
+ customerName: orderData.customerName,
138
+ customerPhone: orderData.customerPhone,
139
+ price: finalTotal,
140
+ orderNumber: orderData.simpleOrderNumber,
141
+ products: {
142
+ data: productsData
143
+ }
144
+ })
145
+ });
146
+
147
+ const result = await response.json();
148
+
149
+ if (result.success) {
150
+ return {
151
+ success: true,
152
+ data: {
153
+ paymentUrl: result.data.paymentUrl,
154
+ paymentId: result.data.paymentId,
155
+ expiresAt: result.data.expiresAt,
156
+ simpleOrderNumber: orderData.simpleOrderNumber
157
+ }
158
+ };
159
+ } else {
160
+ return {
161
+ success: false,
162
+ error: result.error || 'שגיאה ביצירת קישור התשלום'
163
+ };
164
+ }
165
+ };
166
+
167
+ const simulatePaymentLinkCreation = async (simpleOrderNumber) => {
168
+ // 50/50 success/failure simulation
169
+ const isSuccess = Math.random() > 0.01;
170
+
171
+ // Simulate API delay
172
+ await new Promise(resolve => setTimeout(resolve, 2000));
173
+
174
+ if (isSuccess) {
175
+ return {
176
+ success: true,
177
+ data: {
178
+ paymentLink: `https://mock-payment-link.com/pay/${simpleOrderNumber}`,
179
+ paymentId: `pay_${Date.now()}`,
180
+ simpleOrderNumber,
181
+ expiresAt: new Date(Date.now() + 5 * 60 * 1000) // 5 minutes
182
+ }
183
+ };
184
+ } else {
185
+ const errors = [
186
+ "שגיאת רשת - אנא נסו שוב",
187
+ "פרטי התשלום לא תקינים",
188
+ "השרת אינו זמין כרגע",
189
+ "שגיאה באימות הנתונים",
190
+ "תקלה זמנית - אנא נסו שוב מאוחר יותר"
191
+ ];
192
+ return {
193
+ success: false,
194
+ error: errors[Math.floor(Math.random() * errors.length)]
195
+ };
196
+ }
197
+ };
198
+
199
+ const validateForm = () => {
200
+ const newErrors = {};
201
+
202
+ if (!formData.name.trim()) {
203
+ newErrors.name = 'שם מלא חובה';
204
+ } else {
205
+ const words = formData.name.trim().split(/\s+/);
206
+ if (words.length < 2) {
207
+ newErrors.name = 'יש להזין שם פרטי ומשפחה';
208
+ }
209
+ }
210
+
211
+ if (!formData.email.trim()) {
212
+ newErrors.email = 'כתובת אימייל חובה';
213
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
214
+ newErrors.email = 'כתובת אימייל לא תקינה';
215
+ }
216
+
217
+ if (!formData.phone.trim()) {
218
+ newErrors.phone = 'מספר טלפון חובה';
219
+ } else if (!/^(05\d{8}|5\d{8})$/.test(formData.phone.replace(/\D/g, ''))) {
220
+ newErrors.phone = 'מספר טלפון לא תקין';
221
+ }
222
+
223
+ // Only validate delivery for physical delivery method
224
+ if (formData.deliveryMethod === 'delivery') {
225
+ const subtotal = (isCoursePurchase || isWorkshopPurchase) ? itemsTotal : totalPrice;
226
+ const clubDiscount = isUserClubMember ? (subtotal * (clubDiscountPercentage / 100)) : 0;
227
+ const currentTotal = subtotal - clubDiscount;
228
+
229
+ if (currentTotal < minDeliveryAmount) {
230
+ newErrors.delivery = `מינימום הזמנה למשלוח הוא ₪${minDeliveryAmount}`;
231
+ }
232
+ if (!formData.deliveryCity) {
233
+ newErrors.deliveryCity = 'בחירת עיר חובה';
234
+ }
235
+ if (!formData.deliveryAddress.trim()) {
236
+ newErrors.deliveryAddress = 'כתובת משלוח חובה';
237
+ }
238
+ }
239
+
240
+ setErrors(newErrors);
241
+ return Object.keys(newErrors).length === 0;
242
+ };
243
+
244
+ const grantCourseAccess = async (userId, courseIds) => {
245
+ try {
246
+ if (!currentUserData) return false;
247
+ const existingAccess = currentUserData.coursesAccess || [];
248
+ const newAccess = [...new Set([...existingAccess, ...courseIds])];
249
+ await updateUserProfile(currentUserData, { coursesAccess: newAccess });
250
+ return true;
251
+ } catch (error) {
252
+ console.error('Error granting course access:', error);
253
+ return false;
254
+ }
255
+ };
256
+
257
+ const handleInputChange = (field, value) => {
258
+ setFormData(prev => ({ ...prev, [field]: value }));
259
+ if (errors[field]) {
260
+ setErrors(prev => ({ ...prev, [field]: '' }));
261
+ }
262
+ };
263
+
264
+ const handleClubButtonClick = () => {
265
+ if (!isAuthenticated()) {
266
+ setClubInfoVisible(true);
267
+ setClubPromoModalOpen(true);
268
+ addToast('יש להתחבר כדי להצטרף למועדון הלקוחות', 'info');
269
+ } else if (!isUserClubMember) {
270
+ setClubMembershipModalOpen(true);
271
+ }
272
+ };
273
+
274
+ const handleLoginRedirect = () => {
275
+ setClubPromoModalOpen(false);
276
+ setClubMembershipModalOpen(false);
277
+ setClubInfoVisible(false);
278
+ if (onBack) {
279
+ onBack(true)
280
+ }
281
+
282
+ onNavigateToLogin();
283
+ };
284
+
285
+ const handleOnBack =() => {
286
+ debugger
287
+ onBack();
288
+ }
289
+
290
+ const handleMembershipComplete = () => {
291
+ // Component will re-render with updated user data
292
+ };
293
+
294
+ const handleSubmit = async (e) => {
295
+ e.preventDefault();
296
+
297
+ // Validate name before submission
298
+ const nameValidationError = validateFullName(formData.name);
299
+ if (nameValidationError) {
300
+ setNameError(nameValidationError);
301
+ return;
302
+ }
303
+
304
+ // Clear previous errors
305
+ setNameError('');
306
+ setDeliveryError('');
307
+
308
+ if (!validateForm()) return;
309
+
310
+ const subtotal = (isCoursePurchase || isWorkshopPurchase) ? itemsTotal : totalPrice;
311
+ const clubDiscount = isUserClubMember ? (subtotal * (clubDiscountPercentage / 100)) : 0;
312
+ const currentTotal = subtotal - clubDiscount;
313
+
314
+ // Check minimum delivery amount
315
+ if (formData.deliveryMethod === 'delivery') {
316
+ if (currentTotal < minDeliveryAmount) {
317
+ setDeliveryError(`מינימום הזמנה למשלוח הוא ₪${minDeliveryAmount}`);
318
+ return;
319
+ }
320
+ }
321
+
322
+ setIsSubmitting(true);
323
+ setSubmitError('');
324
+
325
+ // Calculate delivery cost - only charge if order meets minimum
326
+ const discountedSubtotal = subtotal - clubDiscount;
327
+ const deliveryCost = formData.deliveryMethod === 'delivery' && discountedSubtotal >= minDeliveryAmount ? deliveryFee : 0;
328
+ const grandTotal = discountedSubtotal + deliveryCost;
329
+
330
+ try {
331
+ const orderItems = isCoursePurchase || isWorkshopPurchase ? items : cartItems.map(item => ({
332
+ id: item.id,
333
+ name: item.name || item.title,
334
+ price: item.price,
335
+ quantity: item.quantity,
336
+ totalPrice: item.price * item.quantity,
337
+ }));
338
+
339
+ // Create order with payment_pending status
340
+ const orderData = {
341
+ simpleOrderNumber: generateSimpleOrderNumber(),
342
+ customerName: formData.name,
343
+ customerEmail: formData.email,
344
+ customerPhone: formData.phone,
345
+ deliveryMethod: formData.deliveryMethod,
346
+ deliveryCity: formData.deliveryMethod === 'delivery' ? formData.deliveryCity : null,
347
+ deliveryAddress: formData.deliveryMethod === 'delivery' ? formData.deliveryAddress : null,
348
+ items: isCoursePurchase ? [{
349
+ id: courseData.id,
350
+ name: courseData.title,
351
+ price: courseData.discountPrice || courseData.price,
352
+ quantity: 1,
353
+ totalPrice: courseData.discountPrice || courseData.price,
354
+ type: 'course'
355
+ }] : isWorkshopPurchase ? [{
356
+ id: workshopData.id,
357
+ name: workshopData.title,
358
+ price: workshopData.discountPrice || workshopData.price,
359
+ quantity: 1,
360
+ totalPrice: workshopData.discountPrice || workshopData.price,
361
+ type: 'workshop',
362
+ date: workshopData.date,
363
+ time: workshopData.time,
364
+ duration: workshopData.duration
365
+ }] : orderItems,
366
+ deliveryFee: formData.deliveryMethod === 'delivery' ? deliveryFee : 0,
367
+ totalAmount: grandTotal,
368
+ paymentStatus: 'payment_pending',
369
+ orderType: isCoursePurchase ? 'course' : isWorkshopPurchase ? 'workshop' : 'product'
370
+ };
371
+
372
+ // Create payment link
373
+ // const paymentResponse = await createPaymentLink(orderData);
374
+ const paymentResponse = await simulatePaymentLinkCreation(orderData);
375
+
376
+ if (paymentResponse.success) {
377
+ // Create order items in the format expected by the database
378
+ const orderItemsForDB = orderData.items.map((item) => ({
379
+ product_id: item.id,
380
+ product_name: item.name,
381
+ quantity: item.quantity,
382
+ unit_price: item.price,
383
+ total_price: item.price * item.quantity
384
+ }));
385
+
386
+ // Add complete order data with payment info
387
+ const completeOrderData = {
388
+ ...orderData,
389
+ items: orderItemsForDB,
390
+ paymentId: paymentResponse.data.paymentId,
391
+ paymentLink: paymentResponse.data.paymentUrl,
392
+ paymentExpiresAt: paymentResponse.data.expiresAt,
393
+ paymentStatus: 'link_created'
394
+ };
395
+
396
+ const newOrder = await createOrder(completeOrderData);
397
+ setCreatedOrderId(newOrder.id);
398
+
399
+ setPaymentData({
400
+ paymentLink: paymentResponse.data.paymentUrl,
401
+ paymentId: paymentResponse.data.paymentId,
402
+ expiresAt: paymentResponse.data.expiresAt,
403
+ simpleOrderNumber: paymentResponse.data.simpleOrderNumber
404
+ });
405
+
406
+ setPaymentModalOpen(true);
407
+
408
+ // Add course access for authenticated users
409
+ if (isCoursePurchase && isAuthenticated()) {
410
+ await grantCourseAccess(currentUserData.id, [courseData.id]);
411
+ }
412
+ } else {
413
+ setSubmitError(paymentResponse.error);
414
+ }
415
+ } catch (error) {
416
+ console.error('Order submission error:', error);
417
+ setSubmitError('שגיאה ביצירת ההזמנה או קישור התשלום. אנא נסו שוב.');
418
+ } finally {
419
+ setIsSubmitting(false);
420
+ }
421
+ };
422
+
423
+ if ((!cartItems || cartItems.length === 0) && !isCoursePurchase && !isWorkshopPurchase) {
424
+ return (
425
+ <div className="text-center py-12">
426
+ <h2 className="title mb-4">העגלה ריקה</h2>
427
+ <p className="subtitle">אין פריטים בעגלת הקניות</p>
428
+ </div>
429
+ );
430
+ }
431
+
432
+ if (settingsLoading) {
433
+ return (
434
+ <div className="min-h-screen bg-main flex items-center justify-center">
435
+ <div className="text-center">
436
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
437
+ <p className="content-text">טוען הגדרות...</p>
438
+ </div>
439
+ </div>
440
+ );
441
+ }
442
+
443
+ const subtotal = (isCoursePurchase || isWorkshopPurchase) ? itemsTotal : totalPrice;
444
+ const clubDiscount = isUserClubMember ? (subtotal * (clubDiscountPercentage / 100)) : 0;
445
+ const discountedSubtotal = subtotal - clubDiscount;
446
+ const deliveryCost = formData.deliveryMethod === 'delivery' ?
447
+ (discountedSubtotal >= minDeliveryAmount ? deliveryFee : 0) : 0;
448
+ const grandTotal = discountedSubtotal + deliveryCost;
449
+
450
+ return (
451
+ <div className="min-h-screen bg-main py-12 px-4" dir="rtl">
452
+ <div className="max-w-2xl mx-auto">
453
+
454
+ {/* Back Button */}
455
+ <button
456
+ onClick={handleOnBack}
457
+ className="flex items-center gap-2 mb-6 content-text hover:text-primary transition-colors duration-200"
458
+ >
459
+ <ArrowRight className="w-4 h-4" />
460
+ חזור לעגלה
461
+ </button>
462
+
463
+ <div className="glass-card p-8">
464
+ <h1 className="title text-center mb-8">
465
+ {isCoursePurchase ? 'רכישת קורס' : isWorkshopPurchase ? 'הרשמה לסדנה' : 'פרטים להזמנה'}
466
+ </h1>
467
+
468
+ {isCoursePurchase && (
469
+ <div className="glass-card p-4 border border-blue-200 mb-6">
470
+ <div className="flex items-center gap-2 text-blue-800">
471
+ <BookOpen className="w-5 h-5" />
472
+ <p className="content-text font-medium">
473
+ אתם רוכשים גישה דיגיטלית לקורס. לאחר השלמת התשלום תוכלו לגשת לקורס מיד.
474
+ </p>
475
+ </div>
476
+ </div>
477
+ )}
478
+
479
+ {isWorkshopPurchase && (
480
+ <div className="glass-card p-4 border border-green-200 mb-6">
481
+ <div className="flex items-center gap-2 text-green-800">
482
+ <Calendar className="w-5 h-5" />
483
+ <p className="content-text font-medium">
484
+ אתם נרשמים לסדנה פיזית. פרטי הסדנה יישלחו אליכם לאחר אישור ההרשמה.
485
+ </p>
486
+ </div>
487
+ </div>
488
+ )}
489
+
490
+ <div className={`space-y-6 ${isSubmitting ? 'pointer-events-none opacity-60' : ''}`}>
491
+
492
+ {/* Customer Details */}
493
+ <div className="space-y-4">
494
+ <h2 className="subtitle font-semibold">פרטים אישיים</h2>
495
+
496
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
497
+ <div>
498
+ <label className="block subtitle font-medium mb-2">שם מלא *</label>
499
+ <input
500
+ type="text"
501
+ value={formData.name}
502
+ onChange={(e) => handleInputChange('name', e.target.value)}
503
+ className={`w-full p-3 border rounded-lg focus:ring-2 focus:ring-primary transition-colors duration-200 ${errors.name ? 'border-red-500' : 'border-gray-300 focus:border-primary'
504
+ }`}
505
+ placeholder="שם פרטי ומשפחה"
506
+ />
507
+ {errors.name && (
508
+ <div className="flex items-center gap-2 mt-2 text-sm text-red-600">
509
+ <AlertCircle className="w-4 h-4" />
510
+ <span>{errors.name}</span>
511
+ </div>
512
+ )}
513
+ {nameError && (
514
+ <div className="flex items-center gap-2 mt-2 p-2 bg-red-50 border border-red-200 rounded">
515
+ <AlertCircle className="w-4 h-4 text-red-600" />
516
+ <span className="content-text text-red-700">{nameError}</span>
517
+ </div>
518
+ )}
519
+ </div>
520
+
521
+ <div>
522
+ <label className="block subtitle font-medium mb-2">טלפון *</label>
523
+ <input
524
+ type="tel"
525
+ value={formData.phone}
526
+ onChange={(e) => handleInputChange('phone', e.target.value)}
527
+ className={`w-full p-3 border rounded-lg focus:ring-2 focus:ring-primary transition-colors duration-200 ${errors.phone ? 'border-red-500' : 'border-gray-300 focus:border-primary'
528
+ }`}
529
+ placeholder="050-1234567"
530
+ dir="ltr"
531
+ />
532
+ {errors.phone && (
533
+ <div className="flex items-center gap-2 mt-2 text-sm text-red-600">
534
+ <AlertCircle className="w-4 h-4" />
535
+ <span>{errors.phone}</span>
536
+ </div>
537
+ )}
538
+ </div>
539
+ </div>
540
+
541
+ <div>
542
+ <label className="block subtitle font-medium mb-2">אימייל *</label>
543
+ <input
544
+ type="email"
545
+ value={formData.email}
546
+ onChange={(e) => handleInputChange('email', e.target.value)}
547
+ disabled={!!currentUserData?.email}
548
+ className={`w-full p-3 border rounded-lg focus:ring-2 focus:ring-primary transition-colors duration-200 ${errors.email ? 'border-red-500' : 'border-gray-300 focus:border-primary'
549
+ } ${currentUserData?.email ? 'bg-gray-100' : ''}`}
550
+ placeholder="example@email.com"
551
+ dir="ltr"
552
+ />
553
+ {errors.email && (
554
+ <div className="flex items-center gap-2 mt-2 text-sm text-red-600">
555
+ <AlertCircle className="w-4 h-4" />
556
+ <span>{errors.email}</span>
557
+ </div>
558
+ )}
559
+ </div>
560
+ </div>
561
+
562
+ {/* Delivery Method */}
563
+ {!isCoursePurchase && !isWorkshopPurchase && (<div className="space-y-4">
564
+ <h2 className="subtitle font-semibold">שיטת קבלת ההזמנה</h2>
565
+
566
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
567
+ <label className={`glass-card p-4 cursor-pointer border-2 transition-colors duration-200 ${formData.deliveryMethod === 'delivery'
568
+ ? 'border-primary bg-opacity-20'
569
+ : 'border-transparent hover:border-gray-300'
570
+ }`}>
571
+ <input
572
+ type="radio"
573
+ name="deliveryMethod"
574
+ value="delivery"
575
+ checked={formData.deliveryMethod === 'delivery'}
576
+ onChange={(e) => handleInputChange('deliveryMethod', e.target.value)}
577
+ className="sr-only"
578
+ />
579
+ <div className="flex items-center gap-3">
580
+ <Truck className="w-5 h-5 text-primary" />
581
+ <div>
582
+ <div className="subtitle font-medium">משלוח עד הבית</div>
583
+ <div className="content-text">מינימום ₪{minDeliveryAmount} • דמי משלוח ₪{deliveryFee}</div>
584
+ </div>
585
+ </div>
586
+ </label>
587
+
588
+ <label className={`glass-card p-4 cursor-pointer border-2 transition-colors duration-200 ${formData.deliveryMethod === 'pickup'
589
+ ? 'border-primary bg-opacity-20'
590
+ : 'border-transparent hover:border-gray-300'
591
+ }`}>
592
+ <input
593
+ type="radio"
594
+ name="deliveryMethod"
595
+ value="pickup"
596
+ checked={formData.deliveryMethod === 'pickup'}
597
+ onChange={(e) => handleInputChange('deliveryMethod', e.target.value)}
598
+ className="sr-only"
599
+ />
600
+ <div className="flex items-center gap-3">
601
+ <Store className="w-5 h-5 text-primary" />
602
+ <div>
603
+ <div className="subtitle font-medium">איסוף עצמי</div>
604
+ <div className="content-text">ללא דמי משלוח</div>
605
+ </div>
606
+ </div>
607
+ </label>
608
+ </div>
609
+
610
+ {errors.delivery && (
611
+ <div className="flex items-center gap-2 text-sm text-red-600">
612
+ <AlertCircle className="w-4 h-4" />
613
+ <span>{errors.delivery}</span>
614
+ </div>
615
+ )}
616
+ {deliveryError && (
617
+ <div className="flex items-center gap-2 mt-2 p-2 bg-red-50 border border-red-200 rounded">
618
+ <AlertCircle className="w-4 h-4 text-red-600" />
619
+ <span className="content-text text-red-700">{deliveryError}</span>
620
+ </div>
621
+ )}
622
+ </div>)}
623
+
624
+ {/* Address */}
625
+ {(formData.deliveryMethod === 'delivery' || isCoursePurchase || isWorkshopPurchase) && (
626
+ <div className="space-y-4 border-t border-gray-200 pt-6">
627
+ <h2 className="subtitle font-semibold">כתובת</h2>
628
+
629
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
630
+ <div>
631
+ <label className="block subtitle font-medium mb-2">עיר *</label>
632
+ {(isCoursePurchase || isWorkshopPurchase) ? (
633
+ <input
634
+ type="text"
635
+ value={formData.deliveryCity}
636
+ onChange={(e) => handleInputChange('deliveryCity', e.target.value)}
637
+ className={`w-full p-3 border rounded-lg focus:ring-2 focus:ring-primary transition-colors duration-200 ${errors.deliveryCity ? 'border-red-500' : 'border-gray-300 focus:border-primary'
638
+ }`}
639
+ placeholder="הזן עיר"
640
+ />
641
+ ) : (
642
+ <select
643
+ value={formData.deliveryCity}
644
+ onChange={(e) => handleInputChange('deliveryCity', e.target.value)}
645
+ className={`w-full p-3 border rounded-lg focus:ring-2 focus:ring-primary transition-colors duration-200 ${errors.deliveryCity ? 'border-red-500' : 'border-gray-300 focus:border-primary'
646
+ }`}
647
+ >
648
+ <option value="">בחר עיר</option>
649
+ {AVAILABLE_CITIES.map((city) => (
650
+ <option key={city} value={city}>{city}</option>
651
+ ))}
652
+ </select>
653
+ )}
654
+ {errors.deliveryCity && (
655
+ <div className="flex items-center gap-2 mt-2 text-sm text-red-600">
656
+ <AlertCircle className="w-4 h-4" />
657
+ <span>{errors.deliveryCity}</span>
658
+ </div>
659
+ )}
660
+ </div>
661
+
662
+ <div>
663
+ <label className="block subtitle font-medium mb-2">רחוב ומספר בית *</label>
664
+ <input
665
+ type="text"
666
+ value={formData.deliveryAddress}
667
+ onChange={(e) => handleInputChange('deliveryAddress', e.target.value)}
668
+ className={`w-full p-3 border rounded-lg focus:ring-2 focus:ring-primary transition-colors duration-200 ${errors.deliveryAddress ? 'border-red-500' : 'border-gray-300 focus:border-primary'
669
+ }`}
670
+ placeholder="רחוב 123, דירה 4"
671
+ />
672
+ {errors.deliveryAddress && (
673
+ <div className="flex items-center gap-2 mt-2 text-sm text-red-600">
674
+ <AlertCircle className="w-4 h-4" />
675
+ <span>{errors.deliveryAddress}</span>
676
+ </div>
677
+ )}
678
+ </div>
679
+ </div>
680
+
681
+ {formData.deliveryMethod === 'delivery' && (
682
+ <div className="glass-card p-4 bg-yellow-50 border border-yellow-200">
683
+ <p className="content-text text-yellow-800">
684
+ <strong>שים לב:</strong> אנחנו מבצעים משלוחים רק לערים המופיעות ברשימה.
685
+ </p>
686
+ </div>
687
+ )}
688
+ </div>
689
+ )}
690
+
691
+ {/* Order Summary */}
692
+ <div className="glass-card p-6 bg-opacity-50 border-t border-gray-200">
693
+ <h2 className="subtitle font-semibold mb-4">סיכום ההזמנה</h2>
694
+
695
+ <div className="space-y-3 mb-4">
696
+ {isCoursePurchase ? (
697
+ // Course Purchase Summary
698
+ <div className="flex justify-between items-start content-text py-3 border-b border-gray-200">
699
+ <div className="flex-1">
700
+ <div className="flex items-center gap-2 mb-1">
701
+ <BookOpen className="w-4 h-4 text-primary" />
702
+ <span className="content-text font-medium">{courseData.title}</span>
703
+ </div>
704
+ <p className="text-xs mt-1 line-clamp-2">
705
+ {courseData.description}
706
+ </p>
707
+ <div className="text-xs text-primary mt-2 bg-blue-50 px-2 py-1 rounded inline-block">
708
+ גישה דיגיטלית מיידית
709
+ </div>
710
+ </div>
711
+ <div className="text-left ml-4">
712
+ {courseData.discountPrice && courseData.discountPrice < courseData.price ? (
713
+ <div>
714
+ <div className="text-sm line-through">
715
+ ₪{courseData.price.toFixed(2)}
716
+ </div>
717
+ <div className="font-medium text-primary">
718
+ ₪{courseData.discountPrice.toFixed(2)}
719
+ </div>
720
+ </div>
721
+ ) : (
722
+ <div className="font-medium">₪{courseData.price.toFixed(2)}</div>
723
+ )}
724
+ </div>
725
+ </div>
726
+ ) : isWorkshopPurchase ? (
727
+ // Workshop Purchase Summary
728
+ <div className="flex justify-between items-start content-text py-3 border-b border-gray-200">
729
+ <div className="flex-1">
730
+ <div className="flex items-center gap-2 mb-1">
731
+ <Calendar className="w-4 h-4 text-primary" />
732
+ <span className="content-text font-medium">{workshopData.title}</span>
733
+ </div>
734
+ <p className="text-xs mt-1 line-clamp-2">
735
+ {workshopData.description}
736
+ </p>
737
+ <div className="flex items-center gap-4 mt-2 text-xs">
738
+ <div className="flex items-center gap-1">
739
+ <Calendar className="w-3 h-3" />
740
+ <span>{workshopData.date}</span>
741
+ </div>
742
+ <div className="flex items-center gap-1">
743
+ <span>{workshopData.time}</span>
744
+ </div>
745
+ <div className="flex items-center gap-1">
746
+ <span>({workshopData.duration})</span>
747
+ </div>
748
+ </div>
749
+ {workshopData.location && (
750
+ <div className="flex items-center gap-1 mt-1 text-xs">
751
+ <MapPin className="w-3 h-3" />
752
+ <span>{workshopData.location}</span>
753
+ </div>
754
+ )}
755
+ </div>
756
+ <div className="text-left ml-4">
757
+ {workshopData.discountPrice && workshopData.discountPrice < workshopData.price ? (
758
+ <div>
759
+ <div className="text-sm line-through">
760
+ ₪{workshopData.price.toFixed(2)}
761
+ </div>
762
+ <div className="font-medium text-primary">
763
+ ₪{workshopData.discountPrice.toFixed(2)}
764
+ </div>
765
+ </div>
766
+ ) : (
767
+ <div className="font-medium">₪{workshopData.price.toFixed(2)}</div>
768
+ )}
769
+ </div>
770
+ </div>
771
+ ) : (
772
+ // Regular Cart Items Summary
773
+ cartItems.map((item) => (
774
+ <div key={item.id} className="flex justify-between items-center content-text py-2 border-b border-gray-200 last:border-b-0">
775
+ <div className="flex items-center gap-2">
776
+ <span className="content-text font-medium">{item.name}</span>
777
+ <span className="content-text">× {item.quantity}</span>
778
+ </div>
779
+ <span className="font-medium">₪{(item.price * item.quantity).toFixed(2)}</span>
780
+ </div>
781
+ ))
782
+ )}
783
+ </div>
784
+
785
+ <div className="p-4 rounded-lg space-y-2">
786
+ <div className="flex justify-between content-text">
787
+ <span>סה״כ מוצרים:</span>
788
+ <span>₪{subtotal.toFixed(2)}</span>
789
+ </div>
790
+
791
+ {clubDiscount > 0 && (
792
+ <div className="flex justify-between content-text text-green-600">
793
+ <span>הנחת מועדון לקוחות ({clubDiscountPercentage}%):</span>
794
+ <span>-₪{clubDiscount.toFixed(2)}</span>
795
+ </div>
796
+ )}
797
+
798
+ {formData.deliveryMethod === 'delivery' && (
799
+ <div className="flex justify-between content-text">
800
+ <span>דמי משלוח:</span>
801
+ <span>₪{deliveryCost.toFixed(2)}</span>
802
+ </div>
803
+ )}
804
+
805
+ <div className="flex justify-between subtitle font-semibold text-lg border-t pt-2">
806
+ <span>סה״כ לתשלום:</span>
807
+ <span className="text-primary">₪{grandTotal.toFixed(2)}</span>
808
+ </div>
809
+ </div>
810
+ </div>
811
+
812
+ {shouldShowClubMembership && (
813
+ <>
814
+ <div className="glass-card p-4 bg-gradient-to-r from-primary/10 to-primary-dark/20 border border-primary/30">
815
+ <div className="flex items-center gap-3 mb-3">
816
+ <Crown className="w-6 h-6 text-yellow-600" />
817
+ <h3 className="subtitle font-bold">הצטרף למועדון הלקוחות וחסוך {clubDiscountPercentage}%!</h3>
818
+ </div>
819
+ <p className="content-text mb-3">
820
+ חברי מועדון הלקוחות זוכים ב-{clubDiscountPercentage}% הנחה על כל הזמנה + עדכונים על מבצעים מיוחדים
821
+ </p>
822
+ <button
823
+ type="button"
824
+ className="btn-secondary w-full flex items-center justify-center gap-2"
825
+ onClick={handleClubButtonClick}
826
+ >
827
+ <Crown className="w-4 h-4" />
828
+ {!isAuthenticated() ? 'התחבר והירשם למועדון' : 'הירשם למועדון הלקוחות'}
829
+ </button>
830
+ </div>
831
+ {clubInfoVisible && !isAuthenticated() && (
832
+ <div className="mt-2 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-700">
833
+ יש להתחבר כדי להצטרף למועדון הלקוחות
834
+ </div>
835
+ )}
836
+ </>
837
+ )}
838
+
839
+ {/* Submit Button */}
840
+ <button
841
+ type="button"
842
+ onClick={handleSubmit}
843
+ disabled={isSubmitting || Object.keys(errors).some(key => errors[key])}
844
+ className="w-full btn-primary py-4 text-big font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
845
+ >
846
+ {isSubmitting ? (
847
+ <div className="flex items-center justify-center gap-2">
848
+ <Loader2 className="w-5 h-5 animate-spin" />
849
+ יוצר הזמנה...
850
+ </div>
851
+ ) : (
852
+ isSubmitting ? 'יוצר קישור תשלום...' : 'שלח הזמנה'
853
+ )}
854
+ </button>
855
+
856
+ {submitError && (
857
+ <div className="flex items-center gap-2 mt-4 p-3 bg-red-50 border border-red-200 rounded-lg">
858
+ <AlertCircle className="w-5 h-5 text-red-600" />
859
+ <span className="content-text text-red-700">{submitError}</span>
860
+ </div>
861
+ )}
862
+
863
+ {!isDigitalPurchase && (<p className="content-text text-center text-gray-500">
864
+ {isWorkshopPurchase
865
+ ? 'לאחר שליחת ההרשמה נחזור אליכם לאישור ההשתתפות ופרטי הסדנה'
866
+ : 'לאחר שליחת ההזמנה נחזור אליכם תוך 24 שעות לאישור הזמינות ותיאום המשלוח/איסוף'
867
+ }
868
+ </p>)}
869
+ </div>
870
+ </div>
871
+ </div>
872
+
873
+ <PaymentModalComponent
874
+ isOpen={paymentModalOpen}
875
+ onClose={() => setPaymentModalOpen(false)}
876
+ paymentUrl={paymentData?.paymentLink}
877
+ paymentId={paymentData?.paymentId}
878
+ orderId={createdOrderId}
879
+ simpleOrderNumber={paymentData?.simpleOrderNumber}
880
+ updateOrder={updateOrder}
881
+ />
882
+
883
+ <ClubPromoModal
884
+ isOpen={clubPromoModalOpen}
885
+ onClose={() => setClubPromoModalOpen(false)}
886
+ onLoginRedirect={handleLoginRedirect}
887
+ />
888
+
889
+ <ClubMembershipModal
890
+ isOpen={clubMembershipModalOpen}
891
+ onClose={() => setClubMembershipModalOpen(false)}
892
+ onMembershipComplete={handleMembershipComplete}
893
+ userEmail={currentUserData?.email}
894
+ updateClubMembership={updateClubMembership}
895
+ />
896
+ </div>
897
+ );
898
+ };
899
+
900
+ export default OrderForm;
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ export { default as FullscreenCarousel } from './components/FullscreenCarousel'
27
27
  export { default as ItemDetailsModal } from './components/modals/ItemDetailsModal'
28
28
  export { default as ClubMembershipModal } from './components/clubMembership/ClubMembershipModal'
29
29
  export { default as ClubPromoModal } from './components/clubMembership/ClubPromoModal'
30
+ export { default as OrderForm } from './components/cart/OrderForm'
30
31
 
31
32
  // Context
32
33
  export { ItemModalProvider, useItemModal } from './context/ItemModalContext';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesinger0/shared-components",
3
- "version": "1.1.34",
3
+ "version": "1.1.35",
4
4
  "description": "Shared React components for customer projects",
5
5
  "main": "dist/index.js",
6
6
  "files": [