@jotul/jotul-widgets 0.0.3 → 1.0.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/README.md +7 -0
- package/dist/JotulWidget.css +1 -0
- package/dist/JotulWidget.d.ts +8 -35
- package/dist/JotulWidget.js +217 -149
- package/dist/api.d.ts +7 -0
- package/dist/api.js +118 -0
- package/dist/components/DealerCardSkeleton.d.ts +2 -0
- package/dist/components/DealerCardSkeleton.js +6 -0
- package/dist/components/FinderSearchRowSkeleton.d.ts +2 -0
- package/dist/components/FinderSearchRowSkeleton.js +6 -0
- package/dist/components/InquiryField.d.ts +10 -0
- package/dist/components/InquiryField.js +7 -0
- package/dist/components/ProductPageWidget.d.ts +26 -0
- package/dist/components/ProductPageWidget.js +16 -0
- package/dist/components/product-page/DealerList.d.ts +11 -0
- package/dist/components/product-page/DealerList.js +15 -0
- package/dist/components/product-page/InquiryForm.d.ts +12 -0
- package/dist/components/product-page/InquiryForm.js +14 -0
- package/dist/components/product-page/LocationSearch.d.ts +15 -0
- package/dist/components/product-page/LocationSearch.js +31 -0
- package/dist/components/product-page/StatusBanner.d.ts +6 -0
- package/dist/components/product-page/StatusBanner.js +8 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +3 -0
- package/dist/i18n/widgetStrings.d.ts +39 -0
- package/dist/i18n/widgetStrings.js +87 -0
- package/dist/icons/ArrowRightIcon.d.ts +5 -0
- package/dist/icons/ArrowRightIcon.js +4 -0
- package/dist/icons/ButtonSpinner.d.ts +5 -0
- package/dist/icons/ButtonSpinner.js +4 -0
- package/dist/icons/PinIcon.d.ts +5 -0
- package/dist/icons/PinIcon.js +4 -0
- package/dist/icons/TelephoneIcon.d.ts +5 -0
- package/dist/icons/TelephoneIcon.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +59 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.js +75 -0
- package/dist/widgets/ProductPageWidget.d.ts +1 -0
- package/dist/widgets/ProductPageWidget.js +1 -0
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -9,6 +9,13 @@ It calls a partner-local backend route, for example:
|
|
|
9
9
|
- partner server route -> `@jotul/jotul-api`
|
|
10
10
|
- `@jotul/jotul-api` -> `https://api.jotul.com/api/v1/find-dealer`
|
|
11
11
|
|
|
12
|
+
## Styling
|
|
13
|
+
|
|
14
|
+
Styles are built into the published bundle as `dist/JotulWidget.css`. The component imports that file for you.
|
|
15
|
+
|
|
16
|
+
- **Partners do not need Tailwind** in their app for the widget to look correct.
|
|
17
|
+
- Utilities are compiled with Tailwind’s **`jwi-` prefix**, so they will not clash with a host app that also uses Tailwind.
|
|
18
|
+
|
|
12
19
|
## Example
|
|
13
20
|
|
|
14
21
|
```tsx
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.jwi-pointer-events-none{pointer-events:none}.jwi-absolute{position:absolute}.jwi-relative{position:relative}.jwi-bottom-0{bottom:0}.jwi-left-0{left:0}.jwi-right-0{right:0}.jwi-z-20{z-index:20}.jwi-m-0{margin:0}.jwi--mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.jwi-mb-3{margin-bottom:.75rem}.jwi-mr-\[-12px\]{margin-right:-12px}.jwi-mt-1{margin-top:.25rem}.jwi-mt-2{margin-top:.5rem}.jwi-mt-3{margin-top:.75rem}.jwi-mt-4{margin-top:1rem}.jwi-box-border{box-sizing:border-box}.jwi-flex{display:flex}.jwi-inline-flex{display:inline-flex}.jwi-h-10{height:2.5rem}.jwi-h-12{height:3rem}.jwi-h-3\.5{height:.875rem}.jwi-h-4{height:1rem}.jwi-h-5{height:1.25rem}.jwi-h-6{height:1.5rem}.jwi-h-\[14px\]{height:14px}.jwi-h-\[18px\]{height:18px}.jwi-h-\[22px\]{height:22px}.jwi-h-\[60px\]{height:60px}.jwi-max-h-\[min\(60vh\,480px\)\]{max-height:min(60vh,480px)}.jwi-min-h-\[48px\]{min-height:48px}.jwi-min-h-\[56px\]{min-height:56px}.jwi-w-14{width:3.5rem}.jwi-w-2\/3{width:66.666667%}.jwi-w-24{width:6rem}.jwi-w-28{width:7rem}.jwi-w-3\.5{width:.875rem}.jwi-w-4{width:1rem}.jwi-w-48{width:12rem}.jwi-w-5{width:1.25rem}.jwi-w-\[14px\]{width:14px}.jwi-w-\[18px\]{width:18px}.jwi-w-\[22px\]{width:22px}.jwi-w-\[540px\]{width:540px}.jwi-w-auto{width:auto}.jwi-w-fit{width:-moz-fit-content;width:fit-content}.jwi-w-full{width:100%}.jwi-min-w-0{min-width:0}.jwi-max-w-\[220px\]{max-width:220px}.jwi-max-w-\[70\%\]{max-width:70%}.jwi-max-w-\[calc\(100\%-5rem\)\]{max-width:calc(100% - 5rem)}.jwi-max-w-full{max-width:100%}.jwi-flex-1{flex:1 1 0%}.jwi-flex-shrink-0,.jwi-shrink-0{flex-shrink:0}@keyframes jwi-pulse{50%{opacity:.5}}.jwi-animate-pulse{animation:jwi-pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes jwi-spin{to{transform:rotate(1turn)}}.jwi-animate-spin{animation:jwi-spin 1s linear infinite}.jwi-cursor-pointer{cursor:pointer}.jwi-resize-y{resize:vertical}.jwi-flex-row{flex-direction:row}.jwi-flex-col{flex-direction:column}.jwi-items-start{align-items:flex-start}.jwi-items-center{align-items:center}.jwi-items-stretch{align-items:stretch}.jwi-justify-center{justify-content:center}.jwi-justify-between{justify-content:space-between}.jwi-gap-0\.5{gap:.125rem}.jwi-gap-1\.5{gap:.375rem}.jwi-gap-2{gap:.5rem}.jwi-gap-3{gap:.75rem}.jwi-gap-4{gap:1rem}.jwi-space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.jwi-self-stretch{align-self:stretch}.jwi-overflow-hidden{overflow:hidden}.jwi-overflow-y-auto{overflow-y:auto}.jwi-whitespace-nowrap{white-space:nowrap}.jwi-break-all{word-break:break-all}.jwi-rounded-\[10px\]{border-radius:10px}.jwi-rounded-full{border-radius:9999px}.jwi-border{border-width:1px}.jwi-border-0{border-width:0}.jwi-border-b{border-bottom-width:1px}.jwi-border-\[\#b7e5c2\]{--tw-border-opacity:1;border-color:rgb(183 229 194/var(--tw-border-opacity,1))}.jwi-border-\[\#d8d2c7\]{--tw-border-opacity:1;border-color:rgb(216 210 199/var(--tw-border-opacity,1))}.jwi-border-\[\#e6e1d7\]{--tw-border-opacity:1;border-color:rgb(230 225 215/var(--tw-border-opacity,1))}.jwi-border-\[\#f0c7c2\]{--tw-border-opacity:1;border-color:rgb(240 199 194/var(--tw-border-opacity,1))}.jwi-bg-\[\#FCFCFC\]{--tw-bg-opacity:1;background-color:rgb(252 252 252/var(--tw-bg-opacity,1))}.jwi-bg-\[\#ece8df\]{--tw-bg-opacity:1;background-color:rgb(236 232 223/var(--tw-bg-opacity,1))}.jwi-bg-\[\#eefbf2\]{--tw-bg-opacity:1;background-color:rgb(238 251 242/var(--tw-bg-opacity,1))}.jwi-bg-\[\#ef2b18\]{--tw-bg-opacity:1;background-color:rgb(239 43 24/var(--tw-bg-opacity,1))}.jwi-bg-\[\#f7f5ef\]{--tw-bg-opacity:1;background-color:rgb(247 245 239/var(--tw-bg-opacity,1))}.jwi-bg-\[\#fbf3db\]{--tw-bg-opacity:1;background-color:rgb(251 243 219/var(--tw-bg-opacity,1))}.jwi-bg-\[\#fff3f1\]{--tw-bg-opacity:1;background-color:rgb(255 243 241/var(--tw-bg-opacity,1))}.jwi-bg-transparent{background-color:transparent}.jwi-bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.jwi-bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.jwi-from-white{--tw-gradient-from:#fff var(--tw-gradient-from-position);--tw-gradient-to:hsla(0,0%,100%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.jwi-to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.jwi-p-0{padding:0}.jwi-p-4{padding:1rem}.jwi-p-6{padding:1.5rem}.jwi-px-2\.5{padding-left:.625rem;padding-right:.625rem}.jwi-px-4{padding-left:1rem;padding-right:1rem}.jwi-px-5{padding-left:1.25rem;padding-right:1.25rem}.jwi-px-6{padding-left:1.5rem;padding-right:1.5rem}.jwi-px-7{padding-left:1.75rem;padding-right:1.75rem}.jwi-py-1{padding-top:.25rem;padding-bottom:.25rem}.jwi-py-3{padding-top:.75rem;padding-bottom:.75rem}.jwi-py-4{padding-top:1rem;padding-bottom:1rem}.jwi-py-8{padding-top:2rem;padding-bottom:2rem}.jwi-pb-3{padding-bottom:.75rem}.jwi-pl-5{padding-left:1.25rem}.jwi-pr-1{padding-right:.25rem}.jwi-pr-3{padding-right:.75rem}.jwi-pr-\[12px\]{padding-right:12px}.jwi-text-left{text-align:left}.jwi-font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.jwi-text-\[14px\]{font-size:14px}.jwi-text-base{font-size:1rem;line-height:1.5rem}.jwi-text-sm{font-size:.875rem;line-height:1.25rem}.jwi-text-xs{font-size:.75rem;line-height:1rem}.jwi-font-medium{font-weight:500}.jwi-font-normal{font-weight:400}.jwi-font-semibold{font-weight:600}.jwi-tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.jwi-leading-\[1\.35\]{line-height:1.35}.jwi-leading-\[1\.4\]{line-height:1.4}.jwi-leading-none{line-height:1}.jwi-leading-snug{line-height:1.375}.jwi-text-\[\#111111\]{--tw-text-opacity:1;color:rgb(17 17 17/var(--tw-text-opacity,1))}.jwi-text-\[\#1b5e20\]{--tw-text-opacity:1;color:rgb(27 94 32/var(--tw-text-opacity,1))}.jwi-text-\[\#767676\]{--tw-text-opacity:1;color:rgb(118 118 118/var(--tw-text-opacity,1))}.jwi-text-\[\#8f2d21\]{--tw-text-opacity:1;color:rgb(143 45 33/var(--tw-text-opacity,1))}.jwi-text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.jwi-text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.jwi-opacity-25{opacity:.25}.jwi-opacity-75{opacity:.75}.jwi-opacity-95{opacity:.95}.jwi-shadow-\[0_1px_2px_rgba\(17\,17\,17\,0\.03\)\]{--tw-shadow:0 1px 2px hsla(0,0%,7%,.03);--tw-shadow-colored:0 1px 2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-shadow-\[0_8px_24px_rgba\(17\,17\,17\,0\.08\)\]{--tw-shadow:0 8px 24px hsla(0,0%,7%,.08);--tw-shadow-colored:0 8px 24px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.jwi-outline-none{outline:2px solid transparent;outline-offset:2px}.placeholder\:jwi-text-\[\#767676\]::-moz-placeholder{--tw-text-opacity:1;color:rgb(118 118 118/var(--tw-text-opacity,1))}.placeholder\:jwi-text-\[\#767676\]::placeholder{--tw-text-opacity:1;color:rgb(118 118 118/var(--tw-text-opacity,1))}.hover\:jwi-bg-\[\#d92817\]:hover{--tw-bg-opacity:1;background-color:rgb(217 40 23/var(--tw-bg-opacity,1))}.hover\:jwi-bg-\[\#f7f5f0\]:hover{--tw-bg-opacity:1;background-color:rgb(247 245 240/var(--tw-bg-opacity,1))}.hover\:jwi-text-\[\#444444\]:hover{--tw-text-opacity:1;color:rgb(68 68 68/var(--tw-text-opacity,1))}.hover\:jwi-underline:hover{text-decoration-line:underline}.focus\:jwi-border-\[\#111111\]:focus{--tw-border-opacity:1;border-color:rgb(17 17 17/var(--tw-border-opacity,1))}.disabled\:jwi-cursor-wait:disabled{cursor:wait}.disabled\:hover\:jwi-bg-\[\#ef2b18\]:hover:disabled{--tw-bg-opacity:1;background-color:rgb(239 43 24/var(--tw-bg-opacity,1))}@media (min-width:768px){.md\:jwi-max-w-\[220px\]{max-width:220px}.md\:jwi-flex-row{flex-direction:row}.md\:jwi-items-center{align-items:center}.md\:jwi-justify-between{justify-content:space-between}}
|
package/dist/JotulWidget.d.ts
CHANGED
|
@@ -1,35 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
error?: string;
|
|
10
|
-
};
|
|
11
|
-
export type DealerSearchResponse = {
|
|
12
|
-
apiVersion?: string;
|
|
13
|
-
ok: boolean;
|
|
14
|
-
type?: 'postalCode';
|
|
15
|
-
origin?: {
|
|
16
|
-
postalCode?: string | null;
|
|
17
|
-
postalCodeNumber?: number | null;
|
|
18
|
-
market?: string;
|
|
19
|
-
};
|
|
20
|
-
total?: number;
|
|
21
|
-
dealers?: Array<Record<string, unknown>>;
|
|
22
|
-
error?: string;
|
|
23
|
-
};
|
|
24
|
-
export type CheckWidgetAuthorizationOptions = {
|
|
25
|
-
endpoint?: string;
|
|
26
|
-
fetcher?: typeof fetch;
|
|
27
|
-
};
|
|
28
|
-
export type JotulWidgetProps = {
|
|
29
|
-
type?: string;
|
|
30
|
-
endpoint?: string;
|
|
31
|
-
className?: string;
|
|
32
|
-
};
|
|
33
|
-
export declare function checkWidgetAuthorization(options?: CheckWidgetAuthorizationOptions): Promise<WidgetAuthClientResponse>;
|
|
34
|
-
export declare function searchDealersByPostalCode(postalCode: string, options?: CheckWidgetAuthorizationOptions): Promise<DealerSearchResponse>;
|
|
35
|
-
export declare function JotulWidget({ type, endpoint, className, }: JotulWidgetProps): import("react/jsx-runtime").JSX.Element;
|
|
1
|
+
import './JotulWidget.css';
|
|
2
|
+
import { checkWidgetAuthorization, searchLocationSuggestions, searchDealersByCoordinates, searchDealersByPostalCode } from './api';
|
|
3
|
+
import type { JotulWidgetProps } from './types';
|
|
4
|
+
export { normalizeWidgetLocale } from './i18n/widgetStrings';
|
|
5
|
+
export type { JotulWidgetLocale } from './i18n/widgetStrings';
|
|
6
|
+
export { checkWidgetAuthorization, searchLocationSuggestions, searchDealersByCoordinates, searchDealersByPostalCode, };
|
|
7
|
+
export type { CheckWidgetAuthorizationOptions, DealerSearchResponse, JotulWidgetProps, JotulWidgetType, WidgetAuthClientResponse, } from './types';
|
|
8
|
+
export declare function JotulWidget({ type, endpoint, className, productName, locale: localeProp, brands, }: JotulWidgetProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/JotulWidget.js
CHANGED
|
@@ -1,127 +1,129 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const distanceKm = asNumber(dealer.distanceKm);
|
|
26
|
-
if (distanceKm !== null) {
|
|
27
|
-
return `${Math.round(distanceKm)} km`;
|
|
28
|
-
}
|
|
29
|
-
const postalDistance = asNumber(dealer.postalDistance);
|
|
30
|
-
if (postalDistance !== null) {
|
|
31
|
-
return `${Math.round(postalDistance)} km`;
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
function getDealerKey(dealer, index) {
|
|
36
|
-
return String(dealer.dealerId ?? dealer.name ?? index);
|
|
37
|
-
}
|
|
38
|
-
function getDealerName(dealer) {
|
|
39
|
-
return asText(dealer.name) ?? asText(dealer.dealerId) ?? 'Unknown dealer';
|
|
40
|
-
}
|
|
41
|
-
function getDealerAddressLines(dealer) {
|
|
42
|
-
const address = asText(dealer.address);
|
|
43
|
-
const postalCode = asText(dealer.postalCode);
|
|
44
|
-
return [address, postalCode].filter((value) => Boolean(value));
|
|
45
|
-
}
|
|
46
|
-
export async function checkWidgetAuthorization(options) {
|
|
47
|
-
const endpoint = options?.endpoint ?? '/api/jotul/widget';
|
|
48
|
-
const fetcher = options?.fetcher ?? fetch;
|
|
49
|
-
let response;
|
|
50
|
-
try {
|
|
51
|
-
response = await fetcher(endpoint, {
|
|
52
|
-
method: 'GET',
|
|
53
|
-
headers: {
|
|
54
|
-
Accept: 'application/json',
|
|
55
|
-
},
|
|
56
|
-
cache: 'no-store',
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
return {
|
|
61
|
-
ok: false,
|
|
62
|
-
error: error instanceof Error ? error.message : 'Failed to reach widget auth route',
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
return (await response.json());
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
return {
|
|
70
|
-
ok: false,
|
|
71
|
-
error: 'Invalid API key',
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
export async function searchDealersByPostalCode(postalCode, options) {
|
|
76
|
-
const endpoint = options?.endpoint ?? '/api/jotul/widget';
|
|
77
|
-
const fetcher = options?.fetcher ?? fetch;
|
|
78
|
-
const params = new URLSearchParams({ postalCode: postalCode.trim() });
|
|
79
|
-
let response;
|
|
80
|
-
try {
|
|
81
|
-
response = await fetcher(`${endpoint}?${params.toString()}`, {
|
|
82
|
-
method: 'GET',
|
|
83
|
-
headers: {
|
|
84
|
-
Accept: 'application/json',
|
|
85
|
-
},
|
|
86
|
-
cache: 'no-store',
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
return {
|
|
91
|
-
ok: false,
|
|
92
|
-
error: error instanceof Error ? error.message : 'Failed to reach widget route',
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
return (await response.json());
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
return {
|
|
100
|
-
ok: false,
|
|
101
|
-
error: 'Widget route returned invalid JSON',
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function isWidgetType(value) {
|
|
106
|
-
return value != null && VALID_WIDGET_TYPES.includes(value);
|
|
107
|
-
}
|
|
108
|
-
function renderReadyState(type) {
|
|
109
|
-
switch (type) {
|
|
110
|
-
case 'productPage':
|
|
111
|
-
return null;
|
|
112
|
-
case 'dealerFinder':
|
|
113
|
-
return 'JotulWidget ready: dealerFinder';
|
|
114
|
-
case 'warrantyForm':
|
|
115
|
-
return 'JotulWidget ready: warrantyForm';
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, }) {
|
|
3
|
+
import './JotulWidget.css';
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { FinderSearchRowSkeleton } from './components/FinderSearchRowSkeleton';
|
|
6
|
+
import { ProductPageWidget } from './widgets/ProductPageWidget';
|
|
7
|
+
import { FIND_DEALER_BUTTON_CLASS } from './constants';
|
|
8
|
+
import { ButtonSpinner } from './icons/ButtonSpinner';
|
|
9
|
+
import { normalizeWidgetLocale, WIDGET_STRINGS } from './i18n/widgetStrings';
|
|
10
|
+
import { checkWidgetAuthorization, searchLocationSuggestions, searchDealersByCoordinates, searchDealersByPostalCode, } from './api';
|
|
11
|
+
import { createInquiryFormValues, getSafeWidgetErrorMessage, isValidEmail, isWidgetType, renderReadyState, } from './utils';
|
|
12
|
+
export { normalizeWidgetLocale } from './i18n/widgetStrings';
|
|
13
|
+
export { checkWidgetAuthorization, searchLocationSuggestions, searchDealersByCoordinates, searchDealersByPostalCode, };
|
|
14
|
+
const GEO_PERMISSION_DENIED = 1;
|
|
15
|
+
const GEO_POSITION_UNAVAILABLE = 2;
|
|
16
|
+
const GEO_TIMEOUT = 3;
|
|
17
|
+
const GEOLOCATION_OPTIONS = {
|
|
18
|
+
enableHighAccuracy: false,
|
|
19
|
+
timeout: 15000,
|
|
20
|
+
maximumAge: 300000,
|
|
21
|
+
};
|
|
22
|
+
export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, productName, locale: localeProp, brands, }) {
|
|
23
|
+
const resolvedLocale = useMemo(() => normalizeWidgetLocale(localeProp), [localeProp]);
|
|
24
|
+
const t = WIDGET_STRINGS[resolvedLocale];
|
|
119
25
|
const [auth, setAuth] = useState(null);
|
|
120
|
-
const [isLoading, setIsLoading] = useState(
|
|
121
|
-
const [postalCode, setPostalCode] = useState('');
|
|
26
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
122
27
|
const [searchResult, setSearchResult] = useState(null);
|
|
123
28
|
const [isSearching, setIsSearching] = useState(false);
|
|
29
|
+
const [isSearchingSuggestions, setIsSearchingSuggestions] = useState(false);
|
|
30
|
+
const [locationError, setLocationError] = useState(null);
|
|
31
|
+
const [isManualLocationSearchEnabled] = useState(true);
|
|
32
|
+
const [locationQuery, setLocationQuery] = useState('');
|
|
33
|
+
const [locationSuggestions, setLocationSuggestions] = useState([]);
|
|
34
|
+
const [isSuggestionListOpen, setIsSuggestionListOpen] = useState(false);
|
|
35
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
36
|
+
const [shouldAutoLocateAfterAuth, setShouldAutoLocateAfterAuth] = useState(false);
|
|
37
|
+
const [selectedDealerName, setSelectedDealerName] = useState(null);
|
|
38
|
+
const [inquiryValues, setInquiryValues] = useState(null);
|
|
39
|
+
const [inquiryError, setInquiryError] = useState(null);
|
|
40
|
+
const [isInquirySubmitted, setIsInquirySubmitted] = useState(false);
|
|
41
|
+
const autocompleteCacheRef = useRef(new Map());
|
|
42
|
+
const dealerSearchOptions = useMemo(() => ({
|
|
43
|
+
endpoint,
|
|
44
|
+
locale: resolvedLocale,
|
|
45
|
+
brands,
|
|
46
|
+
}), [brands, endpoint, resolvedLocale]);
|
|
47
|
+
const runDealerSearchByCoordinates = useCallback(async (latitude, longitude) => {
|
|
48
|
+
setLocationError(null);
|
|
49
|
+
setSearchResult(null);
|
|
50
|
+
setIsSearching(true);
|
|
51
|
+
try {
|
|
52
|
+
const result = await searchDealersByCoordinates(latitude, longitude, dealerSearchOptions);
|
|
53
|
+
setSearchResult(result);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
setIsSearching(false);
|
|
57
|
+
}
|
|
58
|
+
}, [dealerSearchOptions]);
|
|
59
|
+
const runLocationSearch = useCallback(() => {
|
|
60
|
+
const messages = WIDGET_STRINGS[resolvedLocale];
|
|
61
|
+
setShouldAutoLocateAfterAuth(false);
|
|
62
|
+
setLocationError(null);
|
|
63
|
+
setSearchResult(null);
|
|
64
|
+
setLocationSuggestions([]);
|
|
65
|
+
setIsSearchingSuggestions(false);
|
|
66
|
+
setIsSuggestionListOpen(false);
|
|
67
|
+
if (typeof navigator === 'undefined' || !navigator.geolocation) {
|
|
68
|
+
setLocationError(messages.locationUnavailableBrowser);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
setIsSearching(true);
|
|
72
|
+
navigator.geolocation.getCurrentPosition(async (position) => {
|
|
73
|
+
setIsSearching(false);
|
|
74
|
+
await runDealerSearchByCoordinates(position.coords.latitude, position.coords.longitude);
|
|
75
|
+
setLocationQuery('');
|
|
76
|
+
setLocationSuggestions([]);
|
|
77
|
+
}, (err) => {
|
|
78
|
+
setIsSearching(false);
|
|
79
|
+
if (err.code === GEO_PERMISSION_DENIED) {
|
|
80
|
+
setLocationError(messages.locationPermissionDenied);
|
|
81
|
+
}
|
|
82
|
+
else if (err.code === GEO_POSITION_UNAVAILABLE) {
|
|
83
|
+
setLocationError(messages.locationPositionUnavailable);
|
|
84
|
+
}
|
|
85
|
+
else if (err.code === GEO_TIMEOUT) {
|
|
86
|
+
setLocationError(messages.locationTimeout);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
setLocationError(messages.locationGenericFailure);
|
|
90
|
+
}
|
|
91
|
+
}, GEOLOCATION_OPTIONS);
|
|
92
|
+
}, [resolvedLocale, runDealerSearchByCoordinates]);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const query = locationQuery.trim();
|
|
95
|
+
if (query.length < 3) {
|
|
96
|
+
setIsSearchingSuggestions(false);
|
|
97
|
+
setLocationSuggestions([]);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const cacheKey = query.toLowerCase();
|
|
101
|
+
const cached = autocompleteCacheRef.current.get(cacheKey);
|
|
102
|
+
if (cached != null) {
|
|
103
|
+
setLocationSuggestions(cached);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
let cancelled = false;
|
|
107
|
+
const timer = setTimeout(async () => {
|
|
108
|
+
setIsSearchingSuggestions(true);
|
|
109
|
+
const result = await searchLocationSuggestions(query, dealerSearchOptions);
|
|
110
|
+
if (cancelled)
|
|
111
|
+
return;
|
|
112
|
+
const suggestions = result.ok && Array.isArray(result.suggestions) ? result.suggestions : [];
|
|
113
|
+
autocompleteCacheRef.current.set(cacheKey, suggestions);
|
|
114
|
+
setLocationSuggestions(suggestions);
|
|
115
|
+
setIsSearchingSuggestions(false);
|
|
116
|
+
}, 350);
|
|
117
|
+
return () => {
|
|
118
|
+
cancelled = true;
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
};
|
|
121
|
+
}, [dealerSearchOptions, locationQuery]);
|
|
124
122
|
useEffect(() => {
|
|
123
|
+
if (type === 'productPage' && !isOpen)
|
|
124
|
+
return;
|
|
125
|
+
if (auth != null)
|
|
126
|
+
return;
|
|
125
127
|
let cancelled = false;
|
|
126
128
|
async function run() {
|
|
127
129
|
setIsLoading(true);
|
|
@@ -135,7 +137,21 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, }
|
|
|
135
137
|
return () => {
|
|
136
138
|
cancelled = true;
|
|
137
139
|
};
|
|
138
|
-
}, [endpoint]);
|
|
140
|
+
}, [auth, endpoint, isOpen, type]);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (!shouldAutoLocateAfterAuth)
|
|
143
|
+
return;
|
|
144
|
+
if (type !== 'productPage' || !isOpen)
|
|
145
|
+
return;
|
|
146
|
+
if (auth == null || isLoading)
|
|
147
|
+
return;
|
|
148
|
+
if (!auth.ok || auth.authorized !== true) {
|
|
149
|
+
setShouldAutoLocateAfterAuth(false);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setShouldAutoLocateAfterAuth(false);
|
|
153
|
+
runLocationSearch();
|
|
154
|
+
}, [auth, isLoading, isOpen, runLocationSearch, shouldAutoLocateAfterAuth, type]);
|
|
139
155
|
const typeState = useMemo(() => {
|
|
140
156
|
if (type == null)
|
|
141
157
|
return 'typeMissing';
|
|
@@ -143,36 +159,88 @@ export function JotulWidget({ type, endpoint = '/api/jotul/widget', className, }
|
|
|
143
159
|
return 'typeInvalid';
|
|
144
160
|
return 'typeReady';
|
|
145
161
|
}, [type]);
|
|
146
|
-
|
|
147
|
-
|
|
162
|
+
const widgetType = isWidgetType(type) ? type : undefined;
|
|
163
|
+
const shellClass = 'jwi-box-border jwi-flex jwi-w-[540px] jwi-max-w-full jwi-flex-col jwi-font-sans jwi-text-[#111111]';
|
|
164
|
+
const rootClass = className != null && className !== '' ? `${shellClass} ${className}` : shellClass;
|
|
165
|
+
if (typeState !== 'typeReady') {
|
|
166
|
+
return _jsx("div", { className: rootClass, children: t.invalidWidgetTypeError });
|
|
167
|
+
}
|
|
168
|
+
if (widgetType === 'productPage' && !isOpen) {
|
|
169
|
+
return (_jsx("div", { className: rootClass, children: _jsx("div", { className: "jwi-flex jwi-py-8 jwi-items-center jwi-justify-center", children: _jsx("button", { type: "button", onClick: () => {
|
|
170
|
+
setLocationError(null);
|
|
171
|
+
setSearchResult(null);
|
|
172
|
+
setLocationQuery('');
|
|
173
|
+
setLocationSuggestions([]);
|
|
174
|
+
setIsSuggestionListOpen(false);
|
|
175
|
+
setIsOpen(true);
|
|
176
|
+
if (auth?.ok && auth.authorized === true && !isLoading) {
|
|
177
|
+
setShouldAutoLocateAfterAuth(false);
|
|
178
|
+
runLocationSearch();
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
setShouldAutoLocateAfterAuth(true);
|
|
182
|
+
}
|
|
183
|
+
}, className: FIND_DEALER_BUTTON_CLASS, children: t.findDealer }) }) }));
|
|
184
|
+
}
|
|
185
|
+
const productPageAuthPending = widgetType === 'productPage' && isOpen && (auth === null || isLoading);
|
|
186
|
+
if (productPageAuthPending) {
|
|
187
|
+
return (_jsx("div", { className: rootClass, children: _jsx("div", { className: "jwi-flex jwi-py-8 jwi-items-center jwi-justify-center", children: _jsx("button", { type: "button", disabled: true, className: `${FIND_DEALER_BUTTON_CLASS} jwi-opacity-95`, children: _jsxs("span", { className: "jwi-inline-flex jwi-items-center jwi-gap-2", children: [_jsx(ButtonSpinner, {}), t.loading] }) }) }) }));
|
|
188
|
+
}
|
|
189
|
+
const waitingForAuth = auth === null && !(widgetType === 'productPage' && !isOpen);
|
|
190
|
+
if ((isLoading || waitingForAuth) && type !== 'productPage') {
|
|
191
|
+
return (_jsx("div", { className: rootClass, children: _jsx("div", { className: "jwi-flex jwi-w-full jwi-flex-col", children: _jsx(FinderSearchRowSkeleton, {}) }) }));
|
|
148
192
|
}
|
|
149
193
|
if (!auth?.ok || !auth.authorized) {
|
|
150
|
-
return (
|
|
151
|
-
}
|
|
152
|
-
if (
|
|
153
|
-
return _jsx("div", { className:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
194
|
+
return _jsx("div", { className: rootClass, children: getSafeWidgetErrorMessage(auth?.error, t) });
|
|
195
|
+
}
|
|
196
|
+
if (widgetType === 'productPage') {
|
|
197
|
+
return (_jsx("div", { className: rootClass, children: _jsx(ProductPageWidget, { t: t, isSearching: isSearching, locationError: locationError, searchResult: searchResult?.ok === false
|
|
198
|
+
? { ...searchResult, error: getSafeWidgetErrorMessage(searchResult.error, t) }
|
|
199
|
+
: searchResult, inquiryValues: inquiryValues, inquiryError: inquiryError, isInquirySubmitted: isInquirySubmitted, selectedDealerName: selectedDealerName, isManualSearchEnabled: isManualLocationSearchEnabled, query: locationQuery, suggestions: locationSuggestions, suggestionsOpen: isSuggestionListOpen, isSuggestionsLoading: isSearchingSuggestions, onQueryChange: (value) => {
|
|
200
|
+
setLocationQuery(value);
|
|
201
|
+
const trimmed = value.trim();
|
|
202
|
+
setIsSuggestionListOpen(trimmed.length > 0);
|
|
203
|
+
if (trimmed.length < 3) {
|
|
204
|
+
setLocationSuggestions([]);
|
|
205
|
+
}
|
|
206
|
+
}, onSuggestionSelect: (suggestion) => {
|
|
207
|
+
setLocationQuery(suggestion.label);
|
|
208
|
+
setLocationSuggestions([]);
|
|
209
|
+
setIsSearchingSuggestions(false);
|
|
210
|
+
setIsSuggestionListOpen(false);
|
|
211
|
+
void runDealerSearchByCoordinates(suggestion.latitude, suggestion.longitude);
|
|
212
|
+
}, onDismissSuggestions: () => {
|
|
213
|
+
setLocationSuggestions([]);
|
|
214
|
+
setIsSearchingSuggestions(false);
|
|
215
|
+
setIsSuggestionListOpen(false);
|
|
216
|
+
}, onInquiryClose: () => {
|
|
217
|
+
setSelectedDealerName(null);
|
|
218
|
+
setInquiryValues(null);
|
|
219
|
+
setInquiryError(null);
|
|
220
|
+
}, onInquirySubmit: () => {
|
|
221
|
+
if (inquiryValues == null)
|
|
222
|
+
return;
|
|
223
|
+
const trimmedName = inquiryValues.name.trim();
|
|
224
|
+
const trimmedEmail = inquiryValues.email.trim();
|
|
225
|
+
const trimmedPhone = inquiryValues.phone.trim();
|
|
226
|
+
if (!trimmedName || !trimmedEmail || !trimmedPhone) {
|
|
227
|
+
setInquiryError(t.formValidationRequired);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (!isValidEmail(trimmedEmail)) {
|
|
231
|
+
setInquiryError(t.formValidationEmail);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
setInquiryError(null);
|
|
235
|
+
setIsInquirySubmitted(true);
|
|
236
|
+
setSelectedDealerName(null);
|
|
237
|
+
setInquiryValues(null);
|
|
238
|
+
}, onInquiryFieldChange: (key, value) => setInquiryValues((current) => current == null ? current : { ...current, [key]: value }), onStartInquiry: (dealerName) => {
|
|
239
|
+
setSelectedDealerName(dealerName);
|
|
240
|
+
setInquiryValues(createInquiryFormValues(productName, dealerName));
|
|
241
|
+
setInquiryError(null);
|
|
242
|
+
setIsInquirySubmitted(false);
|
|
243
|
+
} }) }));
|
|
244
|
+
}
|
|
245
|
+
return _jsx("div", { className: rootClass, children: renderReadyState(widgetType, t) });
|
|
178
246
|
}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CheckWidgetAuthorizationOptions, DealerSearchResponse, LocationAutocompleteResponse, WidgetAuthClientResponse } from './types';
|
|
2
|
+
/** Client-side default when JSON parse fails (English; localized in UI). */
|
|
3
|
+
export declare const GENERIC_WIDGET_ERROR = "Dealer finder is currently unavailable. Please try again later.";
|
|
4
|
+
export declare function checkWidgetAuthorization(options?: CheckWidgetAuthorizationOptions): Promise<WidgetAuthClientResponse>;
|
|
5
|
+
export declare function searchDealersByPostalCode(postalCode: string, options?: CheckWidgetAuthorizationOptions): Promise<DealerSearchResponse>;
|
|
6
|
+
export declare function searchDealersByCoordinates(latitude: number, longitude: number, options?: CheckWidgetAuthorizationOptions): Promise<DealerSearchResponse>;
|
|
7
|
+
export declare function searchLocationSuggestions(query: string, options?: CheckWidgetAuthorizationOptions): Promise<LocationAutocompleteResponse>;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** Client-side default when JSON parse fails (English; localized in UI). */
|
|
2
|
+
export const GENERIC_WIDGET_ERROR = 'Dealer finder is currently unavailable. Please try again later.';
|
|
3
|
+
export async function checkWidgetAuthorization(options) {
|
|
4
|
+
const endpoint = options?.endpoint ?? '/api/jotul/widget';
|
|
5
|
+
const fetcher = options?.fetcher ?? fetch;
|
|
6
|
+
let response;
|
|
7
|
+
try {
|
|
8
|
+
response = await fetcher(endpoint, {
|
|
9
|
+
method: 'GET',
|
|
10
|
+
headers: { Accept: 'application/json' },
|
|
11
|
+
cache: 'no-store',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
return (await response.json());
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function searchDealersByPostalCode(postalCode, options) {
|
|
25
|
+
const endpoint = options?.endpoint ?? '/api/jotul/widget';
|
|
26
|
+
const fetcher = options?.fetcher ?? fetch;
|
|
27
|
+
const params = new URLSearchParams({ postalCode: postalCode.trim() });
|
|
28
|
+
if (options?.locale != null && options.locale.trim() !== '') {
|
|
29
|
+
params.set('locale', options.locale.trim());
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(options?.brands)) {
|
|
32
|
+
for (const brand of options.brands) {
|
|
33
|
+
const value = brand.trim();
|
|
34
|
+
if (value !== '')
|
|
35
|
+
params.append('brands', value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let response;
|
|
39
|
+
try {
|
|
40
|
+
response = await fetcher(`${endpoint}?${params.toString()}`, {
|
|
41
|
+
method: 'GET',
|
|
42
|
+
headers: { Accept: 'application/json' },
|
|
43
|
+
cache: 'no-store',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return (await response.json());
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export async function searchDealersByCoordinates(latitude, longitude, options) {
|
|
57
|
+
const endpoint = options?.endpoint ?? '/api/jotul/widget';
|
|
58
|
+
const fetcher = options?.fetcher ?? fetch;
|
|
59
|
+
const params = new URLSearchParams({
|
|
60
|
+
latitude: String(latitude),
|
|
61
|
+
longitude: String(longitude),
|
|
62
|
+
});
|
|
63
|
+
if (options?.locale != null && options.locale.trim() !== '') {
|
|
64
|
+
params.set('locale', options.locale.trim());
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(options?.brands)) {
|
|
67
|
+
for (const brand of options.brands) {
|
|
68
|
+
const value = brand.trim();
|
|
69
|
+
if (value !== '')
|
|
70
|
+
params.append('brands', value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let response;
|
|
74
|
+
try {
|
|
75
|
+
response = await fetcher(`${endpoint}?${params.toString()}`, {
|
|
76
|
+
method: 'GET',
|
|
77
|
+
headers: { Accept: 'application/json' },
|
|
78
|
+
cache: 'no-store',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
return (await response.json());
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export async function searchLocationSuggestions(query, options) {
|
|
92
|
+
const endpoint = options?.endpoint ?? '/api/jotul/widget';
|
|
93
|
+
const fetcher = options?.fetcher ?? fetch;
|
|
94
|
+
const params = new URLSearchParams({
|
|
95
|
+
autocomplete: '1',
|
|
96
|
+
q: query.trim(),
|
|
97
|
+
});
|
|
98
|
+
if (options?.locale != null && options.locale.trim() !== '') {
|
|
99
|
+
params.set('locale', options.locale.trim());
|
|
100
|
+
}
|
|
101
|
+
let response;
|
|
102
|
+
try {
|
|
103
|
+
response = await fetcher(`${endpoint}?${params.toString()}`, {
|
|
104
|
+
method: 'GET',
|
|
105
|
+
headers: { Accept: 'application/json' },
|
|
106
|
+
cache: 'no-store',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
return (await response.json());
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return { ok: false, error: GENERIC_WIDGET_ERROR };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { R10 } from '../constants';
|
|
3
|
+
/** Matches a dealer card: title + badge, two lines, CTA + phone row. */
|
|
4
|
+
export function DealerCardSkeleton() {
|
|
5
|
+
return (_jsxs("div", { className: `jwi-w-full jwi-animate-pulse ${R10} jwi-border jwi-border-[#e6e1d7] jwi-bg-white jwi-p-4 jwi-shadow-[0_1px_2px_rgba(17,17,17,0.03)]`, children: [_jsxs("div", { className: "jwi-flex jwi-items-start jwi-justify-between jwi-gap-3", children: [_jsx("div", { className: "jwi-h-5 jwi-max-w-[70%] jwi-flex-1 jwi-rounded-full jwi-bg-[#ece8df]" }), _jsx("div", { className: "jwi-h-6 jwi-w-14 jwi-shrink-0 jwi-rounded-full jwi-bg-[#ece8df]" })] }), _jsxs("div", { className: "jwi-mt-4 jwi-space-y-1.5", children: [_jsx("div", { className: "jwi-h-4 jwi-w-full jwi-rounded-full jwi-bg-[#ece8df]" }), _jsx("div", { className: "jwi-h-4 jwi-w-2/3 jwi-rounded-full jwi-bg-[#ece8df]" })] }), _jsxs("div", { className: "jwi-mt-4 jwi-flex jwi-flex-col jwi-gap-3 md:jwi-flex-row md:jwi-items-center md:jwi-justify-between", children: [_jsx("div", { className: `jwi-h-12 jwi-w-full jwi-max-w-[220px] ${R10} jwi-bg-[#ece8df]` }), _jsxs("div", { className: "jwi-flex jwi-min-w-0 jwi-items-center jwi-gap-2", children: [_jsx("div", { className: "jwi-h-3.5 jwi-w-3.5 jwi-shrink-0 jwi-rounded-full jwi-bg-[#ece8df]" }), _jsx("div", { className: "jwi-h-4 jwi-w-24 jwi-rounded-full jwi-bg-[#ece8df]" })] })] })] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { R10 } from '../constants';
|
|
3
|
+
/** Matches the finder chrome: bordered box, pin + hint + Find in one row. */
|
|
4
|
+
export function FinderSearchRowSkeleton() {
|
|
5
|
+
return (_jsx("div", { className: `jwi-w-full jwi-overflow-hidden ${R10} jwi-border jwi-border-[#d8d2c7] jwi-bg-white`, children: _jsxs("div", { className: "jwi-flex jwi-w-full jwi-min-w-0 jwi-flex-row jwi-items-stretch", children: [_jsxs("div", { className: "jwi-flex jwi-min-w-0 jwi-flex-1 jwi-items-center jwi-gap-3 jwi-py-4 jwi-pl-5 jwi-pr-3", children: [_jsx("div", { className: "jwi-h-4 jwi-w-4 jwi-shrink-0 jwi-animate-pulse jwi-rounded-full jwi-bg-[#ece8df]" }), _jsx("div", { className: "jwi-h-5 jwi-min-w-0 jwi-flex-1 jwi-animate-pulse jwi-rounded-full jwi-bg-[#ece8df]" })] }), _jsx("div", { className: "jwi-w-28 jwi-shrink-0 jwi-self-stretch jwi-animate-pulse jwi-bg-[#ece8df]" })] }) }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type InquiryFieldProps = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (value: string) => void;
|
|
5
|
+
type?: 'text' | 'email' | 'tel';
|
|
6
|
+
readOnly?: boolean;
|
|
7
|
+
multiline?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function InquiryField({ label, value, onChange, type, readOnly, multiline, }: InquiryFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|