@codesinger0/shared-components 1.1.29 → 1.1.31
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,289 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import { X, Crown, Loader2, CheckCircle } from 'lucide-react';
|
|
4
|
+
import { useToast } from '../components/ToastProvider';
|
|
5
|
+
|
|
6
|
+
const ClubMembershipModal = ({
|
|
7
|
+
isOpen,
|
|
8
|
+
onClose,
|
|
9
|
+
onMembershipComplete,
|
|
10
|
+
userEmail,
|
|
11
|
+
updateClubMembership
|
|
12
|
+
}) => {
|
|
13
|
+
const [formData, setFormData] = useState({
|
|
14
|
+
firstName: '',
|
|
15
|
+
lastName: '',
|
|
16
|
+
phone: '',
|
|
17
|
+
address: '',
|
|
18
|
+
marketingConsent: false
|
|
19
|
+
});
|
|
20
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
21
|
+
const [isCompleted, setIsCompleted] = useState(false);
|
|
22
|
+
const [errors, setErrors] = useState({});
|
|
23
|
+
|
|
24
|
+
const addToast = useToast();
|
|
25
|
+
|
|
26
|
+
const handleInputChange = (field, value) => {
|
|
27
|
+
setFormData(prev => ({ ...prev, [field]: value }));
|
|
28
|
+
// Clear field error when user starts typing
|
|
29
|
+
if (errors[field]) {
|
|
30
|
+
setErrors(prev => ({ ...prev, [field]: '' }));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const validateForm = () => {
|
|
35
|
+
const newErrors = {};
|
|
36
|
+
|
|
37
|
+
if (!formData.firstName.trim()) {
|
|
38
|
+
newErrors.firstName = 'שם פרטי חובה';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!formData.lastName.trim()) {
|
|
42
|
+
newErrors.lastName = 'שם משפחה חובה';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!formData.phone.trim()) {
|
|
46
|
+
newErrors.phone = 'מספר טלפון חובה';
|
|
47
|
+
} else if (!/^(05\d{8}|5\d{8})$/.test(formData.phone.replace(/\D/g, ''))) {
|
|
48
|
+
newErrors.phone = 'מספר טלפון לא תקין';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!formData.address.trim()) {
|
|
52
|
+
newErrors.address = 'כתובת חובה';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!formData.marketingConsent) {
|
|
56
|
+
newErrors.marketingConsent = 'יש לאשר את הסכמתך לקבלת דיוור שיווקי';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setErrors(newErrors);
|
|
60
|
+
return Object.keys(newErrors).length === 0;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handleSubmit = async (e) => {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
|
|
66
|
+
if (!validateForm()) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setIsSubmitting(true);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// Update user with club membership and form data
|
|
74
|
+
await updateClubMembership({
|
|
75
|
+
firstName: formData.firstName.trim(),
|
|
76
|
+
lastName: formData.lastName.trim(),
|
|
77
|
+
phone: formData.phone.trim(),
|
|
78
|
+
address: formData.address.trim(),
|
|
79
|
+
isClubMember: true,
|
|
80
|
+
marketingConsent: formData.marketingConsent
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
setIsCompleted(true);
|
|
84
|
+
addToast('הצטרפת בהצלחה למועדון הלקוחות!', 'success');
|
|
85
|
+
|
|
86
|
+
// After short delay, notify parent and close
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
if (onMembershipComplete) {
|
|
89
|
+
onMembershipComplete();
|
|
90
|
+
}
|
|
91
|
+
onClose();
|
|
92
|
+
// Reset form for next time
|
|
93
|
+
setFormData({
|
|
94
|
+
firstName: '',
|
|
95
|
+
lastName: '',
|
|
96
|
+
phone: '',
|
|
97
|
+
address: '',
|
|
98
|
+
marketingConsent: false
|
|
99
|
+
});
|
|
100
|
+
setIsCompleted(false);
|
|
101
|
+
}, 2000);
|
|
102
|
+
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Error joining club:', error);
|
|
105
|
+
addToast('שגיאה בהצטרפות למועדון. אנא נסה שנית.', 'error');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
setIsSubmitting(false);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (!isOpen) return null;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<AnimatePresence>
|
|
115
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
116
|
+
<motion.div
|
|
117
|
+
initial={{ opacity: 0 }}
|
|
118
|
+
animate={{ opacity: 1 }}
|
|
119
|
+
exit={{ opacity: 0 }}
|
|
120
|
+
className="absolute inset-0 bg-black bg-opacity-50 backdrop-blur-sm"
|
|
121
|
+
onClick={onClose}
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
<motion.div
|
|
125
|
+
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
126
|
+
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
127
|
+
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
128
|
+
className="relative glass-card p-8 max-w-md w-full mx-4 max-h-[90vh] overflow-y-auto"
|
|
129
|
+
>
|
|
130
|
+
<button
|
|
131
|
+
onClick={onClose}
|
|
132
|
+
className="absolute top-4 left-4 p-2 rounded-full hover:bg-gray-100 transition-colors"
|
|
133
|
+
>
|
|
134
|
+
<X className="w-5 h-5" />
|
|
135
|
+
</button>
|
|
136
|
+
|
|
137
|
+
{isCompleted ? (
|
|
138
|
+
<div className="text-center">
|
|
139
|
+
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
140
|
+
<CheckCircle className="w-8 h-8 text-green-600" />
|
|
141
|
+
</div>
|
|
142
|
+
<h3 className="text-2xl font-bold text-gray-800 mb-3">
|
|
143
|
+
ברוכים הבאים למועדון! 🎉
|
|
144
|
+
</h3>
|
|
145
|
+
<p className="text-gray-600">
|
|
146
|
+
הצטרפת בהצלחה למועדון הלקוחות וזכית ב-10% הנחה על ההזמנה הנוכחית!
|
|
147
|
+
</p>
|
|
148
|
+
</div>
|
|
149
|
+
) : (
|
|
150
|
+
<>
|
|
151
|
+
<div className="text-center mb-6">
|
|
152
|
+
<div className="w-16 h-16 bg-gradient-to-r from-yellow-400 to-orange-400 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
153
|
+
<Crown className="w-8 h-8 text-white" />
|
|
154
|
+
</div>
|
|
155
|
+
<h3 className="text-2xl font-bold text-gray-800 mb-2">
|
|
156
|
+
הצטרפות למועדון הלקוחות
|
|
157
|
+
</h3>
|
|
158
|
+
<p className="text-gray-600">
|
|
159
|
+
מלא את הפרטים וזכה ב-10% הנחה על כל הזמנה!
|
|
160
|
+
</p>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
164
|
+
<div>
|
|
165
|
+
<label htmlFor="firstName" className="block text-sm font-medium text-gray-700 mb-1">
|
|
166
|
+
שם פרטי *
|
|
167
|
+
</label>
|
|
168
|
+
<input
|
|
169
|
+
id="firstName"
|
|
170
|
+
type="text"
|
|
171
|
+
value={formData.firstName}
|
|
172
|
+
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
|
173
|
+
placeholder="שם פרטי"
|
|
174
|
+
className={`w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent ${
|
|
175
|
+
errors.firstName ? 'border-red-500' : 'border-gray-300'
|
|
176
|
+
}`}
|
|
177
|
+
/>
|
|
178
|
+
{errors.firstName && (
|
|
179
|
+
<p className="text-red-600 text-sm mt-1">{errors.firstName}</p>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div>
|
|
184
|
+
<label htmlFor="lastName" className="block text-sm font-medium text-gray-700 mb-1">
|
|
185
|
+
שם משפחה *
|
|
186
|
+
</label>
|
|
187
|
+
<input
|
|
188
|
+
id="lastName"
|
|
189
|
+
type="text"
|
|
190
|
+
value={formData.lastName}
|
|
191
|
+
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
|
192
|
+
placeholder="שם משפחה"
|
|
193
|
+
className={`w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent ${
|
|
194
|
+
errors.lastName ? 'border-red-500' : 'border-gray-300'
|
|
195
|
+
}`}
|
|
196
|
+
/>
|
|
197
|
+
{errors.lastName && (
|
|
198
|
+
<p className="text-red-600 text-sm mt-1">{errors.lastName}</p>
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div>
|
|
203
|
+
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-1">
|
|
204
|
+
טלפון *
|
|
205
|
+
</label>
|
|
206
|
+
<input
|
|
207
|
+
id="phone"
|
|
208
|
+
type="tel"
|
|
209
|
+
value={formData.phone}
|
|
210
|
+
onChange={(e) => handleInputChange('phone', e.target.value)}
|
|
211
|
+
placeholder="052-1234567"
|
|
212
|
+
className={`w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent ${
|
|
213
|
+
errors.phone ? 'border-red-500' : 'border-gray-300'
|
|
214
|
+
}`}
|
|
215
|
+
/>
|
|
216
|
+
{errors.phone && (
|
|
217
|
+
<p className="text-red-600 text-sm mt-1">{errors.phone}</p>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div>
|
|
222
|
+
<label htmlFor="address" className="block text-sm font-medium text-gray-700 mb-1">
|
|
223
|
+
כתובת מלאה *
|
|
224
|
+
</label>
|
|
225
|
+
<textarea
|
|
226
|
+
id="address"
|
|
227
|
+
value={formData.address}
|
|
228
|
+
onChange={(e) => handleInputChange('address', e.target.value)}
|
|
229
|
+
placeholder="רחוב, מספר בית, עיר"
|
|
230
|
+
rows={2}
|
|
231
|
+
className={`w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent ${
|
|
232
|
+
errors.address ? 'border-red-500' : 'border-gray-300'
|
|
233
|
+
}`}
|
|
234
|
+
/>
|
|
235
|
+
{errors.address && (
|
|
236
|
+
<p className="text-red-600 text-sm mt-1">{errors.address}</p>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div className="flex items-start space-x-2 rtl:space-x-reverse">
|
|
241
|
+
<input
|
|
242
|
+
id="marketingConsent"
|
|
243
|
+
type="checkbox"
|
|
244
|
+
checked={formData.marketingConsent}
|
|
245
|
+
onChange={(e) => handleInputChange('marketingConsent', e.target.checked)}
|
|
246
|
+
className="mt-1 w-4 h-4 text-primary focus:ring-primary border-gray-300 rounded"
|
|
247
|
+
/>
|
|
248
|
+
<label htmlFor="marketingConsent" className="text-sm leading-5 text-gray-700">
|
|
249
|
+
אני מסכים/מה לקבל דיוור שיווקי ועדכונים על מבצעים מיוחדים. *
|
|
250
|
+
<span className="text-gray-500 block mt-1">
|
|
251
|
+
(אנחנו מבטיחים לא לחפור ולשלוח רק תוכן איכותי ורלוונטי)
|
|
252
|
+
</span>
|
|
253
|
+
</label>
|
|
254
|
+
</div>
|
|
255
|
+
{errors.marketingConsent && (
|
|
256
|
+
<p className="text-red-600 text-sm">{errors.marketingConsent}</p>
|
|
257
|
+
)}
|
|
258
|
+
|
|
259
|
+
<button
|
|
260
|
+
type="submit"
|
|
261
|
+
disabled={isSubmitting}
|
|
262
|
+
className="w-full rounded-full py-3 text-white bg-gradient-to-r from-yellow-500 to-orange-500 border-0 hover:shadow-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
263
|
+
>
|
|
264
|
+
{isSubmitting ? (
|
|
265
|
+
<>
|
|
266
|
+
<Loader2 className="w-4 h-4 inline ml-2 animate-spin" />
|
|
267
|
+
מצטרף למועדון...
|
|
268
|
+
</>
|
|
269
|
+
) : (
|
|
270
|
+
<>
|
|
271
|
+
<Crown className="w-4 h-4 inline ml-2" />
|
|
272
|
+
הצטרף למועדון וקבל 10% הנחה!
|
|
273
|
+
</>
|
|
274
|
+
)}
|
|
275
|
+
</button>
|
|
276
|
+
|
|
277
|
+
<p className="text-xs text-gray-500 text-center">
|
|
278
|
+
* כל השדות הם שדות חובה להצטרפות למועדון
|
|
279
|
+
</p>
|
|
280
|
+
</form>
|
|
281
|
+
</>
|
|
282
|
+
)}
|
|
283
|
+
</motion.div>
|
|
284
|
+
</div>
|
|
285
|
+
</AnimatePresence>
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export default ClubMembershipModal;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import { X, Crown, Star, Gift, Zap, LogIn } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
const ClubPromoModal = ({ isOpen, onClose, onLoginRedirect }) => {
|
|
6
|
+
const handleJoinClick = () => {
|
|
7
|
+
onLoginRedirect();
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
if (!isOpen) return null;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<AnimatePresence>
|
|
14
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
15
|
+
<motion.div
|
|
16
|
+
initial={{ opacity: 0 }}
|
|
17
|
+
animate={{ opacity: 1 }}
|
|
18
|
+
exit={{ opacity: 0 }}
|
|
19
|
+
className="absolute inset-0 bg-black bg-opacity-50 backdrop-blur-sm"
|
|
20
|
+
onClick={onClose}
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<motion.div
|
|
24
|
+
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
25
|
+
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
26
|
+
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
27
|
+
className="relative glass-card p-8 max-w-lg w-full mx-4"
|
|
28
|
+
>
|
|
29
|
+
<button
|
|
30
|
+
onClick={onClose}
|
|
31
|
+
className="absolute top-4 left-4 p-2 rounded-full hover:bg-gray-100 transition-colors"
|
|
32
|
+
>
|
|
33
|
+
<X className="w-5 h-5" />
|
|
34
|
+
</button>
|
|
35
|
+
|
|
36
|
+
<div className="text-center">
|
|
37
|
+
<div className="w-20 h-20 bg-gradient-to-r from-yellow-400 to-orange-400 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
38
|
+
<Crown className="w-10 h-10 text-white" />
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<h2 className="text-2xl font-bold text-gray-800 mb-4">
|
|
42
|
+
הצטרפו למועדון הלקוחות של טרופיקל!
|
|
43
|
+
</h2>
|
|
44
|
+
|
|
45
|
+
<p className="text-gray-600 mb-6">
|
|
46
|
+
מועדון VIP עם יתרונות בלעדיים שיהפכו כל הזמנה לחגיגה
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<div className="space-y-4 mb-8 text-right">
|
|
50
|
+
<div className="flex items-start gap-3">
|
|
51
|
+
<div className="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
|
52
|
+
<Gift className="w-4 h-4 text-green-600" />
|
|
53
|
+
</div>
|
|
54
|
+
<div>
|
|
55
|
+
<h3 className="font-semibold text-gray-800">10% הנחה על כל הזמנה</h3>
|
|
56
|
+
<p className="text-sm text-gray-600">הנחה קבועה על כל המוצרים בחנות</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div className="flex items-start gap-3">
|
|
61
|
+
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
|
62
|
+
<Zap className="w-4 h-4 text-blue-600" />
|
|
63
|
+
</div>
|
|
64
|
+
<div>
|
|
65
|
+
<h3 className="font-semibold text-gray-800">גישה מהירה להזמנות</h3>
|
|
66
|
+
<p className="text-sm text-gray-600">פרטים שמורים לתהליך הזמנה מהיר</p>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div className="flex items-start gap-3">
|
|
71
|
+
<div className="w-8 h-8 bg-purple-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
|
72
|
+
<Star className="w-4 h-4 text-purple-600" />
|
|
73
|
+
</div>
|
|
74
|
+
<div>
|
|
75
|
+
<h3 className="font-semibold text-gray-800">מבצעים בלעדיים</h3>
|
|
76
|
+
<p className="text-sm text-gray-600">עדכונים ראשונים על מוצרים חדשים ומבצעים</p>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className="space-y-3">
|
|
82
|
+
<button
|
|
83
|
+
onClick={handleJoinClick}
|
|
84
|
+
className="w-full rounded-full py-3 text-white bg-gradient-to-r from-yellow-500 to-orange-500 border-0 text-lg font-semibold hover:shadow-lg transition-all duration-200"
|
|
85
|
+
>
|
|
86
|
+
<LogIn className="w-5 h-5 inline ml-2" />
|
|
87
|
+
התחבר והצטרף למועדון!
|
|
88
|
+
</button>
|
|
89
|
+
|
|
90
|
+
<button
|
|
91
|
+
onClick={onClose}
|
|
92
|
+
className="w-full rounded-full py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-50 transition-colors"
|
|
93
|
+
>
|
|
94
|
+
אולי מאוחר יותר
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<p className="text-xs text-gray-500 mt-4">
|
|
99
|
+
ההצטרפות חינם לחלוטין ואפשר לבטל בכל עת
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
</motion.div>
|
|
103
|
+
</div>
|
|
104
|
+
</AnimatePresence>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default ClubPromoModal;
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,8 @@ export { default as FullscreenCarousel } from './components/FullscreenCarousel'
|
|
|
25
25
|
|
|
26
26
|
// Modals
|
|
27
27
|
export { default as ItemDetailsModal } from './components/modals/ItemDetailsModal'
|
|
28
|
+
export { default as ClubMembershipModal } from './components/clubMembership/ClubMembershipModal'
|
|
29
|
+
export { default as ClubPromoModal } from './components/clubMembership/ClubPromoModal'
|
|
28
30
|
|
|
29
31
|
// Context
|
|
30
32
|
export { ItemModalProvider, useItemModal } from './context/ItemModalContext';
|