@imansi/templates 0.1.0
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/LICENSE +21 -0
- package/README.md +557 -0
- package/package.json +35 -0
- package/src/auth/ForgotPasswordPage.jsx +176 -0
- package/src/auth/LoginPage.jsx +265 -0
- package/src/auth/RegisterPage.jsx +302 -0
- package/src/auth/ResetPasswordPage.jsx +158 -0
- package/src/auth/Verify2FAPage.jsx +175 -0
- package/src/dashboard/BillingPage.jsx +345 -0
- package/src/dashboard/DashboardHome.jsx +246 -0
- package/src/dashboard/NotificationsPage.jsx +230 -0
- package/src/dashboard/ProfilePage.jsx +272 -0
- package/src/dashboard/SettingsPage.jsx +415 -0
- package/src/dashboard/UsersTablePage.jsx +317 -0
- package/src/extra/CareersPage.jsx +166 -0
- package/src/extra/ChangelogPage.jsx +127 -0
- package/src/extra/CheckoutPage.jsx +383 -0
- package/src/extra/ComingSoonPage.jsx +147 -0
- package/src/extra/ErrorPage.jsx +91 -0
- package/src/extra/InvoicePage.jsx +241 -0
- package/src/extra/LegalPage.jsx +115 -0
- package/src/extra/MaintenancePage.jsx +172 -0
- package/src/extra/OnboardingPage.jsx +197 -0
- package/src/extra/StatusPage.jsx +181 -0
- package/src/index.js +53 -0
- package/src/layouts/AuthLayout.jsx +104 -0
- package/src/layouts/DashboardLayout.jsx +249 -0
- package/src/layouts/MarketingLayout.jsx +184 -0
- package/src/marketing/AboutPage.jsx +189 -0
- package/src/marketing/BlogPage.jsx +182 -0
- package/src/marketing/BlogPostPage.jsx +192 -0
- package/src/marketing/ContactPage.jsx +546 -0
- package/src/marketing/DocsPage.jsx +256 -0
- package/src/marketing/HomePage.jsx +702 -0
- package/src/marketing/LandingPage.jsx +755 -0
- package/src/marketing/PricingPage.jsx +205 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Alert,
|
|
4
|
+
Button,
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
Input,
|
|
8
|
+
Label,
|
|
9
|
+
Separator,
|
|
10
|
+
Textarea,
|
|
11
|
+
} from '@imansi/ui';
|
|
12
|
+
import { MarketingLayout } from '../layouts/MarketingLayout.jsx';
|
|
13
|
+
|
|
14
|
+
export function ContactPage({
|
|
15
|
+
brandName = 'Imansi',
|
|
16
|
+
brandLogo,
|
|
17
|
+
navLinks = [],
|
|
18
|
+
headerCTA,
|
|
19
|
+
headerSecondaryCTA,
|
|
20
|
+
footerColumns = [],
|
|
21
|
+
|
|
22
|
+
/* ===== Hero ===== */
|
|
23
|
+
badge = 'Contacto',
|
|
24
|
+
title = 'Hablemos',
|
|
25
|
+
subtitle = 'Nuestro equipo está disponible para ayudarte con cualquier consulta sobre el producto, precios o partnership.',
|
|
26
|
+
|
|
27
|
+
/* ===== Cards de métodos ===== */
|
|
28
|
+
contactMethods = [],
|
|
29
|
+
|
|
30
|
+
/* ===== Formulario ===== */
|
|
31
|
+
formTitle = 'Enviá tu consulta',
|
|
32
|
+
formNameLabel = 'Nombre completo',
|
|
33
|
+
formEmailLabel = 'Correo electrónico',
|
|
34
|
+
formCompanyLabel = 'Empresa',
|
|
35
|
+
formSubjectLabel = 'Asunto',
|
|
36
|
+
formSubjectOptions = [],
|
|
37
|
+
formMessageLabel = 'Mensaje',
|
|
38
|
+
formMessagePlaceholder = 'Contanos sobre tu proyecto, consulta o problema...',
|
|
39
|
+
formPrivacyNote = 'Al enviar aceptás nuestra Política de Privacidad.',
|
|
40
|
+
formSubmitText = 'Enviar mensaje',
|
|
41
|
+
formSubmitLoadingText = 'Enviando...',
|
|
42
|
+
formSuccessTitle = '¡Mensaje enviado!',
|
|
43
|
+
formSuccessMessage = 'Gracias por escribirnos. Te responderemos en menos de 24 horas.',
|
|
44
|
+
|
|
45
|
+
/* ===== Sidebar: info ===== */
|
|
46
|
+
infoTitle = 'Información de contacto',
|
|
47
|
+
infoItems = [],
|
|
48
|
+
|
|
49
|
+
/* ===== Sidebar: horario ===== */
|
|
50
|
+
hoursTitle = 'Horario de atención',
|
|
51
|
+
hoursItems = [],
|
|
52
|
+
|
|
53
|
+
/* ===== Oficinas ===== */
|
|
54
|
+
officesTitle = 'Nuestras oficinas',
|
|
55
|
+
officesSubtitle = 'Estamos presentes en tres continentes.',
|
|
56
|
+
offices = [],
|
|
57
|
+
|
|
58
|
+
onSubmit,
|
|
59
|
+
error: externalError,
|
|
60
|
+
loading = false,
|
|
61
|
+
success: externalSuccess = false,
|
|
62
|
+
}) {
|
|
63
|
+
const [nombre, setNombre] = useState('');
|
|
64
|
+
const [email, setEmail] = useState('');
|
|
65
|
+
const [empresa, setEmpresa] = useState('');
|
|
66
|
+
const [asunto, setAsunto] = useState('');
|
|
67
|
+
const [mensaje, setMensaje] = useState('');
|
|
68
|
+
const [localError, setLocalError] = useState(null);
|
|
69
|
+
const [localSuccess, setLocalSuccess] = useState(false);
|
|
70
|
+
|
|
71
|
+
const showingSuccess = externalSuccess || localSuccess;
|
|
72
|
+
const error = localError || externalError;
|
|
73
|
+
|
|
74
|
+
async function handleSubmit(e) {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
setLocalError(null);
|
|
77
|
+
|
|
78
|
+
if (!nombre || !email || !mensaje) {
|
|
79
|
+
setLocalError('Completá los campos obligatorios.');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!email.includes('@')) {
|
|
84
|
+
setLocalError('Ingresá un correo válido.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (onSubmit) {
|
|
89
|
+
const result = await onSubmit({ nombre, email, empresa, asunto, mensaje });
|
|
90
|
+
if (result?.ok) setLocalSuccess(true);
|
|
91
|
+
} else {
|
|
92
|
+
setLocalSuccess(true);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resetForm() {
|
|
97
|
+
setNombre('');
|
|
98
|
+
setEmail('');
|
|
99
|
+
setEmpresa('');
|
|
100
|
+
setAsunto('');
|
|
101
|
+
setMensaje('');
|
|
102
|
+
setLocalSuccess(false);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<MarketingLayout
|
|
107
|
+
brandName={brandName}
|
|
108
|
+
brandLogo={brandLogo}
|
|
109
|
+
navLinks={navLinks}
|
|
110
|
+
headerCTA={headerCTA}
|
|
111
|
+
headerSecondaryCTA={headerSecondaryCTA}
|
|
112
|
+
footerColumns={footerColumns}
|
|
113
|
+
>
|
|
114
|
+
{/* ============ HERO ============ */}
|
|
115
|
+
<section className="imansi-container pt-24 pb-16">
|
|
116
|
+
<div className="text-center max-w-2xl mx-auto space-y-5">
|
|
117
|
+
<span className="inline-flex items-center rounded-full border border-border bg-card px-4 py-1.5 text-caption font-medium uppercase tracking-widest text-muted-foreground">
|
|
118
|
+
{badge}
|
|
119
|
+
</span>
|
|
120
|
+
<h1 className="text-display font-bold tracking-tight leading-[1.05]">
|
|
121
|
+
{title}
|
|
122
|
+
</h1>
|
|
123
|
+
<p className="text-body text-muted-foreground leading-relaxed">
|
|
124
|
+
{subtitle}
|
|
125
|
+
</p>
|
|
126
|
+
</div>
|
|
127
|
+
</section>
|
|
128
|
+
|
|
129
|
+
{/* ============ MÉTODOS DE CONTACTO ============ */}
|
|
130
|
+
{contactMethods.length > 0 && (
|
|
131
|
+
<section className="imansi-container pb-20">
|
|
132
|
+
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 max-w-5xl mx-auto">
|
|
133
|
+
{contactMethods.map((method, i) => (
|
|
134
|
+
<a
|
|
135
|
+
key={i}
|
|
136
|
+
href={method.href || '#'}
|
|
137
|
+
className="group flex flex-col gap-4 rounded-xl border border-border bg-card p-6 transition-all hover:border-primary/40 hover:shadow-lg"
|
|
138
|
+
>
|
|
139
|
+
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary transition-colors group-hover:bg-primary group-hover:text-primary-foreground">
|
|
140
|
+
{method.icon}
|
|
141
|
+
</div>
|
|
142
|
+
<div className="space-y-1.5 flex-1">
|
|
143
|
+
<h3 className="text-h4 font-semibold text-foreground">
|
|
144
|
+
{method.title}
|
|
145
|
+
</h3>
|
|
146
|
+
<p className="text-small text-muted-foreground leading-relaxed">
|
|
147
|
+
{method.description}
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
{method.action && (
|
|
151
|
+
<span className="inline-flex items-center gap-1.5 text-small font-medium text-primary">
|
|
152
|
+
{method.action}
|
|
153
|
+
<svg
|
|
154
|
+
width="14"
|
|
155
|
+
height="14"
|
|
156
|
+
viewBox="0 0 14 14"
|
|
157
|
+
fill="none"
|
|
158
|
+
stroke="currentColor"
|
|
159
|
+
strokeWidth="1.5"
|
|
160
|
+
strokeLinecap="round"
|
|
161
|
+
strokeLinejoin="round"
|
|
162
|
+
className="transition-transform group-hover:translate-x-0.5"
|
|
163
|
+
>
|
|
164
|
+
<path d="M5 3L9 7L5 11" />
|
|
165
|
+
</svg>
|
|
166
|
+
</span>
|
|
167
|
+
)}
|
|
168
|
+
</a>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
</section>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{/* ============ FORM + INFO ============ */}
|
|
175
|
+
<section className="imansi-container pb-20">
|
|
176
|
+
<div className="grid gap-6 lg:grid-cols-[1.5fr_1fr] max-w-5xl mx-auto items-start">
|
|
177
|
+
{/* ---------- FORMULARIO ---------- */}
|
|
178
|
+
<Card>
|
|
179
|
+
<CardContent className="p-7 sm:p-9">
|
|
180
|
+
{showingSuccess ? (
|
|
181
|
+
<div className="text-center space-y-6 py-14">
|
|
182
|
+
<div className="inline-flex h-16 w-16 items-center justify-center rounded-full bg-success/10 text-success">
|
|
183
|
+
<svg
|
|
184
|
+
width="30"
|
|
185
|
+
height="30"
|
|
186
|
+
viewBox="0 0 24 24"
|
|
187
|
+
fill="none"
|
|
188
|
+
stroke="currentColor"
|
|
189
|
+
strokeWidth="2.5"
|
|
190
|
+
strokeLinecap="round"
|
|
191
|
+
strokeLinejoin="round"
|
|
192
|
+
>
|
|
193
|
+
<polyline points="4,12 10,18 20,6" />
|
|
194
|
+
</svg>
|
|
195
|
+
</div>
|
|
196
|
+
<div className="space-y-2 max-w-md mx-auto">
|
|
197
|
+
<h2 className="text-h3 font-semibold">{formSuccessTitle}</h2>
|
|
198
|
+
<p className="text-small text-muted-foreground leading-relaxed">
|
|
199
|
+
{formSuccessMessage}
|
|
200
|
+
</p>
|
|
201
|
+
</div>
|
|
202
|
+
<Button variant="outline" onClick={resetForm}>
|
|
203
|
+
Enviar otro mensaje
|
|
204
|
+
</Button>
|
|
205
|
+
</div>
|
|
206
|
+
) : (
|
|
207
|
+
<div className="space-y-7">
|
|
208
|
+
{/* Header form */}
|
|
209
|
+
<div className="space-y-2">
|
|
210
|
+
<h2 className="text-h3 font-semibold tracking-tight">
|
|
211
|
+
{formTitle}
|
|
212
|
+
</h2>
|
|
213
|
+
<p className="text-small text-muted-foreground">
|
|
214
|
+
Los campos marcados con <span className="text-destructive">*</span> son obligatorios.
|
|
215
|
+
</p>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
{error && (
|
|
219
|
+
<Alert variant="destructive" title="Error">
|
|
220
|
+
{error}
|
|
221
|
+
</Alert>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
<form onSubmit={handleSubmit} className="space-y-5">
|
|
225
|
+
{/* Nombre + Email */}
|
|
226
|
+
<div className="grid gap-5 sm:grid-cols-2">
|
|
227
|
+
<div className="space-y-2">
|
|
228
|
+
<Label
|
|
229
|
+
htmlFor="contact-nombre"
|
|
230
|
+
className="text-small font-medium"
|
|
231
|
+
>
|
|
232
|
+
{formNameLabel}{' '}
|
|
233
|
+
<span className="text-destructive">*</span>
|
|
234
|
+
</Label>
|
|
235
|
+
<Input
|
|
236
|
+
id="contact-nombre"
|
|
237
|
+
value={nombre}
|
|
238
|
+
onChange={(e) => setNombre(e.target.value)}
|
|
239
|
+
placeholder="Juan Pérez"
|
|
240
|
+
autoComplete="name"
|
|
241
|
+
disabled={loading}
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<div className="space-y-2">
|
|
246
|
+
<Label
|
|
247
|
+
htmlFor="contact-email"
|
|
248
|
+
className="text-small font-medium"
|
|
249
|
+
>
|
|
250
|
+
{formEmailLabel}{' '}
|
|
251
|
+
<span className="text-destructive">*</span>
|
|
252
|
+
</Label>
|
|
253
|
+
<Input
|
|
254
|
+
id="contact-email"
|
|
255
|
+
type="email"
|
|
256
|
+
value={email}
|
|
257
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
258
|
+
placeholder="correo@ejemplo.com"
|
|
259
|
+
autoComplete="email"
|
|
260
|
+
disabled={loading}
|
|
261
|
+
/>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
{/* Empresa + Asunto */}
|
|
266
|
+
<div className="grid gap-5 sm:grid-cols-2">
|
|
267
|
+
<div className="space-y-2">
|
|
268
|
+
<Label
|
|
269
|
+
htmlFor="contact-empresa"
|
|
270
|
+
className="text-small font-medium"
|
|
271
|
+
>
|
|
272
|
+
{formCompanyLabel}
|
|
273
|
+
</Label>
|
|
274
|
+
<Input
|
|
275
|
+
id="contact-empresa"
|
|
276
|
+
value={empresa}
|
|
277
|
+
onChange={(e) => setEmpresa(e.target.value)}
|
|
278
|
+
placeholder="Acme Inc."
|
|
279
|
+
autoComplete="organization"
|
|
280
|
+
disabled={loading}
|
|
281
|
+
/>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<div className="space-y-2">
|
|
285
|
+
<Label
|
|
286
|
+
htmlFor="contact-asunto"
|
|
287
|
+
className="text-small font-medium"
|
|
288
|
+
>
|
|
289
|
+
{formSubjectLabel}
|
|
290
|
+
</Label>
|
|
291
|
+
{formSubjectOptions.length > 0 ? (
|
|
292
|
+
<select
|
|
293
|
+
id="contact-asunto"
|
|
294
|
+
value={asunto}
|
|
295
|
+
onChange={(e) => setAsunto(e.target.value)}
|
|
296
|
+
disabled={loading}
|
|
297
|
+
className="flex h-10 w-full rounded-md border border-border bg-input px-3 py-2 text-body text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50"
|
|
298
|
+
>
|
|
299
|
+
<option value="">Seleccioná una opción</option>
|
|
300
|
+
{formSubjectOptions.map((opt) => (
|
|
301
|
+
<option key={opt} value={opt}>
|
|
302
|
+
{opt}
|
|
303
|
+
</option>
|
|
304
|
+
))}
|
|
305
|
+
</select>
|
|
306
|
+
) : (
|
|
307
|
+
<Input
|
|
308
|
+
id="contact-asunto"
|
|
309
|
+
value={asunto}
|
|
310
|
+
onChange={(e) => setAsunto(e.target.value)}
|
|
311
|
+
placeholder="¿En qué podemos ayudarte?"
|
|
312
|
+
disabled={loading}
|
|
313
|
+
/>
|
|
314
|
+
)}
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
{/* Mensaje */}
|
|
319
|
+
<div className="space-y-2">
|
|
320
|
+
<Label
|
|
321
|
+
htmlFor="contact-mensaje"
|
|
322
|
+
className="text-small font-medium"
|
|
323
|
+
>
|
|
324
|
+
{formMessageLabel}{' '}
|
|
325
|
+
<span className="text-destructive">*</span>
|
|
326
|
+
</Label>
|
|
327
|
+
<Textarea
|
|
328
|
+
id="contact-mensaje"
|
|
329
|
+
placeholder={formMessagePlaceholder}
|
|
330
|
+
value={mensaje}
|
|
331
|
+
onChange={(e) => setMensaje(e.target.value)}
|
|
332
|
+
rows={6}
|
|
333
|
+
disabled={loading}
|
|
334
|
+
/>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<Separator />
|
|
338
|
+
|
|
339
|
+
{/* Footer form */}
|
|
340
|
+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
341
|
+
<p className="text-caption text-muted-foreground max-w-xs leading-relaxed">
|
|
342
|
+
{formPrivacyNote}
|
|
343
|
+
</p>
|
|
344
|
+
<Button
|
|
345
|
+
type="submit"
|
|
346
|
+
disabled={loading}
|
|
347
|
+
className="sm:min-w-[180px]"
|
|
348
|
+
>
|
|
349
|
+
{loading ? formSubmitLoadingText : formSubmitText}
|
|
350
|
+
</Button>
|
|
351
|
+
</div>
|
|
352
|
+
</form>
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
355
|
+
</CardContent>
|
|
356
|
+
</Card>
|
|
357
|
+
|
|
358
|
+
{/* ---------- SIDEBAR ---------- */}
|
|
359
|
+
<div className="space-y-6">
|
|
360
|
+
{/* Info de contacto */}
|
|
361
|
+
{infoItems.length > 0 && (
|
|
362
|
+
<Card>
|
|
363
|
+
<CardContent className="p-6 space-y-5">
|
|
364
|
+
<h3 className="text-h4 font-semibold">{infoTitle}</h3>
|
|
365
|
+
<Separator />
|
|
366
|
+
<ul className="space-y-5">
|
|
367
|
+
{infoItems.map((item, i) => (
|
|
368
|
+
<li key={i} className="flex items-start gap-3">
|
|
369
|
+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-muted text-foreground">
|
|
370
|
+
{item.icon}
|
|
371
|
+
</div>
|
|
372
|
+
<div className="space-y-1 min-w-0 pt-0.5">
|
|
373
|
+
<p className="text-caption font-semibold uppercase tracking-wider text-muted-foreground">
|
|
374
|
+
{item.label}
|
|
375
|
+
</p>
|
|
376
|
+
{item.href ? (
|
|
377
|
+
<a
|
|
378
|
+
href={item.href}
|
|
379
|
+
className="block text-small text-foreground hover:text-primary transition-colors break-words"
|
|
380
|
+
>
|
|
381
|
+
{item.value}
|
|
382
|
+
</a>
|
|
383
|
+
) : (
|
|
384
|
+
<p className="text-small text-foreground break-words">
|
|
385
|
+
{item.value}
|
|
386
|
+
</p>
|
|
387
|
+
)}
|
|
388
|
+
</div>
|
|
389
|
+
</li>
|
|
390
|
+
))}
|
|
391
|
+
</ul>
|
|
392
|
+
</CardContent>
|
|
393
|
+
</Card>
|
|
394
|
+
)}
|
|
395
|
+
|
|
396
|
+
{/* Horario */}
|
|
397
|
+
{hoursItems.length > 0 && (
|
|
398
|
+
<Card>
|
|
399
|
+
<CardContent className="p-6 space-y-5">
|
|
400
|
+
<h3 className="text-h4 font-semibold">{hoursTitle}</h3>
|
|
401
|
+
<Separator />
|
|
402
|
+
<ul className="space-y-3">
|
|
403
|
+
{hoursItems.map((item, i) => (
|
|
404
|
+
<li
|
|
405
|
+
key={i}
|
|
406
|
+
className="flex items-center justify-between gap-3 text-small"
|
|
407
|
+
>
|
|
408
|
+
<span className="text-muted-foreground">
|
|
409
|
+
{item.day}
|
|
410
|
+
</span>
|
|
411
|
+
<span className="font-medium text-foreground">
|
|
412
|
+
{item.hours}
|
|
413
|
+
</span>
|
|
414
|
+
</li>
|
|
415
|
+
))}
|
|
416
|
+
</ul>
|
|
417
|
+
</CardContent>
|
|
418
|
+
</Card>
|
|
419
|
+
)}
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
</section>
|
|
423
|
+
|
|
424
|
+
{/* ============ OFICINAS ============ */}
|
|
425
|
+
{offices.length > 0 && (
|
|
426
|
+
<section className="border-t border-border bg-muted/30">
|
|
427
|
+
<div className="imansi-container py-24">
|
|
428
|
+
<div className="text-center max-w-2xl mx-auto space-y-4 mb-16">
|
|
429
|
+
<h2 className="text-h1 font-bold tracking-tight">
|
|
430
|
+
{officesTitle}
|
|
431
|
+
</h2>
|
|
432
|
+
<p className="text-body text-muted-foreground">
|
|
433
|
+
{officesSubtitle}
|
|
434
|
+
</p>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 max-w-5xl mx-auto">
|
|
438
|
+
{offices.map((office, i) => (
|
|
439
|
+
<Card key={i} className="transition-shadow hover:shadow-md">
|
|
440
|
+
<CardContent className="p-6 space-y-5">
|
|
441
|
+
<div className="flex items-start gap-3">
|
|
442
|
+
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
443
|
+
<svg
|
|
444
|
+
width="20"
|
|
445
|
+
height="20"
|
|
446
|
+
viewBox="0 0 24 24"
|
|
447
|
+
fill="none"
|
|
448
|
+
stroke="currentColor"
|
|
449
|
+
strokeWidth="2"
|
|
450
|
+
strokeLinecap="round"
|
|
451
|
+
strokeLinejoin="round"
|
|
452
|
+
>
|
|
453
|
+
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
|
|
454
|
+
<circle cx="12" cy="10" r="3" />
|
|
455
|
+
</svg>
|
|
456
|
+
</div>
|
|
457
|
+
<div className="space-y-1 min-w-0">
|
|
458
|
+
<h3 className="text-h4 font-semibold leading-tight">
|
|
459
|
+
{office.city}
|
|
460
|
+
</h3>
|
|
461
|
+
{office.country && (
|
|
462
|
+
<p className="text-caption text-muted-foreground uppercase tracking-wider">
|
|
463
|
+
{office.country}
|
|
464
|
+
</p>
|
|
465
|
+
)}
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
<Separator />
|
|
470
|
+
|
|
471
|
+
<div className="space-y-3">
|
|
472
|
+
<div className="flex items-start gap-2.5 text-small">
|
|
473
|
+
<svg
|
|
474
|
+
width="14"
|
|
475
|
+
height="14"
|
|
476
|
+
viewBox="0 0 24 24"
|
|
477
|
+
fill="none"
|
|
478
|
+
stroke="currentColor"
|
|
479
|
+
strokeWidth="2"
|
|
480
|
+
strokeLinecap="round"
|
|
481
|
+
strokeLinejoin="round"
|
|
482
|
+
className="text-muted-foreground shrink-0 mt-0.5"
|
|
483
|
+
>
|
|
484
|
+
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
|
|
485
|
+
<circle cx="12" cy="10" r="3" />
|
|
486
|
+
</svg>
|
|
487
|
+
<span className="text-muted-foreground whitespace-pre-line leading-relaxed">
|
|
488
|
+
{office.address}
|
|
489
|
+
</span>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
{office.phone && (
|
|
493
|
+
<a
|
|
494
|
+
href={`tel:${office.phone.replace(/\s/g, '')}`}
|
|
495
|
+
className="flex items-start gap-2.5 text-small text-muted-foreground hover:text-primary transition-colors"
|
|
496
|
+
>
|
|
497
|
+
<svg
|
|
498
|
+
width="14"
|
|
499
|
+
height="14"
|
|
500
|
+
viewBox="0 0 24 24"
|
|
501
|
+
fill="none"
|
|
502
|
+
stroke="currentColor"
|
|
503
|
+
strokeWidth="2"
|
|
504
|
+
strokeLinecap="round"
|
|
505
|
+
strokeLinejoin="round"
|
|
506
|
+
className="shrink-0 mt-0.5"
|
|
507
|
+
>
|
|
508
|
+
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />
|
|
509
|
+
</svg>
|
|
510
|
+
{office.phone}
|
|
511
|
+
</a>
|
|
512
|
+
)}
|
|
513
|
+
|
|
514
|
+
{office.email && (
|
|
515
|
+
<a
|
|
516
|
+
href={`mailto:${office.email}`}
|
|
517
|
+
className="flex items-start gap-2.5 text-small text-muted-foreground hover:text-primary transition-colors"
|
|
518
|
+
>
|
|
519
|
+
<svg
|
|
520
|
+
width="14"
|
|
521
|
+
height="14"
|
|
522
|
+
viewBox="0 0 24 24"
|
|
523
|
+
fill="none"
|
|
524
|
+
stroke="currentColor"
|
|
525
|
+
strokeWidth="2"
|
|
526
|
+
strokeLinecap="round"
|
|
527
|
+
strokeLinejoin="round"
|
|
528
|
+
className="shrink-0 mt-0.5"
|
|
529
|
+
>
|
|
530
|
+
<rect x="2" y="4" width="20" height="16" rx="2" />
|
|
531
|
+
<path d="m22 6-10 7L2 6" />
|
|
532
|
+
</svg>
|
|
533
|
+
{office.email}
|
|
534
|
+
</a>
|
|
535
|
+
)}
|
|
536
|
+
</div>
|
|
537
|
+
</CardContent>
|
|
538
|
+
</Card>
|
|
539
|
+
))}
|
|
540
|
+
</div>
|
|
541
|
+
</div>
|
|
542
|
+
</section>
|
|
543
|
+
)}
|
|
544
|
+
</MarketingLayout>
|
|
545
|
+
);
|
|
546
|
+
}
|