@codecavepro/brand 3.0.1 → 3.2.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/dist/src/common/BrandNav.vue +3 -1
- package/dist/src/common/SubNavbar.vue +253 -30
- package/dist/src/common/forms/ContactUsForm.vue +42 -30
- package/dist/src/helpers/form-validator.ts +4 -3
- package/dist/src/lib/crm/types.ts +0 -2
- package/dist/src/lib/menus/menuTypes.ts +2 -1
- package/package.json +1 -1
|
@@ -237,7 +237,7 @@ withDefaults(defineProps<{
|
|
|
237
237
|
position: absolute;
|
|
238
238
|
top: 100%;
|
|
239
239
|
left: 0;
|
|
240
|
-
margin-top:
|
|
240
|
+
margin-top: 14px;
|
|
241
241
|
width: max-content;
|
|
242
242
|
opacity: 0;
|
|
243
243
|
visibility: hidden;
|
|
@@ -245,6 +245,8 @@ withDefaults(defineProps<{
|
|
|
245
245
|
padding: 1.25rem;
|
|
246
246
|
z-index: 100;
|
|
247
247
|
background: var(--color-surface-primary);
|
|
248
|
+
border: 1px solid var(--color-surface-quaternary);
|
|
249
|
+
border-top: 0px;
|
|
248
250
|
-webkit-backdrop-filter: blur(24px);
|
|
249
251
|
backdrop-filter: blur(24px);
|
|
250
252
|
transition: opacity 0.2s ease, visibility 0.2s;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue';
|
|
2
|
+
import { computed, ref, onMounted, nextTick } from 'vue';
|
|
3
|
+
import Shevron from '../assets/icons/shevron.vue';
|
|
3
4
|
import { type MenuItem } from '../lib/menus/menuTypes.ts';
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
@@ -10,48 +11,131 @@ interface Props {
|
|
|
10
11
|
|
|
11
12
|
const props = defineProps<Props>();
|
|
12
13
|
|
|
14
|
+
const openItem = ref<string | null>(null);
|
|
15
|
+
|
|
16
|
+
const itemRefs = new Map<string, HTMLElement>();
|
|
17
|
+
|
|
18
|
+
const submenuStyle = ref({
|
|
19
|
+
left: '0px',
|
|
20
|
+
width: '200px'
|
|
21
|
+
});
|
|
22
|
+
|
|
13
23
|
const items = computed<MenuItem[]>(() => {
|
|
14
24
|
return props.section ? props.subMenu[props.section] ?? [] : [];
|
|
15
25
|
});
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
const activeSubmenu = computed(() => {
|
|
28
|
+
const item = items.value.find(i => i.name === openItem.value);
|
|
29
|
+
return item?.subMenu ?? [];
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function setItemRef(name: string, el: Element | null) {
|
|
33
|
+
if (el instanceof HTMLElement) {
|
|
34
|
+
itemRefs.set(name, el);
|
|
35
|
+
} else {
|
|
36
|
+
itemRefs.delete(name);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function updateSubmenuPosition(itemName: string) {
|
|
41
|
+
if (window.innerWidth < 768) {
|
|
42
|
+
submenuStyle.value = {
|
|
43
|
+
left: '0px',
|
|
44
|
+
width: '100vw'
|
|
45
|
+
};
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const el = itemRefs.get(itemName);
|
|
50
|
+
const nav = document.querySelector('.ds-subnav');
|
|
51
|
+
|
|
52
|
+
if (!el || !nav) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const elRect = el.getBoundingClientRect();
|
|
57
|
+
const navRect = nav.getBoundingClientRect();
|
|
58
|
+
|
|
59
|
+
submenuStyle.value = {
|
|
60
|
+
left: `${elRect.left + elRect.width / 2 - navRect.left}px`,
|
|
61
|
+
width: `${elRect.width}px`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function openSubmenu(item: MenuItem) {
|
|
66
|
+
if (!item.subMenu?.length) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (openItem.value === item.name) {
|
|
71
|
+
openItem.value = null;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updateSubmenuPosition(item.name);
|
|
76
|
+
openItem.value = item.name;
|
|
77
|
+
}
|
|
78
|
+
function closeSubmenu(event?: Event) {
|
|
79
|
+
event?.stopPropagation();
|
|
80
|
+
openItem.value = null;
|
|
81
|
+
}
|
|
82
|
+
async function initSubmenu() {
|
|
83
|
+
await nextTick();
|
|
84
|
+
|
|
85
|
+
const activeItem = items.value.find(
|
|
86
|
+
item => item.name === props.current && item.subMenu?.length
|
|
24
87
|
);
|
|
88
|
+
|
|
89
|
+
if (!activeItem) {
|
|
90
|
+
openItem.value = null;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
updateSubmenuPosition(activeItem.name);
|
|
95
|
+
openItem.value = activeItem.name;
|
|
25
96
|
}
|
|
97
|
+
|
|
98
|
+
onMounted(async () => {
|
|
99
|
+
await initSubmenu();
|
|
100
|
+
});
|
|
101
|
+
|
|
26
102
|
</script>
|
|
27
103
|
|
|
28
104
|
<template>
|
|
29
|
-
<nav
|
|
30
|
-
|
|
31
|
-
class="ds-subnav"
|
|
32
|
-
aria-label="Section"
|
|
33
|
-
>
|
|
34
|
-
<div class="ds-subnav-inner">
|
|
105
|
+
<nav v-if="items.length" class="ds-subnav" aria-label="Section">
|
|
106
|
+
<div class="ds-subnav-inner" :class="{ 'is-submenu-open': openItem }">
|
|
35
107
|
<ul>
|
|
36
|
-
<li
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</a>
|
|
108
|
+
<li v-for="item in items" :key="item.name" :ref="el => setItemRef(item.name, el)">
|
|
109
|
+
<div class="ds-subnav-item" @click="item.subMenu?.length ? openSubmenu(item) : closeSubmenu()">
|
|
110
|
+
<component :is="item.href ? 'a' : 'span'" class="ds-subnav-link" :href="item.href" :aria-current="item.name === current ? 'page' : undefined">
|
|
111
|
+
{{ item.label }}
|
|
112
|
+
</component>
|
|
113
|
+
|
|
114
|
+
<span v-if="item.subMenu?.length" class="ds-subnav-toggle" aria-hidden="true">
|
|
115
|
+
<Shevron class="ds-subnav-chevron" :class="{ 'is-open': openItem === item.name }" />
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
47
118
|
</li>
|
|
48
119
|
</ul>
|
|
49
120
|
</div>
|
|
121
|
+
|
|
122
|
+
<Transition name="submenu">
|
|
123
|
+
<div class="ds-submenu-overlay" :class="{ 'is-open': activeSubmenu.length }" :style="submenuStyle">
|
|
124
|
+
<ul class="ds-submenu-list">
|
|
125
|
+
<li v-for="(subItem, index) in activeSubmenu" :key="subItem.name" class="ds-submenu-item" :style="{ '--delay': `${index * 80}ms` }">
|
|
126
|
+
<a :href="subItem.href" @click="closeSubmenu" class="ds-submenu-link">
|
|
127
|
+
{{ subItem.label }}
|
|
128
|
+
</a>
|
|
129
|
+
</li>
|
|
130
|
+
</ul>
|
|
131
|
+
</div>
|
|
132
|
+
</Transition>
|
|
50
133
|
</nav>
|
|
51
134
|
</template>
|
|
52
135
|
|
|
53
136
|
<style>
|
|
54
137
|
.ds-subnav {
|
|
138
|
+
position: relative;
|
|
55
139
|
border-bottom: 1px solid var(--color-surface-quaternary);
|
|
56
140
|
background: var(--color-surface-primary-transparent);
|
|
57
141
|
-webkit-backdrop-filter: blur(24px);
|
|
@@ -67,6 +151,10 @@ if (
|
|
|
67
151
|
overflow-y: hidden;
|
|
68
152
|
}
|
|
69
153
|
|
|
154
|
+
.ds-subnav-inner.is-submenu-open {
|
|
155
|
+
overflow: hidden;
|
|
156
|
+
}
|
|
157
|
+
|
|
70
158
|
@media (min-width: 768px) {
|
|
71
159
|
.ds-subnav-inner {
|
|
72
160
|
padding-left: var(--gutter-md);
|
|
@@ -81,7 +169,6 @@ if (
|
|
|
81
169
|
}
|
|
82
170
|
}
|
|
83
171
|
|
|
84
|
-
/* center links when there is enough space */
|
|
85
172
|
.ds-subnav ul {
|
|
86
173
|
display: flex;
|
|
87
174
|
align-items: center;
|
|
@@ -89,13 +176,11 @@ if (
|
|
|
89
176
|
|
|
90
177
|
margin: 0;
|
|
91
178
|
padding: 0;
|
|
179
|
+
|
|
92
180
|
list-style: none;
|
|
93
181
|
|
|
94
|
-
/* allow overflow while staying centered */
|
|
95
182
|
width: max-content;
|
|
96
183
|
min-width: 100%;
|
|
97
|
-
|
|
98
|
-
/* prevents wrapping */
|
|
99
184
|
flex-wrap: nowrap;
|
|
100
185
|
}
|
|
101
186
|
|
|
@@ -104,6 +189,12 @@ if (
|
|
|
104
189
|
flex-shrink: 0;
|
|
105
190
|
}
|
|
106
191
|
|
|
192
|
+
.ds-subnav-item {
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
}
|
|
197
|
+
|
|
107
198
|
.ds-subnav-link {
|
|
108
199
|
box-sizing: border-box;
|
|
109
200
|
display: inline-flex;
|
|
@@ -125,7 +216,139 @@ if (
|
|
|
125
216
|
|
|
126
217
|
.ds-subnav-link:hover,
|
|
127
218
|
.ds-subnav-link:active,
|
|
128
|
-
.ds-subnav-link[aria-current=
|
|
219
|
+
.ds-subnav-link[aria-current='page'] {
|
|
220
|
+
color: var(--color-hovered);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.ds-subnav-toggle {
|
|
224
|
+
display: inline-flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: center;
|
|
227
|
+
|
|
228
|
+
margin-left: -0.75rem;
|
|
229
|
+
padding: 0;
|
|
230
|
+
|
|
231
|
+
border: 0;
|
|
232
|
+
background: transparent;
|
|
233
|
+
pointer-events: none;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.ds-subnav-chevron {
|
|
237
|
+
width: 1rem;
|
|
238
|
+
height: 1rem;
|
|
239
|
+
|
|
240
|
+
color: var(--color-heading);
|
|
241
|
+
pointer-events: none;
|
|
242
|
+
|
|
243
|
+
transition:
|
|
244
|
+
transform 0.25s ease,
|
|
245
|
+
color 0.25s ease;
|
|
246
|
+
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.ds-subnav-item:hover .ds-subnav-chevron {
|
|
129
250
|
color: var(--color-hovered);
|
|
130
251
|
}
|
|
252
|
+
|
|
253
|
+
.ds-subnav-chevron.is-open {
|
|
254
|
+
transform: rotate(180deg);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.ds-submenu-overlay {
|
|
258
|
+
position: absolute;
|
|
259
|
+
top: calc(100% + 1px);
|
|
260
|
+
transform: translateX(-50%);
|
|
261
|
+
z-index: 100;
|
|
262
|
+
|
|
263
|
+
min-width: max-content;
|
|
264
|
+
|
|
265
|
+
visibility: hidden;
|
|
266
|
+
opacity: 0;
|
|
267
|
+
pointer-events: none;
|
|
268
|
+
|
|
269
|
+
background: var(--color-surface-primary);
|
|
270
|
+
|
|
271
|
+
border: 1px solid var(--color-surface-quaternary);
|
|
272
|
+
border-top: 0;
|
|
273
|
+
border-radius: 0 0 1rem 1rem;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.ds-submenu-overlay.is-open {
|
|
277
|
+
visibility: visible;
|
|
278
|
+
opacity: 1;
|
|
279
|
+
pointer-events: auto;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.ds-submenu-list {
|
|
283
|
+
display: flex;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
|
|
286
|
+
margin: 0;
|
|
287
|
+
padding: 0.5rem 0;
|
|
288
|
+
|
|
289
|
+
min-width: 200px;
|
|
290
|
+
max-width: 280px;
|
|
291
|
+
|
|
292
|
+
list-style: none;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.ds-submenu-item {
|
|
296
|
+
margin: 0;
|
|
297
|
+
padding: 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.ds-submenu-link {
|
|
301
|
+
box-sizing: border-box;
|
|
302
|
+
--delay: 0ms;
|
|
303
|
+
display: block;
|
|
304
|
+
|
|
305
|
+
padding: 0.5rem 1.5rem;
|
|
306
|
+
|
|
307
|
+
color: var(--color-body-primary);
|
|
308
|
+
|
|
309
|
+
font-family: var(--font-sans);
|
|
310
|
+
font-size: var(--text-body);
|
|
311
|
+
font-weight: var(--font-weight-bold);
|
|
312
|
+
line-height: var(--leading-body);
|
|
313
|
+
|
|
314
|
+
text-align: center;
|
|
315
|
+
text-decoration: none;
|
|
316
|
+
|
|
317
|
+
opacity: 0;
|
|
318
|
+
transform: translateY(8px);
|
|
319
|
+
|
|
320
|
+
animation: submenuTextIn 300ms ease forwards;
|
|
321
|
+
animation-delay: var(--delay);
|
|
322
|
+
|
|
323
|
+
transition: color var(--transition-colors);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.ds-submenu-link:hover {
|
|
327
|
+
color: var(--color-hovered);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
@keyframes submenuTextIn {
|
|
331
|
+
from {
|
|
332
|
+
opacity: 0;
|
|
333
|
+
transform: translateY(8px);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
to {
|
|
337
|
+
opacity: 1;
|
|
338
|
+
transform: translateY(0);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@media (max-width: 767px) {
|
|
343
|
+
.ds-submenu-overlay {
|
|
344
|
+
left: 0 !important;
|
|
345
|
+
width: 100vw !important;
|
|
346
|
+
transform: none;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.ds-submenu-list {
|
|
350
|
+
width: 100%;
|
|
351
|
+
max-width: none;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
131
354
|
</style>
|
|
@@ -54,25 +54,24 @@ const createFormField = <T,>(name: keyof ContactFormDefinition, value: T): FormF
|
|
|
54
54
|
const formData = ref({
|
|
55
55
|
email: createFormField('email', ''),
|
|
56
56
|
firstName: createFormField('firstName', ''),
|
|
57
|
-
lastName: createFormField('lastName', ''),
|
|
58
57
|
companyName: createFormField('companyName', ''),
|
|
59
58
|
linkedinCompanyPage: createFormField('linkedinCompanyPage', ''),
|
|
60
59
|
services: createFormField('services', ''),
|
|
61
60
|
description: createFormField('description', ''),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
communicationConsent: {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
},
|
|
61
|
+
|
|
62
|
+
consentToProcess: {
|
|
63
|
+
value: false,
|
|
64
|
+
error: '',
|
|
65
|
+
label: props.definition.consentToProcess.label,
|
|
66
|
+
required: props.definition.consentToProcess.required,
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
communicationConsent: {
|
|
70
|
+
value: false,
|
|
71
|
+
error: '',
|
|
72
|
+
label: props.definition.communicationConsent.label,
|
|
73
|
+
required: props.definition.communicationConsent.required,
|
|
74
|
+
},
|
|
76
75
|
})
|
|
77
76
|
|
|
78
77
|
const services = computed(() => props.definition.services.options ?? [])
|
|
@@ -123,7 +122,6 @@ const validateRequiredFields = () => {
|
|
|
123
122
|
const resetForm = () => {
|
|
124
123
|
formData.value.email.value = '';
|
|
125
124
|
formData.value.firstName.value = '';
|
|
126
|
-
formData.value.lastName.value = '';
|
|
127
125
|
formData.value.companyName.value = '';
|
|
128
126
|
formData.value.linkedinCompanyPage.value = '';
|
|
129
127
|
formData.value.description.value = '';
|
|
@@ -136,7 +134,6 @@ const resetForm = () => {
|
|
|
136
134
|
const currentValues = (): ContactFormValues => ({
|
|
137
135
|
email: formData.value.email.value,
|
|
138
136
|
firstName: formData.value.firstName.value,
|
|
139
|
-
lastName: formData.value.lastName.value,
|
|
140
137
|
companyName: formData.value.companyName.value,
|
|
141
138
|
linkedinCompanyPage: formData.value.linkedinCompanyPage.value,
|
|
142
139
|
services: formData.value.services.value,
|
|
@@ -233,6 +230,22 @@ fields.forEach((key) => {
|
|
|
233
230
|
)
|
|
234
231
|
})
|
|
235
232
|
|
|
233
|
+
const companyNamePattern = /[^\p{L}\p{N} &]/u;
|
|
234
|
+
const firstNamePattern = /[^\p{L} ]/u;
|
|
235
|
+
|
|
236
|
+
const createSanitizer = (pattern) => {
|
|
237
|
+
return (value) => value.replace(pattern, '');
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const sanitizeCompanyName = createSanitizer(companyNamePattern);
|
|
241
|
+
const sanitizeFirstName = createSanitizer(firstNamePattern);
|
|
242
|
+
|
|
243
|
+
const preventInvalidInput = (event, pattern) => {
|
|
244
|
+
if (event.data && pattern.test(event.data)) {
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
236
249
|
</script>
|
|
237
250
|
|
|
238
251
|
<template>
|
|
@@ -241,11 +254,10 @@ fields.forEach((key) => {
|
|
|
241
254
|
<fieldset class="space-y-2">
|
|
242
255
|
<InputText id="email" v-model="formData.email.value" :isRequired=formData.email.required :label=formData.email.label type="email" :isError="!!formData.email.error" :errorMessage="formData.email.error" :placeholder="formData.email.placeholder ?? ''" autocomplete="email" />
|
|
243
256
|
<div class="flex flex-col xl:flex-row gap-2">
|
|
244
|
-
<InputText id="firstname" v-model="formData.firstName.value" :isRequired=formData.firstName.required :label=formData.firstName.label type="text" :isError="!!formData.firstName.error" :errorMessage="formData.firstName.error" :placeholder="formData.firstName.placeholder ?? ''" autocomplete="given-name" />
|
|
245
|
-
<InputText id="
|
|
257
|
+
<InputText id="firstname" v-model="formData.firstName.value" @beforeinput="event => preventInvalidInput(event, firstNamePattern)" @update:model-value="value => { formData.firstName.value = sanitizeFirstName(value) }" :isRequired="formData.firstName.required" :label="formData.firstName.label" type="text" :isError="!!formData.firstName.error" :errorMessage="formData.firstName.error" :placeholder="formData.firstName.placeholder ?? ''" autocomplete="given-name" />
|
|
258
|
+
<InputText id="companyName" v-model="formData.companyName.value" @beforeinput="event => preventInvalidInput(event, companyNamePattern)" @update:model-value="value => { formData.companyName.value = sanitizeCompanyName(value) }" :isRequired="formData.companyName.required" :label="formData.companyName.label" type="text" :isError="!!formData.companyName.error" :errorMessage="formData.companyName.error" :placeholder="formData.companyName.placeholder ?? ''" />
|
|
246
259
|
</div>
|
|
247
260
|
<div class="flex flex-col xl:flex-row gap-2">
|
|
248
|
-
<InputText id="companyName" v-model="formData.companyName.value" :isRequired=formData.companyName.required :label=formData.companyName.label type="text" :isError="!!formData.companyName.error" :errorMessage="formData.companyName.error" :placeholder="formData.companyName.placeholder ?? ''" />
|
|
249
261
|
<InputText id="linkedinCompanyPage" v-model="formData.linkedinCompanyPage.value" :isRequired=formData.linkedinCompanyPage.required :label=formData.linkedinCompanyPage.label type="text" :isError="!!formData.linkedinCompanyPage.error" :errorMessage="formData.linkedinCompanyPage.error" :placeholder="formData.linkedinCompanyPage.placeholder ?? ''" />
|
|
250
262
|
</div>
|
|
251
263
|
</fieldset>
|
|
@@ -260,22 +272,22 @@ fields.forEach((key) => {
|
|
|
260
272
|
</fieldset>
|
|
261
273
|
<div class="space-y-2">
|
|
262
274
|
<Checkbox id="privacy" v-model="formData.consentToProcess.value" :label="formData.consentToProcess.label" :isRequired="formData.consentToProcess.required" :isError="!!formData.consentToProcess.error" />
|
|
263
|
-
<Checkbox id="promotions" v-model="formData.communicationConsent.value" :label="formData.communicationConsent.label" :isRequired="formData.communicationConsent.required" :isError="!!formData.communicationConsent.error"
|
|
275
|
+
<Checkbox id="promotions" v-model="formData.communicationConsent.value" :label="formData.communicationConsent.label" :isRequired="formData.communicationConsent.required" :isError="!!formData.communicationConsent.error" />
|
|
264
276
|
</div>
|
|
265
277
|
</div>
|
|
266
278
|
<GlowButton @click="submitContactsForm" title="Leave consultation request" class="self-center lg:self-start" />
|
|
267
279
|
</form>
|
|
268
280
|
<div v-show="isAlertVisible" class="border border-action group absolute bottom-19.25 md:fixed z-350 left-1/2 -translate-x-1/2 md:top-40 form-alert flex gap-2 md:gap-8 items-center justify-between w-full h-fit max-w-139 bg-surface-secondary p-4 md:px-6 md:py-2.5 rounded-3xl">
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
</div>
|
|
272
|
-
<p class="text-sm md:text-lg font-bold text-heading md:max-w-sm ">
|
|
273
|
-
{{ alertKind ? ALERT_TEXT[alertKind] : '' }}
|
|
274
|
-
</p>
|
|
275
|
-
<button @click="alertKind = null" class="group-hover:text-action text-heading transition-colors cursor-pointer">
|
|
276
|
-
<component :is="CloseIcon" />
|
|
277
|
-
</button>
|
|
281
|
+
<div v-if="alertKind === 'success'" class="w-8 h-8">
|
|
282
|
+
<component :is="SuccessIcon" class="w-8 h-8" />
|
|
278
283
|
</div>
|
|
284
|
+
<p class="text-sm md:text-lg font-bold text-heading md:max-w-sm ">
|
|
285
|
+
{{ alertKind ? ALERT_TEXT[alertKind] : '' }}
|
|
286
|
+
</p>
|
|
287
|
+
<button @click="alertKind = null" class="group-hover:text-action text-heading transition-colors cursor-pointer">
|
|
288
|
+
<component :is="CloseIcon" />
|
|
289
|
+
</button>
|
|
290
|
+
</div>
|
|
279
291
|
</template>
|
|
280
292
|
|
|
281
293
|
<style scoped>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
2
|
-
const linkedInRegex = /^https?:\/\/([a-z]{2,3}\.)?linkedin\.com\/.*$/
|
|
2
|
+
const linkedInRegex = /^https?:\/\/([a-z]{2,3}\.)?linkedin\.com\/.*$/im
|
|
3
3
|
|
|
4
4
|
export const isCorrectEmailFormat = (email: string | null): boolean => {
|
|
5
5
|
return !!email && emailRegex.test(email)
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
export const isCorrectLinkedInFormat = (linkedIn: string | null): boolean => {
|
|
9
|
+
return !!linkedIn && linkedInRegex.test(linkedIn)
|
|
9
10
|
}
|
|
@@ -26,7 +26,6 @@ export interface CrmFormFieldSpec {
|
|
|
26
26
|
export type ContactFormFieldName =
|
|
27
27
|
| 'email'
|
|
28
28
|
| 'firstName'
|
|
29
|
-
| 'lastName'
|
|
30
29
|
| 'companyName'
|
|
31
30
|
| 'linkedinCompanyPage'
|
|
32
31
|
| 'services'
|
|
@@ -41,7 +40,6 @@ export type ContactFormDefinition = Record<ContactFormFieldName, CrmFormFieldSpe
|
|
|
41
40
|
export interface ContactFormValues {
|
|
42
41
|
email: string;
|
|
43
42
|
firstName: string;
|
|
44
|
-
lastName: string;
|
|
45
43
|
companyName: string;
|
|
46
44
|
linkedinCompanyPage: string;
|
|
47
45
|
services: string;
|
package/package.json
CHANGED