@traky-app/ui 1.6.3 → 1.6.4
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/package.json +15 -1
- package/registry/traky/json/city-select.json +19 -0
- package/registry/traky/json/country-select.json +24 -0
- package/registry/traky/json/geo-search-select.json +24 -0
- package/registry/traky/json/geo-selectors.json +21 -0
- package/registry/traky/json/google-maps-address-picker.json +22 -0
- package/registry/traky/json/lib-geo-city-codes.json +14 -0
- package/registry/traky/json/lib-geo-countries.json +14 -0
- package/registry/traky/json/lib-geo-csc.json +17 -0
- package/registry/traky/json/lib-geo-locations.json +14 -0
- package/registry/traky/json/lib-geo-overrides.json +14 -0
- package/registry/traky/json/lib-geo-types.json +14 -0
- package/registry/traky/json/lib-google-maps-address.json +14 -0
- package/registry/traky/json/lib-phone-countries.json +14 -0
- package/registry/traky/json/lib-phone-e164.json +14 -0
- package/registry/traky/json/lib-utils.json +1 -1
- package/registry/traky/json/office-address-form-dialog.json +17 -0
- package/registry/traky/json/office-address-form.json +31 -0
- package/registry/traky/json/phone-number-input.json +25 -0
- package/registry/traky/json/state-select.json +19 -0
- package/registry/traky/lib/geo-city-codes.ts +234 -0
- package/registry/traky/lib/geo-countries.ts +75 -0
- package/registry/traky/lib/geo-csc.ts +82 -0
- package/registry/traky/lib/geo-locations.ts +171 -0
- package/registry/traky/lib/geo-overrides.ts +375 -0
- package/registry/traky/lib/geo-types.ts +34 -0
- package/registry/traky/lib/google-maps-address.ts +100 -0
- package/registry/traky/lib/phone-countries.ts +380 -0
- package/registry/traky/lib/phone-e164.ts +66 -0
- package/registry/traky/lib/utils.ts +24 -0
- package/registry/traky/registry.json +180 -0
- package/registry/traky/ui/city-select.tsx +113 -0
- package/registry/traky/ui/country-select.tsx +233 -0
- package/registry/traky/ui/geo-search-select.tsx +136 -0
- package/registry/traky/ui/geo-selectors.tsx +162 -0
- package/registry/traky/ui/google-maps-address-picker.tsx +422 -0
- package/registry/traky/ui/office-address-form-dialog.tsx +16 -0
- package/registry/traky/ui/office-address-form.tsx +868 -0
- package/registry/traky/ui/phone-number-input.tsx +253 -0
- package/registry/traky/ui/state-select.tsx +107 -0
- package/src/lib/geo-city-codes.ts +234 -0
- package/src/lib/geo-countries.ts +75 -0
- package/src/lib/geo-csc.ts +82 -0
- package/src/lib/geo-locations.ts +171 -0
- package/src/lib/geo-overrides.ts +375 -0
- package/src/lib/geo-types.ts +34 -0
- package/src/lib/google-maps-address.ts +100 -0
- package/src/lib/phone-countries.ts +380 -0
- package/src/lib/phone-e164.ts +66 -0
- package/src/test/setup.ts +31 -0
- package/src/ui/__tests__/geo-selects.test.tsx +504 -0
- package/src/ui/city-select.tsx +113 -0
- package/src/ui/country-select.tsx +233 -0
- package/src/ui/geo-search-select.tsx +136 -0
- package/src/ui/geo-selectors.tsx +162 -0
- package/src/ui/google-maps-address-picker.tsx +422 -0
- package/src/ui/office-address-form-dialog.tsx +16 -0
- package/src/ui/office-address-form.tsx +877 -0
- package/src/ui/phone-number-input.tsx +253 -0
- package/src/ui/state-select.tsx +107 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
4
|
+
import { useJsApiLoader, GoogleMap, Marker } from "@react-google-maps/api"
|
|
5
|
+
import { IconLoader } from "@tabler/icons-react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../lib/utils"
|
|
8
|
+
import {
|
|
9
|
+
parseGoogleAddressComponents,
|
|
10
|
+
type GoogleMapsAddressSelection,
|
|
11
|
+
} from "../lib/google-maps-address"
|
|
12
|
+
import { Label } from "./label"
|
|
13
|
+
|
|
14
|
+
const MAP_LIBRARIES: ("places")[] = ["places"]
|
|
15
|
+
const DEFAULT_CENTER = { lat: -1.8312, lng: -78.1834 }
|
|
16
|
+
const DEFAULT_ZOOM = 6
|
|
17
|
+
const SELECTED_ZOOM = 16
|
|
18
|
+
const MAP_CONTAINER_STYLE = { width: "100%", height: "min(52dvh, 360px)" }
|
|
19
|
+
|
|
20
|
+
function applyGooglePlacesLightTheme(
|
|
21
|
+
element: google.maps.places.PlaceAutocompleteElement,
|
|
22
|
+
) {
|
|
23
|
+
element.style.setProperty("color-scheme", "light")
|
|
24
|
+
element.style.setProperty("background-color", "#ffffff")
|
|
25
|
+
element.style.setProperty("color", "#141414")
|
|
26
|
+
element.style.setProperty("border", "1px solid #e2e8f0")
|
|
27
|
+
element.style.setProperty("border-radius", "0.375rem")
|
|
28
|
+
element.style.setProperty("width", "100%")
|
|
29
|
+
element.style.setProperty("min-height", "2.75rem")
|
|
30
|
+
element.style.setProperty("font-size", "0.875rem")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parsePlaceInstance(
|
|
34
|
+
place: google.maps.places.Place,
|
|
35
|
+
): GoogleMapsAddressSelection | null {
|
|
36
|
+
const lat = place.location?.lat()
|
|
37
|
+
const lng = place.location?.lng()
|
|
38
|
+
if (lat == null || lng == null) return null
|
|
39
|
+
|
|
40
|
+
const parsed = parseGoogleAddressComponents(
|
|
41
|
+
place.addressComponents as Parameters<
|
|
42
|
+
typeof parseGoogleAddressComponents
|
|
43
|
+
>[0],
|
|
44
|
+
place.displayName || place.formattedAddress || "",
|
|
45
|
+
lat,
|
|
46
|
+
lng,
|
|
47
|
+
)
|
|
48
|
+
if (!parsed) return null
|
|
49
|
+
return {
|
|
50
|
+
...parsed,
|
|
51
|
+
formattedAddress: place.formattedAddress?.trim() || undefined,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type GoogleMapsAddressPickerProps = {
|
|
56
|
+
onLocationSelect: (address: GoogleMapsAddressSelection) => void
|
|
57
|
+
onLocationClear?: () => void
|
|
58
|
+
regionCode?: string
|
|
59
|
+
className?: string
|
|
60
|
+
initialLatitude?: number
|
|
61
|
+
initialLongitude?: number
|
|
62
|
+
initialFormattedAddress?: string
|
|
63
|
+
/** Prioridad sobre `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY`. */
|
|
64
|
+
apiKey?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function GoogleMapsAddressPickerInner({
|
|
68
|
+
onLocationSelect,
|
|
69
|
+
onLocationClear,
|
|
70
|
+
regionCode,
|
|
71
|
+
className,
|
|
72
|
+
initialLatitude,
|
|
73
|
+
initialLongitude,
|
|
74
|
+
initialFormattedAddress,
|
|
75
|
+
}: Omit<GoogleMapsAddressPickerProps, "apiKey">) {
|
|
76
|
+
const autocompleteHostRef = useRef<HTMLDivElement>(null)
|
|
77
|
+
const autocompleteElementRef =
|
|
78
|
+
useRef<google.maps.places.PlaceAutocompleteElement | null>(null)
|
|
79
|
+
const geocoderRef = useRef<google.maps.Geocoder | null>(null)
|
|
80
|
+
|
|
81
|
+
const [searchLabel, setSearchLabel] = useState("")
|
|
82
|
+
const [isGeocoding, setIsGeocoding] = useState(false)
|
|
83
|
+
const [geocodeError, setGeocodeError] = useState<string | null>(null)
|
|
84
|
+
const [marker, setMarker] = useState<google.maps.LatLngLiteral | null>(() => {
|
|
85
|
+
if (
|
|
86
|
+
typeof initialLatitude === "number" &&
|
|
87
|
+
typeof initialLongitude === "number"
|
|
88
|
+
) {
|
|
89
|
+
return { lat: initialLatitude, lng: initialLongitude }
|
|
90
|
+
}
|
|
91
|
+
return null
|
|
92
|
+
})
|
|
93
|
+
const [mapCenter, setMapCenter] = useState<google.maps.LatLngLiteral>(
|
|
94
|
+
marker ?? DEFAULT_CENTER,
|
|
95
|
+
)
|
|
96
|
+
const [mapZoom, setMapZoom] = useState(marker ? SELECTED_ZOOM : DEFAULT_ZOOM)
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
const lat =
|
|
100
|
+
typeof initialLatitude === "number" && Number.isFinite(initialLatitude)
|
|
101
|
+
? initialLatitude
|
|
102
|
+
: null
|
|
103
|
+
const lng =
|
|
104
|
+
typeof initialLongitude === "number" && Number.isFinite(initialLongitude)
|
|
105
|
+
? initialLongitude
|
|
106
|
+
: null
|
|
107
|
+
if (lat == null || lng == null) return
|
|
108
|
+
const position = { lat, lng }
|
|
109
|
+
setMarker(position)
|
|
110
|
+
setMapCenter(position)
|
|
111
|
+
setMapZoom(SELECTED_ZOOM)
|
|
112
|
+
}, [initialLatitude, initialLongitude])
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
const label = initialFormattedAddress?.trim()
|
|
116
|
+
if (!label) return
|
|
117
|
+
setSearchLabel(label)
|
|
118
|
+
if (autocompleteElementRef.current) {
|
|
119
|
+
autocompleteElementRef.current.value = label
|
|
120
|
+
}
|
|
121
|
+
}, [initialFormattedAddress])
|
|
122
|
+
|
|
123
|
+
const mapOptions = useMemo<google.maps.MapOptions>(
|
|
124
|
+
() => ({
|
|
125
|
+
streetViewControl: false,
|
|
126
|
+
mapTypeControl: false,
|
|
127
|
+
fullscreenControl: false,
|
|
128
|
+
zoomControl: true,
|
|
129
|
+
}),
|
|
130
|
+
[],
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const applySelection = useCallback(
|
|
134
|
+
(address: GoogleMapsAddressSelection, label: string) => {
|
|
135
|
+
const position = { lat: address.latitude, lng: address.longitude }
|
|
136
|
+
setMarker(position)
|
|
137
|
+
setMapCenter(position)
|
|
138
|
+
setMapZoom(SELECTED_ZOOM)
|
|
139
|
+
setSearchLabel(label)
|
|
140
|
+
setGeocodeError(null)
|
|
141
|
+
if (autocompleteElementRef.current) {
|
|
142
|
+
autocompleteElementRef.current.value = label
|
|
143
|
+
}
|
|
144
|
+
onLocationSelect({
|
|
145
|
+
...address,
|
|
146
|
+
formattedAddress: label.trim() || address.formattedAddress,
|
|
147
|
+
})
|
|
148
|
+
},
|
|
149
|
+
[onLocationSelect],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const reverseGeocode = useCallback(
|
|
153
|
+
(position: google.maps.LatLngLiteral) =>
|
|
154
|
+
new Promise<{ address: GoogleMapsAddressSelection; label: string } | null>(
|
|
155
|
+
(resolve) => {
|
|
156
|
+
if (!geocoderRef.current) {
|
|
157
|
+
geocoderRef.current = new google.maps.Geocoder()
|
|
158
|
+
}
|
|
159
|
+
geocoderRef.current.geocode({ location: position }, (results, status) => {
|
|
160
|
+
if (status !== "OK" || !results?.[0]) {
|
|
161
|
+
resolve(null)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
const result = results[0]
|
|
165
|
+
const parsed = parseGoogleAddressComponents(
|
|
166
|
+
result.address_components,
|
|
167
|
+
result.formatted_address || "",
|
|
168
|
+
position.lat,
|
|
169
|
+
position.lng,
|
|
170
|
+
)
|
|
171
|
+
if (!parsed) {
|
|
172
|
+
resolve(null)
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
const label = result.formatted_address?.trim() || ""
|
|
176
|
+
resolve({
|
|
177
|
+
address: {
|
|
178
|
+
...parsed,
|
|
179
|
+
formattedAddress: label || undefined,
|
|
180
|
+
},
|
|
181
|
+
label,
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
},
|
|
185
|
+
),
|
|
186
|
+
[],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const handlePositionChange = useCallback(
|
|
190
|
+
async (position: google.maps.LatLngLiteral) => {
|
|
191
|
+
setMarker(position)
|
|
192
|
+
setMapCenter(position)
|
|
193
|
+
setMapZoom(SELECTED_ZOOM)
|
|
194
|
+
setIsGeocoding(true)
|
|
195
|
+
setGeocodeError(null)
|
|
196
|
+
|
|
197
|
+
const resolved = await reverseGeocode(position)
|
|
198
|
+
setIsGeocoding(false)
|
|
199
|
+
|
|
200
|
+
if (resolved) {
|
|
201
|
+
applySelection(resolved.address, resolved.label)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
setGeocodeError(
|
|
206
|
+
"No pudimos resolver esa ubicación. Elige una dirección en el buscador o activa Geocoding API en Google Cloud.",
|
|
207
|
+
)
|
|
208
|
+
},
|
|
209
|
+
[applySelection, reverseGeocode],
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
const host = autocompleteHostRef.current
|
|
214
|
+
if (!host) return
|
|
215
|
+
|
|
216
|
+
let cancelled = false
|
|
217
|
+
let placeAutocomplete: google.maps.places.PlaceAutocompleteElement | null =
|
|
218
|
+
null
|
|
219
|
+
|
|
220
|
+
const onSelect: EventListener = (event) => {
|
|
221
|
+
void (async () => {
|
|
222
|
+
const selectEvent =
|
|
223
|
+
event as google.maps.places.PlacePredictionSelectEvent
|
|
224
|
+
const place = selectEvent.placePrediction.toPlace()
|
|
225
|
+
await place.fetchFields({
|
|
226
|
+
fields: [
|
|
227
|
+
"displayName",
|
|
228
|
+
"formattedAddress",
|
|
229
|
+
"location",
|
|
230
|
+
"addressComponents",
|
|
231
|
+
],
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const parsed = parsePlaceInstance(place)
|
|
235
|
+
if (!parsed) return
|
|
236
|
+
|
|
237
|
+
const label =
|
|
238
|
+
place.formattedAddress?.trim() ||
|
|
239
|
+
[
|
|
240
|
+
parsed.streetNumber,
|
|
241
|
+
parsed.street,
|
|
242
|
+
parsed.city,
|
|
243
|
+
parsed.state,
|
|
244
|
+
parsed.postalCode,
|
|
245
|
+
]
|
|
246
|
+
.filter(Boolean)
|
|
247
|
+
.join(", ")
|
|
248
|
+
|
|
249
|
+
applySelection(parsed, label)
|
|
250
|
+
})()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
void (async () => {
|
|
254
|
+
await google.maps.importLibrary("places")
|
|
255
|
+
|
|
256
|
+
if (cancelled || !autocompleteHostRef.current) return
|
|
257
|
+
|
|
258
|
+
const region = regionCode?.trim().toLowerCase()
|
|
259
|
+
placeAutocomplete = new google.maps.places.PlaceAutocompleteElement({
|
|
260
|
+
...(region ? { includedRegionCodes: [region] } : {}),
|
|
261
|
+
placeholder: "Busca tu dirección en Google Maps…",
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
placeAutocomplete.className = "gmp-place-autocomplete-host block w-full"
|
|
265
|
+
applyGooglePlacesLightTheme(placeAutocomplete)
|
|
266
|
+
|
|
267
|
+
const savedLabel = initialFormattedAddress?.trim()
|
|
268
|
+
if (savedLabel) {
|
|
269
|
+
placeAutocomplete.value = savedLabel
|
|
270
|
+
setSearchLabel(savedLabel)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
host.replaceChildren(placeAutocomplete)
|
|
274
|
+
autocompleteElementRef.current = placeAutocomplete
|
|
275
|
+
placeAutocomplete.addEventListener("gmp-select", onSelect)
|
|
276
|
+
})()
|
|
277
|
+
|
|
278
|
+
return () => {
|
|
279
|
+
cancelled = true
|
|
280
|
+
if (placeAutocomplete) {
|
|
281
|
+
placeAutocomplete.removeEventListener("gmp-select", onSelect)
|
|
282
|
+
}
|
|
283
|
+
host.replaceChildren()
|
|
284
|
+
autocompleteElementRef.current = null
|
|
285
|
+
}
|
|
286
|
+
}, [applySelection, initialFormattedAddress, regionCode])
|
|
287
|
+
|
|
288
|
+
const handleClear = () => {
|
|
289
|
+
setMarker(null)
|
|
290
|
+
setSearchLabel("")
|
|
291
|
+
setGeocodeError(null)
|
|
292
|
+
setIsGeocoding(false)
|
|
293
|
+
setMapCenter(DEFAULT_CENTER)
|
|
294
|
+
setMapZoom(DEFAULT_ZOOM)
|
|
295
|
+
if (autocompleteElementRef.current) {
|
|
296
|
+
autocompleteElementRef.current.value = ""
|
|
297
|
+
}
|
|
298
|
+
onLocationClear?.()
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<div className={cn("space-y-3", className)}>
|
|
303
|
+
<div className="space-y-1.5">
|
|
304
|
+
<Label>Buscar dirección</Label>
|
|
305
|
+
<div
|
|
306
|
+
ref={autocompleteHostRef}
|
|
307
|
+
className="gmp-autocomplete-wrapper w-full rounded-md bg-background"
|
|
308
|
+
/>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div className="overflow-hidden rounded-lg border border-border">
|
|
312
|
+
<GoogleMap
|
|
313
|
+
mapContainerStyle={MAP_CONTAINER_STYLE}
|
|
314
|
+
center={mapCenter}
|
|
315
|
+
zoom={mapZoom}
|
|
316
|
+
onClick={(event) => {
|
|
317
|
+
const latLng = event.latLng
|
|
318
|
+
if (!latLng) return
|
|
319
|
+
void handlePositionChange({ lat: latLng.lat(), lng: latLng.lng() })
|
|
320
|
+
}}
|
|
321
|
+
options={mapOptions}
|
|
322
|
+
>
|
|
323
|
+
{marker ? (
|
|
324
|
+
<Marker
|
|
325
|
+
position={marker}
|
|
326
|
+
draggable
|
|
327
|
+
onDragEnd={(event) => {
|
|
328
|
+
const latLng = event.latLng
|
|
329
|
+
if (!latLng) return
|
|
330
|
+
void handlePositionChange({
|
|
331
|
+
lat: latLng.lat(),
|
|
332
|
+
lng: latLng.lng(),
|
|
333
|
+
})
|
|
334
|
+
}}
|
|
335
|
+
/>
|
|
336
|
+
) : null}
|
|
337
|
+
</GoogleMap>
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
{geocodeError ? (
|
|
341
|
+
<p className="text-xs text-destructive">{geocodeError}</p>
|
|
342
|
+
) : null}
|
|
343
|
+
|
|
344
|
+
{marker ? (
|
|
345
|
+
<div className="flex flex-wrap items-center justify-between gap-2 text-xs text-muted-foreground">
|
|
346
|
+
<span className="min-w-0 break-words">
|
|
347
|
+
{isGeocoding ? (
|
|
348
|
+
<>
|
|
349
|
+
<IconLoader className="mr-1 inline size-3 animate-spin" />
|
|
350
|
+
Actualizando dirección…
|
|
351
|
+
</>
|
|
352
|
+
) : (
|
|
353
|
+
searchLabel ||
|
|
354
|
+
`${marker.lat.toFixed(5)}, ${marker.lng.toFixed(5)}`
|
|
355
|
+
)}
|
|
356
|
+
</span>
|
|
357
|
+
<button
|
|
358
|
+
type="button"
|
|
359
|
+
className="shrink-0 text-primary underline underline-offset-2"
|
|
360
|
+
onClick={handleClear}
|
|
361
|
+
>
|
|
362
|
+
Limpiar
|
|
363
|
+
</button>
|
|
364
|
+
</div>
|
|
365
|
+
) : (
|
|
366
|
+
<p className="text-xs text-muted-foreground">
|
|
367
|
+
Busca una dirección o haz clic en el mapa para fijar el punto.
|
|
368
|
+
</p>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function resolveGoogleMapsApiKey(apiKey?: string): string {
|
|
375
|
+
if (apiKey?.trim()) return apiKey.trim()
|
|
376
|
+
if (typeof process !== "undefined" && process.env) {
|
|
377
|
+
return process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY?.trim() ?? ""
|
|
378
|
+
}
|
|
379
|
+
return ""
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function GoogleMapsAddressPicker({
|
|
383
|
+
apiKey,
|
|
384
|
+
...props
|
|
385
|
+
}: GoogleMapsAddressPickerProps) {
|
|
386
|
+
const resolvedKey = resolveGoogleMapsApiKey(apiKey)
|
|
387
|
+
|
|
388
|
+
const { isLoaded, loadError } = useJsApiLoader({
|
|
389
|
+
googleMapsApiKey: resolvedKey,
|
|
390
|
+
libraries: MAP_LIBRARIES,
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
if (!resolvedKey) {
|
|
394
|
+
return (
|
|
395
|
+
<div className="rounded-lg border border-dashed border-border bg-muted/30 p-4 text-sm text-muted-foreground">
|
|
396
|
+
Configura{" "}
|
|
397
|
+
<code className="text-xs">NEXT_PUBLIC_GOOGLE_MAPS_API_KEY</code> (solo
|
|
398
|
+
desarrollo) para usar búsqueda y mapa de Google.
|
|
399
|
+
</div>
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (loadError) {
|
|
404
|
+
return (
|
|
405
|
+
<div className="rounded-lg border border-destructive/40 bg-destructive/10 p-4 text-sm text-destructive">
|
|
406
|
+
No se pudo cargar Google Maps. Habilita Maps JavaScript API, Places API
|
|
407
|
+
(New) y Geocoding API para esta clave.
|
|
408
|
+
</div>
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!isLoaded) {
|
|
413
|
+
return (
|
|
414
|
+
<div className="flex h-[min(52dvh,360px)] items-center justify-center rounded-lg border bg-muted/30 text-sm text-muted-foreground">
|
|
415
|
+
<IconLoader className="mr-2 size-4 animate-spin" />
|
|
416
|
+
Cargando mapa…
|
|
417
|
+
</div>
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return <GoogleMapsAddressPickerInner {...props} />
|
|
422
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/** Compat: importa desde `@traky-app/ui/office-address-form` (`surface="modal"`). */
|
|
4
|
+
export {
|
|
5
|
+
OfficeAddressForm,
|
|
6
|
+
OfficeAddressForm as OfficeAddressFormDialog,
|
|
7
|
+
OfficeAddressFormCard,
|
|
8
|
+
useOfficeAddressForm,
|
|
9
|
+
EMPTY_OFFICE_ADDRESS_FORM_VALUES,
|
|
10
|
+
mergeOfficeAddressFormValues,
|
|
11
|
+
type OfficeAddressFormProps,
|
|
12
|
+
type OfficeAddressFormDialogProps,
|
|
13
|
+
type OfficeAddressFormValues,
|
|
14
|
+
type OfficeAddressEntryMode,
|
|
15
|
+
type OfficeAddressFormCardProps,
|
|
16
|
+
} from "./office-address-form"
|