@codesinger0/shared-components 1.1.56 → 1.1.58
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.
- package/dist/components/{QAAccordion.jsx → QAAccordion 2.jsx } +11 -9
- package/package.json +1 -1
- package/dist/components/AdvantagesList.jsx +0 -89
- package/dist/components/ArticlesList.jsx +0 -237
- package/dist/components/DualTextCard.jsx +0 -73
- package/dist/components/FloatingWhatsAppButton.jsx +0 -180
- package/dist/components/IconGrid.jsx +0 -144
- package/dist/components/IntroSection.jsx +0 -74
- package/dist/components/Menu.jsx +0 -268
- package/dist/components/SmallItemsGrid.jsx +0 -308
- package/dist/components/TextListCards.jsx +0 -107
- package/dist/components/UnderConstruction.jsx +0 -76
- package/dist/components/VideoCard.jsx +0 -88
- package/dist/context/CartContext.jsx +0 -165
- package/dist/context/ItemModalContext.jsx +0 -40
- package/dist/hooks/useScrollLock.js +0 -52
- package/dist/integrations/emailService.js +0 -167
- package/dist/styles/shared-components.css +0 -29
- package/dist/utils/ScrollManager.jsx +0 -85
- package/dist/utils/ScrollToTop.jsx +0 -14
|
@@ -5,7 +5,8 @@ const QAAccordion = ({
|
|
|
5
5
|
title = "שאלות נפוצות",
|
|
6
6
|
subtitle = "מענה לשאלות הנפוצות ביותר",
|
|
7
7
|
qaItems = [],
|
|
8
|
-
|
|
8
|
+
showBottomCTA = true,
|
|
9
|
+
onClickCta = () => { },
|
|
9
10
|
className = ""
|
|
10
11
|
}) => {
|
|
11
12
|
const [openIndex, setOpenIndex] = useState(null);
|
|
@@ -183,14 +184,15 @@ const QAAccordion = ({
|
|
|
183
184
|
</div>
|
|
184
185
|
|
|
185
186
|
{/* Optional CTA */}
|
|
186
|
-
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
{showBottomCTA && (
|
|
188
|
+
<div className="text-center mt-12">
|
|
189
|
+
<p className="content-text mb-4">
|
|
190
|
+
לא מצאתם את התשובה שחיפשתם?
|
|
191
|
+
</p>
|
|
192
|
+
<button className="btn-primary" onClick={onClickCta}>
|
|
193
|
+
צרו קשר
|
|
194
|
+
</button>
|
|
195
|
+
</div>)}
|
|
194
196
|
</div>
|
|
195
197
|
</section>
|
|
196
198
|
);
|
package/package.json
CHANGED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
const AdvantagesList = ({
|
|
2
|
-
points = [],
|
|
3
|
-
title,
|
|
4
|
-
subtitle,
|
|
5
|
-
className = '',
|
|
6
|
-
classNames = {},
|
|
7
|
-
...props
|
|
8
|
-
}) => {
|
|
9
|
-
const {
|
|
10
|
-
title: titleClass = 'main-title',
|
|
11
|
-
subtitle: subtitleClass = 'main-subtitle',
|
|
12
|
-
content: contentClass = 'content-text'
|
|
13
|
-
} = classNames;
|
|
14
|
-
|
|
15
|
-
if (!points || points.length === 0) {
|
|
16
|
-
return (
|
|
17
|
-
<section className={`py-16 px-4 ${className}`} {...props} dir="rtl">
|
|
18
|
-
<div className="max-w-6xl mx-auto text-center">
|
|
19
|
-
{title && <h2 className={titleClass + " mb-4"}>{title}</h2>}
|
|
20
|
-
{subtitle && <h3 className={subtitleClass + " mb-8 whitespace-pre-line"}>{subtitle}</h3>}
|
|
21
|
-
<p className={contentClass}>אין נקודות להצגה</p>
|
|
22
|
-
</div>
|
|
23
|
-
</section>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<section className={`py-16 px-4 bg-main ${className}`} {...props} dir="rtl">
|
|
29
|
-
<div className="max-w-6xl mx-auto">
|
|
30
|
-
{/* Header */}
|
|
31
|
-
{(title || subtitle) && (
|
|
32
|
-
<div className="text-center mb-12">
|
|
33
|
-
{title && <h2 className={titleClass + " mb-4"}>{title}</h2>}
|
|
34
|
-
{subtitle && <h3 className={subtitleClass}>{subtitle}</h3>}
|
|
35
|
-
</div>
|
|
36
|
-
)}
|
|
37
|
-
|
|
38
|
-
{/* Points Grid */}
|
|
39
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
40
|
-
{points.map((point, index) => (
|
|
41
|
-
<div key={index} className="glass-card p-6 hover:scale-105 transition-transform duration-200">
|
|
42
|
-
{/* Desktop Layout - Horizontal */}
|
|
43
|
-
<div className="hidden lg:flex items-start gap-4">
|
|
44
|
-
<div className="flex-shrink-0 w-12 h-12 rounded-full flex items-center justify-center text-primary">
|
|
45
|
-
<point.icon size={24} />
|
|
46
|
-
</div>
|
|
47
|
-
<div className="flex-1">
|
|
48
|
-
<span className='content-text leading-relaxed font-semibold'>{point.bold}</span>
|
|
49
|
-
<p className="content-text leading-relaxed" style={{ whiteSpace: 'pre-line' }}>{point.text}</p>
|
|
50
|
-
{point.subs && point.subs.length > 0 && (
|
|
51
|
-
<ul className="mt-3 space-y-2 list-disc list-inside">
|
|
52
|
-
{point.subs.map((sub, subIndex) => (
|
|
53
|
-
<li key={subIndex} className="subtitle leading-relaxed text-sm">
|
|
54
|
-
{sub}
|
|
55
|
-
</li>
|
|
56
|
-
))}
|
|
57
|
-
</ul>
|
|
58
|
-
)}
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
{/* Mobile Layout - Vertical */}
|
|
63
|
-
<div className="lg:hidden text-center space-y-2">
|
|
64
|
-
<div className="flex justify-center">
|
|
65
|
-
<div className="w-14 h-10 rounded-full flex items-center justify-center text-primary">
|
|
66
|
-
<point.icon size={26} />
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
<span className='content-text leading-relaxed font-semibold'>{point.bold}</span>
|
|
70
|
-
<p className="content-text leading-relaxed" style={{ whiteSpace: 'pre-line' }}>{point.text}</p>
|
|
71
|
-
{point.subs && point.subs.length > 0 && (
|
|
72
|
-
<ul className="mt-3 space-y-2 list-disc list-inside text-right">
|
|
73
|
-
{point.subs.map((sub, subIndex) => (
|
|
74
|
-
<li key={subIndex} className="subtitle leading-relaxed text-sm">
|
|
75
|
-
{sub}
|
|
76
|
-
</li>
|
|
77
|
-
))}
|
|
78
|
-
</ul>
|
|
79
|
-
)}
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
))}
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
</section>
|
|
86
|
-
);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export default AdvantagesList;
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
-
import { BookOpen, ArrowDown, X } from 'lucide-react';
|
|
4
|
-
import useScrollLock from '../hooks/useScrollLock';
|
|
5
|
-
|
|
6
|
-
const ArticlesList = ({
|
|
7
|
-
title,
|
|
8
|
-
subtitle,
|
|
9
|
-
articles = [],
|
|
10
|
-
className = '',
|
|
11
|
-
...props
|
|
12
|
-
}) => {
|
|
13
|
-
const [selectedArticle, setSelectedArticle] = useState(null);
|
|
14
|
-
|
|
15
|
-
useScrollLock(selectedArticle !== null);
|
|
16
|
-
|
|
17
|
-
if (!articles || articles.length === 0) {
|
|
18
|
-
return (
|
|
19
|
-
<section className={`py-20 ${className}`} {...props}>
|
|
20
|
-
<div className="max-w-7xl mx-auto px-6 text-center" dir="rtl">
|
|
21
|
-
{title && <h2 className="title mb-4">{title}</h2>}
|
|
22
|
-
{subtitle && <p className="subtitle">{subtitle}</p>}
|
|
23
|
-
<p className="content-text mt-8">אין מאמרים להצגה</p>
|
|
24
|
-
</div>
|
|
25
|
-
</section>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Function to get first 3 lines of text
|
|
30
|
-
const getExcerpt = (text) => {
|
|
31
|
-
const lines = text?.trim().split('\n').filter(line => line.trim());
|
|
32
|
-
return lines?.slice(0, 3).join('\n') + (lines?.length > 3 ? '...' : '');
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<>
|
|
37
|
-
<section className={`py-20 bg-white ${className}`} {...props} id="articles">
|
|
38
|
-
<div className="max-w-7xl mx-auto px-6">
|
|
39
|
-
{/* Header */}
|
|
40
|
-
{(title || subtitle) && (
|
|
41
|
-
<motion.div
|
|
42
|
-
initial={{ opacity: 0, y: 20 }}
|
|
43
|
-
whileInView={{ opacity: 1, y: 0 }}
|
|
44
|
-
viewport={{ once: true }}
|
|
45
|
-
className="text-center mb-16"
|
|
46
|
-
dir="rtl"
|
|
47
|
-
>
|
|
48
|
-
{title && (
|
|
49
|
-
<h2 className="title mb-6">
|
|
50
|
-
{title}
|
|
51
|
-
</h2>
|
|
52
|
-
)}
|
|
53
|
-
|
|
54
|
-
<div className="w-20 h-1 bg-gradient-to-r from-primary to-primary-bright mx-auto mb-6"></div>
|
|
55
|
-
|
|
56
|
-
{subtitle && (
|
|
57
|
-
<p className="subtitle max-w-3xl mx-auto">
|
|
58
|
-
{subtitle}
|
|
59
|
-
</p>
|
|
60
|
-
)}
|
|
61
|
-
</motion.div>
|
|
62
|
-
)}
|
|
63
|
-
|
|
64
|
-
{/* Articles Grid */}
|
|
65
|
-
<div className="grid lg:grid-cols-2 gap-8" dir="rtl">
|
|
66
|
-
{articles.map((article, index) => {
|
|
67
|
-
const isEven = index % 2 === 0;
|
|
68
|
-
const cardBg = isEven ? 'bg-gradient-to-br from-white to-green-50' : 'bg-gradient-to-br from-white to-sky-50';
|
|
69
|
-
const iconGradient = isEven ? 'from-green-500 to-green-600' : 'from-sky-400 to-sky-500';
|
|
70
|
-
const textHover = isEven ? 'group-hover:text-green-700' : 'group-hover:text-sky-700';
|
|
71
|
-
const textColor = isEven ? 'text-green-600' : 'text-sky-600';
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<motion.div
|
|
75
|
-
key={article.id || index}
|
|
76
|
-
initial={{ opacity: 0, y: 20 }}
|
|
77
|
-
whileInView={{ opacity: 1, y: 0 }}
|
|
78
|
-
viewport={{ once: true }}
|
|
79
|
-
transition={{ delay: index * 0.1 }}
|
|
80
|
-
>
|
|
81
|
-
<div
|
|
82
|
-
className={`h-full cursor-pointer border-none shadow-lg hover:shadow-xl transition-all duration-300 ${cardBg} group rounded-xl`}
|
|
83
|
-
onClick={() => setSelectedArticle(article)}
|
|
84
|
-
>
|
|
85
|
-
<div className="p-6 pb-4">
|
|
86
|
-
<div className="flex items-start gap-4">
|
|
87
|
-
<div className={`w-12 h-12 bg-gradient-to-br ${iconGradient} rounded-full flex items-center justify-center flex-shrink-0`}>
|
|
88
|
-
{article.icon ? (
|
|
89
|
-
<article.icon className="w-6 h-6 text-white" />
|
|
90
|
-
) : (
|
|
91
|
-
<BookOpen className="w-6 h-6 text-white" />
|
|
92
|
-
)}
|
|
93
|
-
</div>
|
|
94
|
-
<div className="flex-1">
|
|
95
|
-
<h3 className={`text-xl font-semibold text-green-900 leading-tight ${textHover} transition-colors`} dir="rtl">
|
|
96
|
-
{article.title}
|
|
97
|
-
</h3>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
<div className="px-6 pb-6">
|
|
102
|
-
<p className="text-gray-600 leading-relaxed whitespace-pre-line" dir="rtl">
|
|
103
|
-
{article.excerpt}
|
|
104
|
-
</p>
|
|
105
|
-
<div className="mt-6">
|
|
106
|
-
<span className={`inline-flex items-center ${textColor} font-medium ${textHover} transition-colors`}>
|
|
107
|
-
קרא עוד
|
|
108
|
-
<ArrowDown className="w-4 h-4 mr-2 rotate-180 group-hover:transform group-hover:-translate-y-1 transition-transform" />
|
|
109
|
-
</span>
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
</motion.div>
|
|
114
|
-
);
|
|
115
|
-
})}
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
</section>
|
|
119
|
-
|
|
120
|
-
{/* Article Modal */}
|
|
121
|
-
<AnimatePresence>
|
|
122
|
-
{selectedArticle && (
|
|
123
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center supports-[height:100dvh]:h-[100dvh]">
|
|
124
|
-
{/* Backdrop */}
|
|
125
|
-
<motion.div
|
|
126
|
-
initial={{ opacity: 0 }}
|
|
127
|
-
animate={{ opacity: 1 }}
|
|
128
|
-
exit={{ opacity: 0 }}
|
|
129
|
-
transition={{ duration: 0.2 }}
|
|
130
|
-
className="absolute inset-0 bg-black bg-opacity-50 backdrop-blur-sm"
|
|
131
|
-
onClick={() => setSelectedArticle(null)}
|
|
132
|
-
/>
|
|
133
|
-
|
|
134
|
-
{/* Modal Content */}
|
|
135
|
-
<motion.div
|
|
136
|
-
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
137
|
-
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
138
|
-
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
|
139
|
-
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
|
140
|
-
className="relative w-full max-w-4xl mx-4 max-h-[90vh] bg-white rounded-xl shadow-2xl overflow-hidden"
|
|
141
|
-
onClick={(e) => e.stopPropagation()}
|
|
142
|
-
>
|
|
143
|
-
{/* Close Button */}
|
|
144
|
-
<button
|
|
145
|
-
onClick={() => setSelectedArticle(null)}
|
|
146
|
-
className="absolute top-4 left-4 z-10 bg-white hover:bg-gray-100 text-gray-700 p-2 rounded-full shadow-lg transition-colors duration-200"
|
|
147
|
-
aria-label="סגור"
|
|
148
|
-
>
|
|
149
|
-
<X size={24} />
|
|
150
|
-
</button>
|
|
151
|
-
|
|
152
|
-
{/* Scrollable Content */}
|
|
153
|
-
<div className="overflow-y-auto max-h-[90vh] p-8 md:p-12" dir="rtl">
|
|
154
|
-
{/* Article Header */}
|
|
155
|
-
<div className="mb-8">
|
|
156
|
-
<div className="flex items-center gap-4 mb-6">
|
|
157
|
-
<div className="w-16 h-16 bg-gradient-to-br from-green-500 to-green-600 rounded-full flex items-center justify-center flex-shrink-0">
|
|
158
|
-
{selectedArticle.icon ? (
|
|
159
|
-
<selectedArticle.icon className="w-8 h-8 text-white" />
|
|
160
|
-
) : (
|
|
161
|
-
<BookOpen className="w-8 h-8 text-white" />
|
|
162
|
-
)}
|
|
163
|
-
</div>
|
|
164
|
-
<h2 className="text-3xl md:text-4xl font-bold text-green-900">
|
|
165
|
-
{selectedArticle.title}
|
|
166
|
-
</h2>
|
|
167
|
-
</div>
|
|
168
|
-
<div className="w-20 h-1 bg-gradient-to-r from-green-500 to-sky-400"></div>
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
{/* Article Content */}
|
|
172
|
-
<div className="prose prose-lg max-w-none">
|
|
173
|
-
<ArticleFormatter text={selectedArticle.content} />
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
</motion.div>
|
|
177
|
-
</div>
|
|
178
|
-
)}
|
|
179
|
-
</AnimatePresence>
|
|
180
|
-
|
|
181
|
-
{/* Custom Styles */}
|
|
182
|
-
<style jsx>{`
|
|
183
|
-
.from-primary {
|
|
184
|
-
--tw-gradient-from: var(--primary);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.to-primary-bright {
|
|
188
|
-
--tw-gradient-to: var(--primary-bright);
|
|
189
|
-
}
|
|
190
|
-
`}</style>
|
|
191
|
-
</>
|
|
192
|
-
);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const ArticleFormatter = ({ text }) => {
|
|
196
|
-
// Split the text into logical sections. A section is typically a heading followed by its text.
|
|
197
|
-
// We assume sections are separated by two or more newlines.
|
|
198
|
-
const sections = text.trim().split(/\n\s*\n+/);
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<>
|
|
202
|
-
{sections.map((section, index) => {
|
|
203
|
-
// Find the first newline to separate a potential heading from its paragraph
|
|
204
|
-
const firstNewlineIndex = section.indexOf('\n');
|
|
205
|
-
|
|
206
|
-
let heading = '';
|
|
207
|
-
let paragraph = '';
|
|
208
|
-
|
|
209
|
-
// Heuristic: If the first line is short and there's more content, it's a heading.
|
|
210
|
-
if (firstNewlineIndex > 0 && firstNewlineIndex < 80) {
|
|
211
|
-
heading = section.substring(0, firstNewlineIndex).trim();
|
|
212
|
-
paragraph = section.substring(firstNewlineIndex + 1).trim();
|
|
213
|
-
} else {
|
|
214
|
-
// Otherwise, treat the whole block as a single paragraph
|
|
215
|
-
paragraph = section.trim();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return (
|
|
219
|
-
<div key={index} className="mb-6">
|
|
220
|
-
{heading && (
|
|
221
|
-
<h2 className="text-2xl font-semibold text-green-800 mt-10 mb-4 pb-2 border-b border-sky-200">
|
|
222
|
-
{heading}
|
|
223
|
-
</h2>
|
|
224
|
-
)}
|
|
225
|
-
{paragraph && (
|
|
226
|
-
<p className="text-gray-700 leading-relaxed text-lg whitespace-pre-line">
|
|
227
|
-
{paragraph}
|
|
228
|
-
</p>
|
|
229
|
-
)}
|
|
230
|
-
</div>
|
|
231
|
-
);
|
|
232
|
-
})}
|
|
233
|
-
</>
|
|
234
|
-
);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
export default ArticlesList;
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { motion } from 'framer-motion';
|
|
3
|
-
|
|
4
|
-
const DualTextCard = ({
|
|
5
|
-
// First card props
|
|
6
|
-
title1,
|
|
7
|
-
subtitle1,
|
|
8
|
-
text1,
|
|
9
|
-
|
|
10
|
-
// Second card props
|
|
11
|
-
title2,
|
|
12
|
-
subtitle2,
|
|
13
|
-
text2,
|
|
14
|
-
|
|
15
|
-
// Layout options
|
|
16
|
-
reverse = false,
|
|
17
|
-
|
|
18
|
-
// Custom class names
|
|
19
|
-
classNames = {},
|
|
20
|
-
className = '',
|
|
21
|
-
|
|
22
|
-
...props
|
|
23
|
-
}) => {
|
|
24
|
-
const {
|
|
25
|
-
card: cardClass = 'glass-card',
|
|
26
|
-
title: titleClass = 'title',
|
|
27
|
-
subtitle: subtitleClass = 'subtitle',
|
|
28
|
-
content: contentClass = 'content-text',
|
|
29
|
-
} = classNames;
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<section className={`relative w-full overflow-visible py-16 px-4 ${className}`} {...props}>
|
|
33
|
-
{/* Background Section with Text Content */}
|
|
34
|
-
<div className={cardClass + " relative"} style={{ minHeight: '48vh', overflow: 'visible' }}>
|
|
35
|
-
<div className="max-w-7xl mx-auto px-6 py-16">
|
|
36
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-start">
|
|
37
|
-
|
|
38
|
-
{/* First Text Block */}
|
|
39
|
-
<motion.div
|
|
40
|
-
initial={{ opacity: 0, x: reverse ? 50 : -50 }}
|
|
41
|
-
whileInView={{ opacity: 1, x: 0 }}
|
|
42
|
-
viewport={{ once: true }}
|
|
43
|
-
transition={{ delay: 0.2 }}
|
|
44
|
-
className={`text-center lg:text-right space-y-6 ${reverse ? 'lg:order-2' : 'lg:order-1'}`}
|
|
45
|
-
dir="rtl"
|
|
46
|
-
>
|
|
47
|
-
<h2 className={titleClass}>{title1}</h2>
|
|
48
|
-
<h3 className={subtitleClass}>{subtitle1}</h3>
|
|
49
|
-
<p className={contentClass + " max-w-md mx-auto lg:mx-0"} style={{ whiteSpace: 'pre-line' }}>{text1}</p>
|
|
50
|
-
</motion.div>
|
|
51
|
-
|
|
52
|
-
{/* Second Text Block */}
|
|
53
|
-
<motion.div
|
|
54
|
-
initial={{ opacity: 0, x: reverse ? -50 : 50 }}
|
|
55
|
-
whileInView={{ opacity: 1, x: 0 }}
|
|
56
|
-
viewport={{ once: true }}
|
|
57
|
-
transition={{ delay: 0.4 }}
|
|
58
|
-
className={`text-center lg:text-right space-y-6 ${reverse ? 'lg:order-1' : 'lg:order-2'}`}
|
|
59
|
-
dir="rtl"
|
|
60
|
-
>
|
|
61
|
-
<h2 className={titleClass}>{title2}</h2>
|
|
62
|
-
<h3 className={subtitleClass}>{subtitle2}</h3>
|
|
63
|
-
<p className={contentClass + " max-w-md mx-auto lg:mx-0"} style={{ whiteSpace: 'pre-line' }}>{text2}</p>
|
|
64
|
-
</motion.div>
|
|
65
|
-
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
</section>
|
|
70
|
-
);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export default DualTextCard;
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { motion } from 'framer-motion';
|
|
3
|
-
import { MessageCircle } from 'lucide-react';
|
|
4
|
-
|
|
5
|
-
const FloatingWhatsAppButton = ({
|
|
6
|
-
phoneNumber,
|
|
7
|
-
message = 'שלום, אשמח לקבל מידע נוסף',
|
|
8
|
-
position = 'bottom-left', // 'bottom-left' or 'bottom-right'
|
|
9
|
-
bottomOffset = '1.5rem', // distance from bottom
|
|
10
|
-
sideOffset = '1.5rem', // distance from left/right edge
|
|
11
|
-
size = 'large', // 'small', 'medium', 'large'
|
|
12
|
-
className = '',
|
|
13
|
-
onBeforeOpen = null, // callback before opening WhatsApp
|
|
14
|
-
...props
|
|
15
|
-
}) => {
|
|
16
|
-
// Size configurations
|
|
17
|
-
const sizeClasses = {
|
|
18
|
-
small: 'w-12 h-12',
|
|
19
|
-
medium: 'w-14 h-14',
|
|
20
|
-
large: 'w-16 h-16'
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const iconSizes = {
|
|
24
|
-
small: 20,
|
|
25
|
-
medium: 24,
|
|
26
|
-
large: 28
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// Position styles
|
|
30
|
-
const positionStyles = {
|
|
31
|
-
'bottom-left': {
|
|
32
|
-
bottom: bottomOffset,
|
|
33
|
-
left: sideOffset,
|
|
34
|
-
right: 'auto'
|
|
35
|
-
},
|
|
36
|
-
'bottom-right': {
|
|
37
|
-
bottom: bottomOffset,
|
|
38
|
-
right: sideOffset,
|
|
39
|
-
left: 'auto'
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const openWhatsApp = () => {
|
|
44
|
-
// Call the callback if provided (e.g., for analytics)
|
|
45
|
-
if (onBeforeOpen) {
|
|
46
|
-
onBeforeOpen();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Format phone number (remove any non-digits, ensure international format)
|
|
50
|
-
const cleanPhone = phoneNumber.replace(/\D/g, '');
|
|
51
|
-
const encodedMessage = encodeURIComponent(message);
|
|
52
|
-
|
|
53
|
-
// Open WhatsApp in new tab
|
|
54
|
-
window.open(
|
|
55
|
-
`https://wa.me/${cleanPhone}?text=${encodedMessage}`,
|
|
56
|
-
'_blank'
|
|
57
|
-
);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<motion.button
|
|
62
|
-
onClick={openWhatsApp}
|
|
63
|
-
className={`fixed z-50 bg-green-500 hover:bg-green-600 text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300 flex items-center justify-center ${sizeClasses[size]} ${className}`}
|
|
64
|
-
style={positionStyles[position]}
|
|
65
|
-
initial={{ scale: 0, opacity: 0 }}
|
|
66
|
-
animate={{ scale: 1, opacity: 1 }}
|
|
67
|
-
whileHover={{ scale: 1.1 }}
|
|
68
|
-
whileTap={{ scale: 0.9 }}
|
|
69
|
-
transition={{
|
|
70
|
-
type: "spring",
|
|
71
|
-
stiffness: 300,
|
|
72
|
-
damping: 25
|
|
73
|
-
}}
|
|
74
|
-
aria-label="פתח WhatsApp"
|
|
75
|
-
{...props}
|
|
76
|
-
>
|
|
77
|
-
<MessageCircle size={iconSizes[size]} />
|
|
78
|
-
</motion.button>
|
|
79
|
-
);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// Demo Component
|
|
83
|
-
const Demo = () => {
|
|
84
|
-
const handleBeforeOpen = () => {
|
|
85
|
-
console.log('WhatsApp button clicked - you can add analytics here');
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 p-8" dir="rtl">
|
|
90
|
-
<div className="max-w-4xl mx-auto">
|
|
91
|
-
<h1 className="text-4xl font-bold text-gray-800 mb-6 text-center">
|
|
92
|
-
WhatsApp Floating Button Demo
|
|
93
|
-
</h1>
|
|
94
|
-
|
|
95
|
-
<div className="bg-white rounded-xl shadow-lg p-8 mb-8">
|
|
96
|
-
<h2 className="text-2xl font-semibold mb-4">תכונות:</h2>
|
|
97
|
-
<ul className="space-y-2 text-gray-700">
|
|
98
|
-
<li>✅ מיקום גמיש (שמאל/ימין למטה)</li>
|
|
99
|
-
<li>✅ שליטה מלאה על המרחקים מהקצוות</li>
|
|
100
|
-
<li>✅ 3 גדלים: small, medium, large</li>
|
|
101
|
-
<li>✅ הודעה מותאמת אישית</li>
|
|
102
|
-
<li>✅ Callback לפני פתיחה (לאנליטיקס)</li>
|
|
103
|
-
<li>✅ אנימציות חלקות עם Framer Motion</li>
|
|
104
|
-
</ul>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
<div className="bg-white rounded-xl shadow-lg p-8">
|
|
108
|
-
<h2 className="text-2xl font-semibold mb-4">דוגמאות שימוש:</h2>
|
|
109
|
-
|
|
110
|
-
<div className="space-y-4 text-sm bg-gray-50 p-4 rounded-lg font-mono text-left" dir="ltr">
|
|
111
|
-
<div className="bg-white p-3 rounded border">
|
|
112
|
-
<div className="text-gray-500 mb-2">// Bottom Left (default)</div>
|
|
113
|
-
<code className="text-blue-600">
|
|
114
|
-
{`<FloatingWhatsAppButton
|
|
115
|
-
phoneNumber="972542397230"
|
|
116
|
-
message="שלום, אני מעוניין לקבוע פגישה"
|
|
117
|
-
position="bottom-left"
|
|
118
|
-
/>`}
|
|
119
|
-
</code>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
<div className="bg-white p-3 rounded border">
|
|
123
|
-
<div className="text-gray-500 mb-2">// Bottom Right with custom offsets</div>
|
|
124
|
-
<code className="text-blue-600">
|
|
125
|
-
{`<FloatingWhatsAppButton
|
|
126
|
-
phoneNumber="972542397230"
|
|
127
|
-
position="bottom-right"
|
|
128
|
-
bottomOffset="2rem"
|
|
129
|
-
sideOffset="2rem"
|
|
130
|
-
size="medium"
|
|
131
|
-
/>`}
|
|
132
|
-
</code>
|
|
133
|
-
</div>
|
|
134
|
-
|
|
135
|
-
<div className="bg-white p-3 rounded border">
|
|
136
|
-
<div className="text-gray-500 mb-2">// With analytics callback</div>
|
|
137
|
-
<code className="text-blue-600">
|
|
138
|
-
{`<FloatingWhatsAppButton
|
|
139
|
-
phoneNumber="972542397230"
|
|
140
|
-
onBeforeOpen={() => {
|
|
141
|
-
// Send analytics event
|
|
142
|
-
sendAdsConversion({...});
|
|
143
|
-
}}
|
|
144
|
-
/>`}
|
|
145
|
-
</code>
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
|
|
150
|
-
<div className="mt-8 text-center text-gray-600">
|
|
151
|
-
<p>גלול למטה כדי לראות את הכפתור בפעולה! 👇</p>
|
|
152
|
-
<div className="mt-4 h-96"></div>
|
|
153
|
-
</div>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
{/* Demo Buttons */}
|
|
157
|
-
<FloatingWhatsAppButton
|
|
158
|
-
phoneNumber="972542397230"
|
|
159
|
-
message="שלום, אני מעוניין לקבוע פגישה לטיפול פסיכולוגי"
|
|
160
|
-
position="bottom-left"
|
|
161
|
-
bottomOffset="1.5rem"
|
|
162
|
-
sideOffset="1.5rem"
|
|
163
|
-
size="large"
|
|
164
|
-
onBeforeOpen={handleBeforeOpen}
|
|
165
|
-
/>
|
|
166
|
-
|
|
167
|
-
<FloatingWhatsAppButton
|
|
168
|
-
phoneNumber="972542397230"
|
|
169
|
-
message="היי! רציתי לשאול משהו"
|
|
170
|
-
position="bottom-right"
|
|
171
|
-
bottomOffset="1.5rem"
|
|
172
|
-
sideOffset="1.5rem"
|
|
173
|
-
size="medium"
|
|
174
|
-
onBeforeOpen={handleBeforeOpen}
|
|
175
|
-
/>
|
|
176
|
-
</div>
|
|
177
|
-
);
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
export default FloatingWhatsAppButton;
|