@codesinger0/shared-components 1.1.85 → 1.1.86

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