@codesinger0/shared-components 1.1.1 → 1.1.2
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/products/CategoryList.jsx +24 -0
- package/dist/components/products/PriceRangeSlider.jsx +162 -0
- package/dist/components/products/ProductsDisplay.jsx +40 -0
- package/dist/components/products/ProductsSidebar.jsx +46 -0
- package/dist/components/products/SubcategorySection.jsx +37 -0
- package/dist/index.js +2 -1
- package/package.json +1 -1
- package/dist/components/ContactUs.jsx +0 -332
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
const CategoryList = ({ categories, selectedCategory, onCategorySelect, className = '' }) => (
|
|
3
|
+
<div className={className}>
|
|
4
|
+
<h3 className="subtitle font-semibold mb-4">קטגוריות</h3>
|
|
5
|
+
<div className="space-y-2">
|
|
6
|
+
{categories.map((category, index) => (
|
|
7
|
+
<button
|
|
8
|
+
key={index}
|
|
9
|
+
onClick={() => onCategorySelect(category)}
|
|
10
|
+
className={`w-full text-right p-3 rounded-lg transition-all duration-200 ${selectedCategory === category
|
|
11
|
+
? 'text-white'
|
|
12
|
+
: 'hover:opacity-80'
|
|
13
|
+
}`}
|
|
14
|
+
style={selectedCategory === category ? { backgroundColor: 'var(--primary)' } : {}}
|
|
15
|
+
dir="rtl"
|
|
16
|
+
>
|
|
17
|
+
<span className="content-text">{category}</span>
|
|
18
|
+
</button>
|
|
19
|
+
))}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export default CategoryList
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const PriceRangeSlider = ({ min, max, value, onChange, className = '' }) => {
|
|
4
|
+
const [localValue, setLocalValue] = useState(value);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
setLocalValue(value);
|
|
8
|
+
}, [value]);
|
|
9
|
+
|
|
10
|
+
const handleMinChange = (e) => {
|
|
11
|
+
const newMin = Math.min(Number(e.target.value), localValue[1] - 1);
|
|
12
|
+
const newValue = [newMin, localValue[1]];
|
|
13
|
+
setLocalValue(newValue);
|
|
14
|
+
onChange(newValue);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const handleMaxChange = (e) => {
|
|
18
|
+
const newMax = Math.max(Number(e.target.value), localValue[0] + 1);
|
|
19
|
+
const newValue = [localValue[0], newMax];
|
|
20
|
+
setLocalValue(newValue);
|
|
21
|
+
onChange(newValue);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const percentage = (val) => ((val - min) / (max - min)) * 100;
|
|
25
|
+
|
|
26
|
+
const minPercent = percentage(localValue[0]);
|
|
27
|
+
const maxPercent = percentage(localValue[1]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className={`relative ${className}`}>
|
|
31
|
+
<div className="relative h-6 mb-4">
|
|
32
|
+
{/* Track */}
|
|
33
|
+
<div className="absolute top-1/2 w-full h-2 bg-gray-200 rounded transform -translate-y-1/2"></div>
|
|
34
|
+
|
|
35
|
+
{/* Active range */}
|
|
36
|
+
<div
|
|
37
|
+
className="absolute top-1/2 h-2 rounded transform -translate-y-1/2"
|
|
38
|
+
style={{
|
|
39
|
+
insetInlineStart: `${minPercent}%`,
|
|
40
|
+
insetInlineEnd: `${100 - maxPercent}%`,
|
|
41
|
+
backgroundColor: 'var(--primary)'
|
|
42
|
+
}}
|
|
43
|
+
></div>
|
|
44
|
+
|
|
45
|
+
{/* Min slider - with z-index control */}
|
|
46
|
+
<input
|
|
47
|
+
type="range"
|
|
48
|
+
min={min}
|
|
49
|
+
max={max}
|
|
50
|
+
value={localValue[0]}
|
|
51
|
+
onChange={handleMinChange}
|
|
52
|
+
className="absolute w-full h-2 bg-transparent appearance-none cursor-pointer min-slider"
|
|
53
|
+
style={{
|
|
54
|
+
top: '50%',
|
|
55
|
+
transform: 'translateY(-50%)',
|
|
56
|
+
zIndex: localValue[0] > max - 100 ? 5 : 3
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
{/* Max slider */}
|
|
61
|
+
<input
|
|
62
|
+
type="range"
|
|
63
|
+
min={min}
|
|
64
|
+
max={max}
|
|
65
|
+
value={localValue[1]}
|
|
66
|
+
onChange={handleMaxChange}
|
|
67
|
+
className="absolute w-full h-2 bg-transparent appearance-none cursor-pointer max-slider"
|
|
68
|
+
style={{
|
|
69
|
+
top: '50%',
|
|
70
|
+
transform: 'translateY(-50%)',
|
|
71
|
+
zIndex: 4
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
{/* Values display */}
|
|
77
|
+
<div className="flex justify-between items-center text-sm content-text">
|
|
78
|
+
<span>₪{localValue[0].toLocaleString()}</span>
|
|
79
|
+
<span>₪{localValue[1].toLocaleString()}</span>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<style jsx>{`
|
|
83
|
+
.min-slider::-webkit-slider-thumb {
|
|
84
|
+
appearance: none;
|
|
85
|
+
width: 20px;
|
|
86
|
+
height: 20px;
|
|
87
|
+
border-radius: 50%;
|
|
88
|
+
background: var(--primary);
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
border: 2px solid white;
|
|
91
|
+
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
|
|
92
|
+
position: relative;
|
|
93
|
+
z-index: 10;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.min-slider::-moz-range-thumb {
|
|
97
|
+
width: 20px;
|
|
98
|
+
height: 20px;
|
|
99
|
+
border-radius: 50%;
|
|
100
|
+
background: var(--primary);
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
border: 2px solid white;
|
|
103
|
+
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
|
|
104
|
+
position: relative;
|
|
105
|
+
z-index: 10;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.max-slider::-webkit-slider-thumb {
|
|
109
|
+
appearance: none;
|
|
110
|
+
width: 20px;
|
|
111
|
+
height: 20px;
|
|
112
|
+
border-radius: 50%;
|
|
113
|
+
background: var(--primary);
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
border: 2px solid white;
|
|
116
|
+
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
|
|
117
|
+
position: relative;
|
|
118
|
+
z-index: 10;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.max-slider::-moz-range-thumb {
|
|
122
|
+
width: 20px;
|
|
123
|
+
height: 20px;
|
|
124
|
+
border-radius: 50%;
|
|
125
|
+
background: var(--primary);
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
border: 2px solid white;
|
|
128
|
+
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
|
|
129
|
+
position: relative;
|
|
130
|
+
z-index: 10;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Only show the thumb for each slider */
|
|
134
|
+
.min-slider {
|
|
135
|
+
pointer-events: none;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.min-slider::-webkit-slider-thumb {
|
|
139
|
+
pointer-events: auto;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.min-slider::-moz-range-thumb {
|
|
143
|
+
pointer-events: auto;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.max-slider {
|
|
147
|
+
pointer-events: none;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.max-slider::-webkit-slider-thumb {
|
|
151
|
+
pointer-events: auto;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.max-slider::-moz-range-thumb {
|
|
155
|
+
pointer-events: auto;
|
|
156
|
+
}
|
|
157
|
+
`}</style>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export default PriceRangeSlider
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import SubCategorySection from "./SubcategorySection";
|
|
2
|
+
|
|
3
|
+
const ProductsDisplay = ({
|
|
4
|
+
productsGroupedBySubCategory,
|
|
5
|
+
selectedCategory,
|
|
6
|
+
onProductClick,
|
|
7
|
+
onAddToCart
|
|
8
|
+
}) => {
|
|
9
|
+
const subCategoryNames = Object.keys(productsGroupedBySubCategory);
|
|
10
|
+
|
|
11
|
+
if (subCategoryNames.length === 0) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="glass-card px-12 text-center">
|
|
14
|
+
<h2 className="lighterTitle mb-4">אין מוצרים זמינים</h2>
|
|
15
|
+
<p className="content-text">
|
|
16
|
+
{selectedCategory
|
|
17
|
+
? `לא נמצאו מוצרים בקטגוריה "${selectedCategory}" בטווח המחירים שנבחר`
|
|
18
|
+
: 'בחר קטגוריה כדי לראות מוצרים'
|
|
19
|
+
}
|
|
20
|
+
</p>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div>
|
|
27
|
+
{subCategoryNames.map(subCategoryName => (
|
|
28
|
+
<SubCategorySection
|
|
29
|
+
key={subCategoryName}
|
|
30
|
+
subCategoryName={subCategoryName}
|
|
31
|
+
products={productsGroupedBySubCategory[subCategoryName]}
|
|
32
|
+
onProductClick={onProductClick}
|
|
33
|
+
onAddToCart={onAddToCart}
|
|
34
|
+
/>
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default ProductsDisplay
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import CategoryList from "./CategoryList";
|
|
2
|
+
import PriceRangeSlider from "./PriceRangeSlider";
|
|
3
|
+
|
|
4
|
+
const ProductsSidebar = ({
|
|
5
|
+
categories,
|
|
6
|
+
selectedCategory,
|
|
7
|
+
onCategorySelect,
|
|
8
|
+
priceRange,
|
|
9
|
+
onPriceRangeChange,
|
|
10
|
+
minPrice,
|
|
11
|
+
maxPrice,
|
|
12
|
+
onResetFilters,
|
|
13
|
+
className = ''
|
|
14
|
+
}) => (
|
|
15
|
+
<div className={`space-y-6 ${className}`} dir="rtl">
|
|
16
|
+
{/* Reset Filters Button */}
|
|
17
|
+
<button
|
|
18
|
+
onClick={onResetFilters}
|
|
19
|
+
className="w-full text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition-colors duration-200"
|
|
20
|
+
>
|
|
21
|
+
אפס סינונים
|
|
22
|
+
</button>
|
|
23
|
+
|
|
24
|
+
{/* Price Range Filter */}
|
|
25
|
+
<div className="glass-card p-4">
|
|
26
|
+
<h3 className="subtitle font-semibold mb-4">סינון לפי מחיר</h3>
|
|
27
|
+
<PriceRangeSlider
|
|
28
|
+
min={minPrice}
|
|
29
|
+
max={maxPrice}
|
|
30
|
+
value={priceRange}
|
|
31
|
+
onChange={onPriceRangeChange}
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
{/* Category Selection */}
|
|
36
|
+
<div className="glass-card p-4">
|
|
37
|
+
<CategoryList
|
|
38
|
+
categories={categories}
|
|
39
|
+
selectedCategory={selectedCategory}
|
|
40
|
+
onCategorySelect={onCategorySelect}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export default ProductsSidebar
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import SmallItemsGrid from "../SmallItemsGrid";
|
|
2
|
+
|
|
3
|
+
// Subcategory Section Component
|
|
4
|
+
const SubCategorySection = ({ subCategoryName, products, onProductClick, onAddToCart }) => {
|
|
5
|
+
if (!products || products.length === 0) {
|
|
6
|
+
return (
|
|
7
|
+
<div className="mb-2">
|
|
8
|
+
<div className="p-8 text-center">
|
|
9
|
+
<span className='lighterTitle'>
|
|
10
|
+
{subCategoryName}
|
|
11
|
+
</span>
|
|
12
|
+
<p className="content-text">אין מוצרים זמינים בקטגוריה זו</p>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Transform products to SmallItemsGrid format
|
|
19
|
+
const gridItems = products;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="mb-2">
|
|
23
|
+
<SmallItemsGrid
|
|
24
|
+
isLighter={true}
|
|
25
|
+
headerText={subCategoryName}
|
|
26
|
+
items={gridItems}
|
|
27
|
+
showMoreType="carousel"
|
|
28
|
+
autoplay={false}
|
|
29
|
+
onItemClick={onProductClick}
|
|
30
|
+
onAddToCart={onAddToCart}
|
|
31
|
+
className="bg-transparent"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default SubCategorySection
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,8 @@ export { default as AdvantagesList } from './components/AdvantagesList'
|
|
|
10
10
|
export { default as ShoppingCartModal } from './components/cart/ShoppingCartModal'
|
|
11
11
|
export { default as FloatingCartButton } from './components/cart/FloatingCartButton'
|
|
12
12
|
export { default as Menu } from './components/Menu'
|
|
13
|
-
export { default as
|
|
13
|
+
export { default as ProductsDisplay } from './components/products/ProductsDisplay'
|
|
14
|
+
export { default as ProductsSidebar } from './components/products/ProductsSidebar'
|
|
14
15
|
|
|
15
16
|
// Modals
|
|
16
17
|
export { default as ItemDetailsModal } from './components/modals/ItemDetailsModal'
|
package/package.json
CHANGED
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const ContactUs = ({
|
|
4
|
-
title = "צרו קשר",
|
|
5
|
-
subtitle = "נשמח לשמוע מכם ולענות על כל שאלה",
|
|
6
|
-
agreeToTermsText = `אני מסכים/ה לתנאי השימוש ומדיניות הפרטיות של האתר,
|
|
7
|
-
ומאשר/ת קבלת עדכונים שיווקיים באימייל
|
|
8
|
-
(ניתן לבטל בכל עת).`,
|
|
9
|
-
submitContactMessage = () => { },
|
|
10
|
-
businessInfo = {},
|
|
11
|
-
className = ""
|
|
12
|
-
}) => {
|
|
13
|
-
const [formData, setFormData] = useState({
|
|
14
|
-
name: '',
|
|
15
|
-
phone: '',
|
|
16
|
-
email: '',
|
|
17
|
-
message: '',
|
|
18
|
-
agreeToTerms: false
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const [errors, setErrors] = useState({});
|
|
22
|
-
const [loading, setLoading] = useState(false);
|
|
23
|
-
const [submitted, setSubmitted] = useState(false);
|
|
24
|
-
|
|
25
|
-
// Handle input changes
|
|
26
|
-
const handleInputChange = (e) => {
|
|
27
|
-
const { name, value, type, checked } = e.target;
|
|
28
|
-
setFormData(prev => ({
|
|
29
|
-
...prev,
|
|
30
|
-
[name]: type === 'checkbox' ? checked : value
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
// Clear error for this field when user starts typing
|
|
34
|
-
if (errors[name]) {
|
|
35
|
-
setErrors(prev => ({ ...prev, [name]: '' }));
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// Validation functions
|
|
40
|
-
const validateEmail = (email) => {
|
|
41
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
42
|
-
return emailRegex.test(email);
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const validatePhone = (phone) => {
|
|
46
|
-
const phoneRegex = /^[0-9\-\+\(\)\s]{10,}$/;
|
|
47
|
-
return phoneRegex.test(phone.replace(/\s/g, ''));
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Form validation
|
|
51
|
-
const validateForm = () => {
|
|
52
|
-
const newErrors = {};
|
|
53
|
-
|
|
54
|
-
if (!formData.name.trim()) {
|
|
55
|
-
newErrors.name = 'שם מלא הוא שדה חובה';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!formData.phone.trim()) {
|
|
59
|
-
newErrors.phone = 'מספר טלפון הוא שדה חובה';
|
|
60
|
-
} else if (!validatePhone(formData.phone)) {
|
|
61
|
-
newErrors.phone = 'מספר טלפון לא תקין';
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!formData.email.trim()) {
|
|
65
|
-
newErrors.email = 'כתובת אימייל היא שדה חובה';
|
|
66
|
-
} else if (!validateEmail(formData.email)) {
|
|
67
|
-
newErrors.email = 'כתובת אימייל לא תקינה';
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (formData.message && formData.message.trim().length > 500) {
|
|
71
|
-
newErrors.message = 'ההודעה לא יכולה להיות ארוכה מ-500 תווים';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!formData.agreeToTerms) {
|
|
75
|
-
newErrors.agreeToTerms = 'יש לאשר את תנאי השימוש והפרטיות';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
setErrors(newErrors);
|
|
79
|
-
return Object.keys(newErrors).length === 0;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const handleSubmit = async () => {
|
|
83
|
-
if (!validateForm()) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
setLoading(true);
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const result = await submitContactMessage({
|
|
91
|
-
name: formData.name,
|
|
92
|
-
email: formData.email,
|
|
93
|
-
phone: formData.phone,
|
|
94
|
-
message: formData.message || `צרו קשר מהאתר - שם: ${formData.name}, טלפון: ${formData.phone}, אימייל: ${formData.email}. ${formData.agreeToTerms ? 'הסכים לתנאי השימוש.' : ''}`
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
if (result) {
|
|
98
|
-
console.log('Contact message submitted successfully:', result);
|
|
99
|
-
setSubmitted(true);
|
|
100
|
-
// Reset form
|
|
101
|
-
setFormData({
|
|
102
|
-
name: '',
|
|
103
|
-
phone: '',
|
|
104
|
-
email: '',
|
|
105
|
-
message: '',
|
|
106
|
-
agreeToTerms: false
|
|
107
|
-
});
|
|
108
|
-
} else {
|
|
109
|
-
setErrors({ submit: 'אירעה שגיאה בשליחת הטופס. אנא נסו שוב.' });
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.error('Submission error:', error);
|
|
114
|
-
setErrors({ submit: 'אירעה שגיאה בשליחת הטופס. אנא נסו שוב.' });
|
|
115
|
-
} finally {
|
|
116
|
-
setLoading(false);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// Success message component
|
|
122
|
-
if (submitted) {
|
|
123
|
-
return (
|
|
124
|
-
<section className={`py-16 px-4 bg-main ${className}`} dir="rtl">
|
|
125
|
-
<div className="max-w-4xl mx-auto">
|
|
126
|
-
<div className="glass-card p-8 text-center">
|
|
127
|
-
<h3 className="title mb-4">תודה רבה!</h3>
|
|
128
|
-
<p className="subtitle mb-6">
|
|
129
|
-
הטופס נשלח בהצלחה. נחזור אליכם בהקדם האפשרי.
|
|
130
|
-
</p>
|
|
131
|
-
<button
|
|
132
|
-
onClick={() => setSubmitted(false)}
|
|
133
|
-
className="btn-secondary"
|
|
134
|
-
>
|
|
135
|
-
שלח הודעה נוספת
|
|
136
|
-
</button>
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
</section>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<section className={`py-16 px-4 bg-main ${className}`} dir="rtl">
|
|
145
|
-
<div className="max-w-4xl mx-auto">
|
|
146
|
-
{/* Header */}
|
|
147
|
-
<div className="text-center mb-12">
|
|
148
|
-
<h2 className="title mb-4">{title}</h2>
|
|
149
|
-
<p className="subtitle text-gray-600">{subtitle}</p>
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
{/* Contact Form */}
|
|
153
|
-
<div className="glass-card p-8">
|
|
154
|
-
<div className="space-y-4">
|
|
155
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
156
|
-
{/* Name Field */}
|
|
157
|
-
<div>
|
|
158
|
-
<label htmlFor="name" className="block subtitle font-semibold mb-2">
|
|
159
|
-
שם מלא *
|
|
160
|
-
</label>
|
|
161
|
-
<input
|
|
162
|
-
type="text"
|
|
163
|
-
id="name"
|
|
164
|
-
name="name"
|
|
165
|
-
value={formData.name}
|
|
166
|
-
onChange={handleInputChange}
|
|
167
|
-
className={`w-full p-4 rounded-lg border-2 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-20 ${errors.name
|
|
168
|
-
? 'border-red-500 bg-red-50'
|
|
169
|
-
: 'border-gray-300 hover:border-gray-400 focus:border-primary'
|
|
170
|
-
}`}
|
|
171
|
-
placeholder="הכניסו את שמכם המלא"
|
|
172
|
-
dir="rtl"
|
|
173
|
-
/>
|
|
174
|
-
{errors.name && (
|
|
175
|
-
<p className="text-red-500 text-sm mt-2">{errors.name}</p>
|
|
176
|
-
)}
|
|
177
|
-
</div>
|
|
178
|
-
|
|
179
|
-
{/* Phone Field */}
|
|
180
|
-
<div>
|
|
181
|
-
<label htmlFor="phone" className="block subtitle font-semibold mb-2">
|
|
182
|
-
מספר טלפון *
|
|
183
|
-
</label>
|
|
184
|
-
<input
|
|
185
|
-
type="tel"
|
|
186
|
-
id="phone"
|
|
187
|
-
name="phone"
|
|
188
|
-
value={formData.phone}
|
|
189
|
-
onChange={handleInputChange}
|
|
190
|
-
className={`w-full p-4 rounded-lg border-2 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-20 ${errors.phone
|
|
191
|
-
? 'border-red-500 bg-red-50'
|
|
192
|
-
: 'border-gray-300 hover:border-gray-400 focus:border-primary'
|
|
193
|
-
}`}
|
|
194
|
-
placeholder="050-1234567"
|
|
195
|
-
dir="ltr"
|
|
196
|
-
/>
|
|
197
|
-
{errors.phone && (
|
|
198
|
-
<p className="text-red-500 text-sm mt-2">{errors.phone}</p>
|
|
199
|
-
)}
|
|
200
|
-
</div>
|
|
201
|
-
</div>
|
|
202
|
-
|
|
203
|
-
{/* Email Field */}
|
|
204
|
-
<div>
|
|
205
|
-
<label htmlFor="email" className="block subtitle font-semibold mb-2">
|
|
206
|
-
כתובת אימייל *
|
|
207
|
-
</label>
|
|
208
|
-
<input
|
|
209
|
-
type="email"
|
|
210
|
-
id="email"
|
|
211
|
-
name="email"
|
|
212
|
-
value={formData.email}
|
|
213
|
-
onChange={handleInputChange}
|
|
214
|
-
className={`w-full p-4 rounded-lg border-2 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-20 ${errors.email
|
|
215
|
-
? 'border-red-500 bg-red-50'
|
|
216
|
-
: 'border-gray-300 hover:border-gray-400 focus:border-primary'
|
|
217
|
-
}`}
|
|
218
|
-
placeholder="example@email.com"
|
|
219
|
-
dir="ltr"
|
|
220
|
-
/>
|
|
221
|
-
{errors.email && (
|
|
222
|
-
<p className="text-red-500 text-sm mt-2">{errors.email}</p>
|
|
223
|
-
)}
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
{/* Message Field */}
|
|
227
|
-
<div>
|
|
228
|
-
<label htmlFor="message" className="block subtitle font-semibold mb-2">
|
|
229
|
-
הודעה (אופציונלי)
|
|
230
|
-
</label>
|
|
231
|
-
<textarea
|
|
232
|
-
id="message"
|
|
233
|
-
name="message"
|
|
234
|
-
value={formData.message}
|
|
235
|
-
onChange={handleInputChange}
|
|
236
|
-
rows={4}
|
|
237
|
-
maxLength={500}
|
|
238
|
-
className={`w-full p-4 rounded-lg border-2 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-20 resize-vertical ${errors.message
|
|
239
|
-
? 'border-red-500 bg-red-50'
|
|
240
|
-
: 'border-gray-300 hover:border-gray-400 focus:border-primary'
|
|
241
|
-
}`}
|
|
242
|
-
placeholder="ספרו לנו מה אתם מחפשים, איך אנחנו יכולים לעזור לכם, או כל דבר אחר שתרצו לשתף איתנו..."
|
|
243
|
-
dir="rtl"
|
|
244
|
-
/>
|
|
245
|
-
<div className="flex justify-between items-center mt-1">
|
|
246
|
-
{errors.message && (
|
|
247
|
-
<p className="text-red-500 text-sm">{errors.message}</p>
|
|
248
|
-
)}
|
|
249
|
-
<span className="text-xs text-gray-500 mr-auto">
|
|
250
|
-
{formData.message.length}/500 תווים
|
|
251
|
-
</span>
|
|
252
|
-
</div>
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
{/* Checkbox Agreement */}
|
|
257
|
-
<div className="px-4 rounded-lg">
|
|
258
|
-
<label className="flex items-start space-x-3 space-x-reverse cursor-pointer">
|
|
259
|
-
<input
|
|
260
|
-
type="checkbox"
|
|
261
|
-
name="agreeToTerms"
|
|
262
|
-
checked={formData.agreeToTerms}
|
|
263
|
-
onChange={handleInputChange}
|
|
264
|
-
className="mt-1 w-5 h-5 text-primary border-2 border-gray-300 rounded focus:ring-primary focus:ring-2 focus:ring-opacity-20"
|
|
265
|
-
/>
|
|
266
|
-
<div className="flex-1">
|
|
267
|
-
<span className="content-text leading-relaxed">
|
|
268
|
-
{agreeToTermsText}
|
|
269
|
-
</span>
|
|
270
|
-
</div>
|
|
271
|
-
</label>
|
|
272
|
-
{errors.agreeToTerms && (
|
|
273
|
-
<p className="text-red-500 text-sm mt-2">{errors.agreeToTerms}</p>
|
|
274
|
-
)}
|
|
275
|
-
</div>
|
|
276
|
-
|
|
277
|
-
{/* Submit Error */}
|
|
278
|
-
{errors.submit && (
|
|
279
|
-
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
280
|
-
{errors.submit}
|
|
281
|
-
</div>
|
|
282
|
-
)}
|
|
283
|
-
|
|
284
|
-
{/* Submit Button */}
|
|
285
|
-
<div className="text-center pt-4">
|
|
286
|
-
<button
|
|
287
|
-
onClick={handleSubmit}
|
|
288
|
-
disabled={loading}
|
|
289
|
-
className={`btn-primary text-lg px-8 py-4 inline-flex items-center gap-3 transition-all duration-200 ${loading
|
|
290
|
-
? 'opacity-50 cursor-not-allowed'
|
|
291
|
-
: 'hover:brightness-90 hover:scale-105'
|
|
292
|
-
}`}
|
|
293
|
-
>
|
|
294
|
-
{loading ? (
|
|
295
|
-
<>
|
|
296
|
-
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
|
297
|
-
שולח...
|
|
298
|
-
</>
|
|
299
|
-
) : (
|
|
300
|
-
<>
|
|
301
|
-
שלח הודעה
|
|
302
|
-
<span className="text-xl">→</span>
|
|
303
|
-
</>
|
|
304
|
-
)}
|
|
305
|
-
</button>
|
|
306
|
-
</div>
|
|
307
|
-
</div>
|
|
308
|
-
|
|
309
|
-
{/* Contact Info */}
|
|
310
|
-
<div className="mt-12 pt-8 border-t border-gray-200">
|
|
311
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 text-center">
|
|
312
|
-
<div>
|
|
313
|
-
<h4 className="subtitle font-semibold mb-1">טלפון</h4>
|
|
314
|
-
<p className="content-text">{businessInfo.phone}</p>
|
|
315
|
-
</div>
|
|
316
|
-
<div>
|
|
317
|
-
<h4 className="subtitle font-semibold mb-1">אימייל</h4>
|
|
318
|
-
<p className="content-text">{businessInfo.email}</p>
|
|
319
|
-
</div>
|
|
320
|
-
<div>
|
|
321
|
-
<h4 className="subtitle font-semibold mb-1">שעות פעילות</h4>
|
|
322
|
-
<p className="content-text">{businessInfo.hours}</p>
|
|
323
|
-
</div>
|
|
324
|
-
</div>
|
|
325
|
-
</div>
|
|
326
|
-
</div>
|
|
327
|
-
</div>
|
|
328
|
-
</section>
|
|
329
|
-
);
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
export default ContactUs;
|