@pablo2410/shared-ui 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,575 @@
1
+ import {
2
+ cn
3
+ } from "../chunk-JT3XLKKD.js";
4
+
5
+ // src/components/ErrorBoundary.tsx
6
+ import { Component } from "react";
7
+ import { AlertTriangle, RefreshCw } from "lucide-react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ var ErrorBoundary = class extends Component {
10
+ constructor(props) {
11
+ super(props);
12
+ this.state = { hasError: false, error: null };
13
+ }
14
+ static getDerivedStateFromError(error) {
15
+ return { hasError: true, error };
16
+ }
17
+ componentDidCatch(error, errorInfo) {
18
+ console.error("[ErrorBoundary] Caught error:", error, errorInfo);
19
+ this.props.onError?.(error, errorInfo);
20
+ }
21
+ handleRetry = () => {
22
+ this.setState({ hasError: false, error: null });
23
+ };
24
+ render() {
25
+ if (this.state.hasError) {
26
+ if (this.props.fallback) {
27
+ return this.props.fallback;
28
+ }
29
+ return /* @__PURE__ */ jsx(
30
+ "div",
31
+ {
32
+ className: cn(
33
+ "flex flex-col items-center justify-center min-h-[400px] p-8",
34
+ "bg-[#0A0E1A] text-[#E2E8F0]",
35
+ this.props.className
36
+ ),
37
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4 max-w-md text-center", children: [
38
+ /* @__PURE__ */ jsx("div", { className: "w-14 h-14 rounded-full bg-[#ef4444]/10 flex items-center justify-center", children: /* @__PURE__ */ jsx(AlertTriangle, { className: "h-7 w-7 text-[#ef4444]" }) }),
39
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold font-[Montserrat,sans-serif]", children: "Something went wrong" }),
40
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-[#8890A0] font-[Space_Grotesk,sans-serif]", children: "An unexpected error occurred. Please try again or contact support if the problem persists." }),
41
+ this.state.error && /* @__PURE__ */ jsx("pre", { className: "mt-2 p-3 rounded-lg bg-[#0D1220] border border-[#1E2738] text-xs text-[#596475] max-w-full overflow-x-auto font-[JetBrains_Mono,monospace]", children: this.state.error.message }),
42
+ /* @__PURE__ */ jsxs(
43
+ "button",
44
+ {
45
+ onClick: this.handleRetry,
46
+ className: cn(
47
+ "mt-2 flex items-center gap-2 px-4 py-2 rounded-lg",
48
+ "bg-[#8C34E9] text-white text-sm font-medium",
49
+ "hover:bg-[#7928d4] transition-colors cursor-pointer",
50
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]"
51
+ ),
52
+ children: [
53
+ /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" }),
54
+ "Try Again"
55
+ ]
56
+ }
57
+ )
58
+ ] })
59
+ }
60
+ );
61
+ }
62
+ return this.props.children;
63
+ }
64
+ };
65
+
66
+ // src/components/NotFound.tsx
67
+ import { Home, ArrowLeft } from "lucide-react";
68
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
69
+ function NotFoundPage({
70
+ title = "Page Not Found",
71
+ description = "The page you're looking for doesn't exist or has been moved.",
72
+ homePath = "/",
73
+ onBack,
74
+ onHome,
75
+ className
76
+ }) {
77
+ const handleBack = onBack || (() => {
78
+ if (typeof window !== "undefined") {
79
+ window.history.back();
80
+ }
81
+ });
82
+ return /* @__PURE__ */ jsx2(
83
+ "div",
84
+ {
85
+ className: cn(
86
+ "flex flex-col items-center justify-center min-h-screen p-8",
87
+ "bg-[#0A0E1A] text-[#E2E8F0]",
88
+ className
89
+ ),
90
+ children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-6 max-w-md text-center", children: [
91
+ /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
92
+ /* @__PURE__ */ jsx2(
93
+ "span",
94
+ {
95
+ className: "text-[120px] font-bold leading-none font-[Montserrat,sans-serif]",
96
+ style: {
97
+ background: "linear-gradient(135deg, #8C34E9 0%, #14b8a6 100%)",
98
+ WebkitBackgroundClip: "text",
99
+ WebkitTextFillColor: "transparent",
100
+ opacity: 0.15
101
+ },
102
+ children: "404"
103
+ }
104
+ ),
105
+ /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx2(
106
+ "div",
107
+ {
108
+ className: "w-16 h-16 rounded-full flex items-center justify-center",
109
+ style: { background: "rgba(140, 52, 233, 0.1)", border: "1px solid rgba(140, 52, 233, 0.2)" },
110
+ children: /* @__PURE__ */ jsx2("span", { className: "text-2xl font-bold text-[#8C34E9] font-[Montserrat,sans-serif]", children: "?" })
111
+ }
112
+ ) })
113
+ ] }),
114
+ /* @__PURE__ */ jsx2("h1", { className: "text-2xl font-bold font-[Montserrat,sans-serif] text-[#E2E8F0]", children: title }),
115
+ /* @__PURE__ */ jsx2("p", { className: "text-sm text-[#8890A0] font-[Space_Grotesk,sans-serif] leading-relaxed", children: description }),
116
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-col sm:flex-row gap-3 mt-2", children: [
117
+ /* @__PURE__ */ jsxs2(
118
+ "button",
119
+ {
120
+ onClick: handleBack,
121
+ className: cn(
122
+ "flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg",
123
+ "border border-[#1E2738] text-[#8890A0] text-sm font-medium",
124
+ "hover:bg-[#1E2738] hover:text-[#E2E8F0] transition-colors cursor-pointer",
125
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]"
126
+ ),
127
+ children: [
128
+ /* @__PURE__ */ jsx2(ArrowLeft, { className: "h-4 w-4" }),
129
+ "Go Back"
130
+ ]
131
+ }
132
+ ),
133
+ onHome ? /* @__PURE__ */ jsxs2(
134
+ "button",
135
+ {
136
+ onClick: onHome,
137
+ className: cn(
138
+ "flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg",
139
+ "bg-[#8C34E9] text-white text-sm font-medium",
140
+ "hover:bg-[#7928d4] transition-colors cursor-pointer",
141
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]"
142
+ ),
143
+ children: [
144
+ /* @__PURE__ */ jsx2(Home, { className: "h-4 w-4" }),
145
+ "Go Home"
146
+ ]
147
+ }
148
+ ) : /* @__PURE__ */ jsxs2(
149
+ "a",
150
+ {
151
+ href: homePath,
152
+ className: cn(
153
+ "flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg",
154
+ "bg-[#8C34E9] text-white text-sm font-medium no-underline",
155
+ "hover:bg-[#7928d4] transition-colors",
156
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]"
157
+ ),
158
+ children: [
159
+ /* @__PURE__ */ jsx2(Home, { className: "h-4 w-4" }),
160
+ "Go Home"
161
+ ]
162
+ }
163
+ )
164
+ ] })
165
+ ] })
166
+ }
167
+ );
168
+ }
169
+
170
+ // src/components/ManusDialog.tsx
171
+ import { X } from "lucide-react";
172
+ import { useEffect, useRef, useCallback } from "react";
173
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
174
+ function ManusDialog({
175
+ open,
176
+ onClose,
177
+ title,
178
+ subtitle,
179
+ logo,
180
+ children,
181
+ footer,
182
+ maxWidth = "max-w-md",
183
+ className,
184
+ closeOnOverlay = true
185
+ }) {
186
+ const dialogRef = useRef(null);
187
+ useEffect(() => {
188
+ const dialog = dialogRef.current;
189
+ if (!dialog) return;
190
+ if (open && !dialog.open) {
191
+ dialog.showModal();
192
+ } else if (!open && dialog.open) {
193
+ dialog.close();
194
+ }
195
+ }, [open]);
196
+ const handleBackdropClick = useCallback(
197
+ (e) => {
198
+ if (closeOnOverlay && e.target === dialogRef.current) {
199
+ onClose();
200
+ }
201
+ },
202
+ [closeOnOverlay, onClose]
203
+ );
204
+ const handleCancel = useCallback(
205
+ (e) => {
206
+ e.preventDefault();
207
+ onClose();
208
+ },
209
+ [onClose]
210
+ );
211
+ return /* @__PURE__ */ jsx3(
212
+ "dialog",
213
+ {
214
+ ref: dialogRef,
215
+ onClick: handleBackdropClick,
216
+ onCancel: handleCancel,
217
+ className: cn(
218
+ "backdrop:bg-black/60 backdrop:backdrop-blur-sm",
219
+ "bg-transparent p-0 m-auto",
220
+ "open:animate-in open:fade-in-0 open:zoom-in-95",
221
+ maxWidth,
222
+ "w-full"
223
+ ),
224
+ children: /* @__PURE__ */ jsxs3(
225
+ "div",
226
+ {
227
+ className: cn(
228
+ "bg-[#0F1525] border border-[#1E2738] rounded-xl shadow-xl",
229
+ "text-[#E2E8F0] font-[Space_Grotesk,sans-serif]",
230
+ className
231
+ ),
232
+ children: [
233
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-start justify-between p-5 border-b border-[#1E2738]", children: [
234
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3", children: [
235
+ logo && /* @__PURE__ */ jsx3("div", { className: "shrink-0", children: typeof logo === "string" ? /* @__PURE__ */ jsx3(
236
+ "img",
237
+ {
238
+ src: logo,
239
+ alt: "Dialog graphic",
240
+ className: "w-10 h-10 rounded-md"
241
+ }
242
+ ) : logo }),
243
+ /* @__PURE__ */ jsxs3("div", { children: [
244
+ /* @__PURE__ */ jsx3("h2", { className: "text-base font-semibold font-[Montserrat,sans-serif]", children: title }),
245
+ subtitle && /* @__PURE__ */ jsx3("p", { className: "text-xs text-[#8890A0] mt-0.5", children: subtitle })
246
+ ] })
247
+ ] }),
248
+ /* @__PURE__ */ jsx3(
249
+ "button",
250
+ {
251
+ onClick: onClose,
252
+ className: cn(
253
+ "shrink-0 p-1.5 rounded-md -mt-1 -mr-1",
254
+ "text-[#596475] hover:text-[#E2E8F0] hover:bg-[#1E2738]",
255
+ "transition-colors cursor-pointer",
256
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]"
257
+ ),
258
+ children: /* @__PURE__ */ jsx3(X, { className: "h-4 w-4" })
259
+ }
260
+ )
261
+ ] }),
262
+ /* @__PURE__ */ jsx3("div", { className: "p-5", children }),
263
+ footer && /* @__PURE__ */ jsx3("div", { className: "px-5 pb-5 pt-2 flex items-center justify-end gap-2", children: footer })
264
+ ]
265
+ }
266
+ )
267
+ }
268
+ );
269
+ }
270
+
271
+ // src/components/AIChatBox.tsx
272
+ import { Send, Bot, User, Loader2, X as X2 } from "lucide-react";
273
+ import {
274
+ useState,
275
+ useRef as useRef2,
276
+ useCallback as useCallback2,
277
+ useEffect as useEffect2
278
+ } from "react";
279
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
280
+ function MessageBubble({
281
+ message,
282
+ renderContent
283
+ }) {
284
+ const isUser = message.role === "user";
285
+ return /* @__PURE__ */ jsxs4(
286
+ "div",
287
+ {
288
+ className: cn(
289
+ "flex gap-3 px-4 py-3",
290
+ isUser ? "flex-row-reverse" : "flex-row"
291
+ ),
292
+ children: [
293
+ /* @__PURE__ */ jsx4(
294
+ "div",
295
+ {
296
+ className: cn(
297
+ "shrink-0 w-7 h-7 rounded-full flex items-center justify-center",
298
+ isUser ? "bg-[#8C34E9]/20" : "bg-[#14b8a6]/20"
299
+ ),
300
+ children: isUser ? /* @__PURE__ */ jsx4(User, { className: "h-3.5 w-3.5 text-[#A855F7]" }) : /* @__PURE__ */ jsx4(Bot, { className: "h-3.5 w-3.5 text-[#14b8a6]" })
301
+ }
302
+ ),
303
+ /* @__PURE__ */ jsxs4(
304
+ "div",
305
+ {
306
+ className: cn(
307
+ "max-w-[80%] rounded-xl px-3.5 py-2.5 text-sm",
308
+ "font-[Space_Grotesk,sans-serif]",
309
+ isUser ? "bg-[#8C34E9]/15 text-[#E2E8F0] rounded-tr-sm" : "bg-[#1E2738] text-[#C4CDD9] rounded-tl-sm"
310
+ ),
311
+ children: [
312
+ renderContent ? renderContent(message.content, message.isStreaming) : /* @__PURE__ */ jsx4("p", { className: "whitespace-pre-wrap break-words", children: message.content }),
313
+ message.isStreaming && /* @__PURE__ */ jsx4("span", { className: "inline-block w-1.5 h-4 bg-[#8C34E9] animate-pulse ml-0.5 align-text-bottom rounded-sm" })
314
+ ]
315
+ }
316
+ )
317
+ ]
318
+ }
319
+ );
320
+ }
321
+ function AIChatBox({
322
+ messages,
323
+ onSend,
324
+ isLoading = false,
325
+ placeholder = "Type a message...",
326
+ title = "AI Assistant",
327
+ floating = false,
328
+ isOpen = true,
329
+ onToggle,
330
+ renderContent,
331
+ maxHeight = "400px",
332
+ className,
333
+ showHeader = true
334
+ }) {
335
+ const [input, setInput] = useState("");
336
+ const messagesEndRef = useRef2(null);
337
+ const inputRef = useRef2(null);
338
+ useEffect2(() => {
339
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
340
+ }, [messages]);
341
+ const handleSend = useCallback2(() => {
342
+ const trimmed = input.trim();
343
+ if (!trimmed || isLoading) return;
344
+ onSend(trimmed);
345
+ setInput("");
346
+ if (inputRef.current) {
347
+ inputRef.current.style.height = "auto";
348
+ }
349
+ }, [input, isLoading, onSend]);
350
+ const handleKeyDown = useCallback2(
351
+ (e) => {
352
+ if (e.key === "Enter" && !e.shiftKey) {
353
+ e.preventDefault();
354
+ handleSend();
355
+ }
356
+ },
357
+ [handleSend]
358
+ );
359
+ const handleInput = useCallback2((e) => {
360
+ setInput(e.target.value);
361
+ const el = e.target;
362
+ el.style.height = "auto";
363
+ el.style.height = `${Math.min(el.scrollHeight, 120)}px`;
364
+ }, []);
365
+ if (floating && !isOpen) {
366
+ return /* @__PURE__ */ jsx4(
367
+ "button",
368
+ {
369
+ onClick: onToggle,
370
+ className: cn(
371
+ "fixed bottom-6 right-6 z-50",
372
+ "w-12 h-12 rounded-full shadow-lg",
373
+ "bg-[#8C34E9] text-white",
374
+ "hover:bg-[#7928d4] transition-colors cursor-pointer",
375
+ "flex items-center justify-center",
376
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2"
377
+ ),
378
+ title: "Open chat",
379
+ children: /* @__PURE__ */ jsx4(Bot, { className: "h-5 w-5" })
380
+ }
381
+ );
382
+ }
383
+ const chatContent = /* @__PURE__ */ jsxs4(
384
+ "div",
385
+ {
386
+ className: cn(
387
+ "flex flex-col bg-[#0F1525] border border-[#1E2738] rounded-xl overflow-hidden",
388
+ floating && "fixed bottom-6 right-6 z-50 w-[380px] shadow-2xl",
389
+ className
390
+ ),
391
+ children: [
392
+ showHeader && /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between px-4 py-3 border-b border-[#1E2738] bg-[#0D1220]", children: [
393
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
394
+ /* @__PURE__ */ jsx4(Bot, { className: "h-4 w-4 text-[#14b8a6]" }),
395
+ /* @__PURE__ */ jsx4("span", { className: "text-sm font-semibold text-[#E2E8F0] font-[Montserrat,sans-serif]", children: title })
396
+ ] }),
397
+ floating && onToggle && /* @__PURE__ */ jsx4(
398
+ "button",
399
+ {
400
+ onClick: onToggle,
401
+ className: "p-1 rounded-md text-[#596475] hover:text-[#E2E8F0] hover:bg-[#1E2738] transition-colors",
402
+ children: /* @__PURE__ */ jsx4(X2, { className: "h-4 w-4" })
403
+ }
404
+ )
405
+ ] }),
406
+ /* @__PURE__ */ jsxs4(
407
+ "div",
408
+ {
409
+ className: "flex-1 overflow-y-auto py-2",
410
+ style: { maxHeight },
411
+ children: [
412
+ messages.length === 0 && /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-center justify-center h-full py-8 text-center", children: [
413
+ /* @__PURE__ */ jsx4(Bot, { className: "h-8 w-8 text-[#596475] mb-3" }),
414
+ /* @__PURE__ */ jsx4("p", { className: "text-sm text-[#596475] font-[Space_Grotesk,sans-serif]", children: "Start a conversation" })
415
+ ] }),
416
+ messages.map((msg) => /* @__PURE__ */ jsx4(
417
+ MessageBubble,
418
+ {
419
+ message: msg,
420
+ renderContent
421
+ },
422
+ msg.id
423
+ )),
424
+ isLoading && messages[messages.length - 1]?.role !== "assistant" && /* @__PURE__ */ jsxs4("div", { className: "flex gap-3 px-4 py-3", children: [
425
+ /* @__PURE__ */ jsx4("div", { className: "shrink-0 w-7 h-7 rounded-full bg-[#14b8a6]/20 flex items-center justify-center", children: /* @__PURE__ */ jsx4(Bot, { className: "h-3.5 w-3.5 text-[#14b8a6]" }) }),
426
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5 px-3.5 py-2.5 rounded-xl bg-[#1E2738] rounded-tl-sm", children: [
427
+ /* @__PURE__ */ jsx4(Loader2, { className: "h-3.5 w-3.5 text-[#596475] animate-spin" }),
428
+ /* @__PURE__ */ jsx4("span", { className: "text-xs text-[#596475]", children: "Thinking..." })
429
+ ] })
430
+ ] }),
431
+ /* @__PURE__ */ jsx4("div", { ref: messagesEndRef })
432
+ ]
433
+ }
434
+ ),
435
+ /* @__PURE__ */ jsx4("div", { className: "border-t border-[#1E2738] p-3", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-end gap-2", children: [
436
+ /* @__PURE__ */ jsx4(
437
+ "textarea",
438
+ {
439
+ ref: inputRef,
440
+ value: input,
441
+ onChange: handleInput,
442
+ onKeyDown: handleKeyDown,
443
+ placeholder,
444
+ rows: 1,
445
+ className: cn(
446
+ "flex-1 resize-none rounded-lg px-3 py-2",
447
+ "bg-[#0A0E1A] border border-[#1E2738] text-sm text-[#E2E8F0]",
448
+ "placeholder:text-[#596475]",
449
+ "focus:outline-none focus:border-[#8C34E9]",
450
+ "font-[Space_Grotesk,sans-serif]",
451
+ "transition-colors"
452
+ ),
453
+ disabled: isLoading
454
+ }
455
+ ),
456
+ /* @__PURE__ */ jsx4(
457
+ "button",
458
+ {
459
+ onClick: handleSend,
460
+ disabled: !input.trim() || isLoading,
461
+ className: cn(
462
+ "shrink-0 p-2.5 rounded-lg",
463
+ "bg-[#8C34E9] text-white",
464
+ "hover:bg-[#7928d4] transition-colors cursor-pointer",
465
+ "disabled:opacity-40 disabled:cursor-not-allowed",
466
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]"
467
+ ),
468
+ children: isLoading ? /* @__PURE__ */ jsx4(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx4(Send, { className: "h-4 w-4" })
469
+ }
470
+ )
471
+ ] }) })
472
+ ]
473
+ }
474
+ );
475
+ return chatContent;
476
+ }
477
+
478
+ // src/components/MapView.tsx
479
+ import { useEffect as useEffect3, useRef as useRef3, useCallback as useCallback3 } from "react";
480
+ import { jsx as jsx5 } from "react/jsx-runtime";
481
+ var loadPromise = null;
482
+ function loadMapScript(apiKey, forgeBaseUrl) {
483
+ if (loadPromise) return loadPromise;
484
+ if (typeof window !== "undefined" && window.google?.maps) {
485
+ return Promise.resolve();
486
+ }
487
+ const proxyUrl = `${forgeBaseUrl}/v1/maps/proxy`;
488
+ loadPromise = new Promise((resolve, reject) => {
489
+ const script = document.createElement("script");
490
+ script.src = `${proxyUrl}/maps/api/js?key=${apiKey}&v=weekly&libraries=marker,places,geocoding,geometry`;
491
+ script.async = true;
492
+ script.crossOrigin = "anonymous";
493
+ script.onload = () => {
494
+ resolve();
495
+ script.remove();
496
+ };
497
+ script.onerror = () => {
498
+ loadPromise = null;
499
+ reject(new Error("Failed to load Google Maps script"));
500
+ };
501
+ document.head.appendChild(script);
502
+ });
503
+ return loadPromise;
504
+ }
505
+ function MapView({
506
+ apiKey,
507
+ forgeBaseUrl = "https://forge.butterfly-effect.dev",
508
+ initialCenter = { lat: 37.7749, lng: -122.4194 },
509
+ initialZoom = 12,
510
+ onMapReady,
511
+ mapId = "DEMO_MAP_ID",
512
+ className
513
+ }) {
514
+ const mapContainer = useRef3(null);
515
+ const mapInstance = useRef3(null);
516
+ const onMapReadyRef = useRef3(onMapReady);
517
+ onMapReadyRef.current = onMapReady;
518
+ const init = useCallback3(async () => {
519
+ try {
520
+ await loadMapScript(apiKey, forgeBaseUrl);
521
+ } catch (err) {
522
+ console.error("[MapView] Failed to load Google Maps:", err);
523
+ return;
524
+ }
525
+ if (!mapContainer.current || !window.google) return;
526
+ mapInstance.current = new window.google.maps.Map(mapContainer.current, {
527
+ zoom: initialZoom,
528
+ center: initialCenter,
529
+ mapTypeControl: true,
530
+ fullscreenControl: true,
531
+ zoomControl: true,
532
+ streetViewControl: true,
533
+ mapId
534
+ });
535
+ onMapReadyRef.current?.(mapInstance.current);
536
+ }, [apiKey, forgeBaseUrl, initialZoom, initialCenter, mapId]);
537
+ useEffect3(() => {
538
+ init();
539
+ }, [init]);
540
+ return /* @__PURE__ */ jsx5("div", { ref: mapContainer, className: cn("w-full h-[500px]", className) });
541
+ }
542
+
543
+ // src/components/ViewOnlyBadge.tsx
544
+ import { Eye } from "lucide-react";
545
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
546
+ function ViewOnlyBadge({
547
+ label = "View Only",
548
+ size = "sm",
549
+ className
550
+ }) {
551
+ return /* @__PURE__ */ jsxs5(
552
+ "span",
553
+ {
554
+ className: cn(
555
+ "inline-flex items-center gap-1 rounded-full font-medium",
556
+ "bg-[#f59e0b]/10 text-[#f59e0b] border border-[#f59e0b]/20",
557
+ size === "sm" ? "px-2 py-0.5 text-[10px]" : "px-3 py-1 text-xs",
558
+ className
559
+ ),
560
+ children: [
561
+ /* @__PURE__ */ jsx6(Eye, { className: cn(size === "sm" ? "h-3 w-3" : "h-3.5 w-3.5") }),
562
+ label
563
+ ]
564
+ }
565
+ );
566
+ }
567
+ export {
568
+ AIChatBox,
569
+ ErrorBoundary,
570
+ ManusDialog,
571
+ MapView,
572
+ NotFoundPage,
573
+ ViewOnlyBadge
574
+ };
575
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/ErrorBoundary.tsx","../../src/components/NotFound.tsx","../../src/components/ManusDialog.tsx","../../src/components/AIChatBox.tsx","../../src/components/MapView.tsx","../../src/components/ViewOnlyBadge.tsx"],"sourcesContent":["/**\n * ErrorBoundary — standard error boundary for all Oplytics subdomains.\n *\n * Catches render errors and displays a fallback UI with Oplytics branding.\n * Optionally calls an onError callback for error reporting.\n */\nimport { Component, type ReactNode, type ErrorInfo } from \"react\";\nimport { cn } from \"../utils/cn\";\nimport { AlertTriangle, RefreshCw } from \"lucide-react\";\n\nexport interface ErrorBoundaryProps {\n /** Fallback UI to show when an error is caught. If not provided, uses the default Oplytics error UI. */\n fallback?: ReactNode;\n /** Error handler callback — use for logging/reporting */\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\n /** Children to wrap */\n children: ReactNode;\n /** CSS class for the fallback container */\n className?: string;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n console.error(\"[ErrorBoundary] Caught error:\", error, errorInfo);\n this.props.onError?.(error, errorInfo);\n }\n\n handleRetry = () => {\n this.setState({ hasError: false, error: null });\n };\n\n render() {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center min-h-[400px] p-8\",\n \"bg-[#0A0E1A] text-[#E2E8F0]\",\n this.props.className\n )}\n >\n <div className=\"flex flex-col items-center gap-4 max-w-md text-center\">\n <div className=\"w-14 h-14 rounded-full bg-[#ef4444]/10 flex items-center justify-center\">\n <AlertTriangle className=\"h-7 w-7 text-[#ef4444]\" />\n </div>\n <h2 className=\"text-lg font-semibold font-[Montserrat,sans-serif]\">\n Something went wrong\n </h2>\n <p className=\"text-sm text-[#8890A0] font-[Space_Grotesk,sans-serif]\">\n An unexpected error occurred. Please try again or contact support if the problem persists.\n </p>\n {this.state.error && (\n <pre className=\"mt-2 p-3 rounded-lg bg-[#0D1220] border border-[#1E2738] text-xs text-[#596475] max-w-full overflow-x-auto font-[JetBrains_Mono,monospace]\">\n {this.state.error.message}\n </pre>\n )}\n <button\n onClick={this.handleRetry}\n className={cn(\n \"mt-2 flex items-center gap-2 px-4 py-2 rounded-lg\",\n \"bg-[#8C34E9] text-white text-sm font-medium\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]\"\n )}\n >\n <RefreshCw className=\"h-4 w-4\" />\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","/**\n * NotFound — standard 404 page for all Oplytics subdomains.\n *\n * Displays a branded \"Page Not Found\" message with navigation options.\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\nimport { Home, ArrowLeft } from \"lucide-react\";\n\nexport interface NotFoundPageProps {\n /** Title text (default: \"Page Not Found\") */\n title?: string;\n /** Description text */\n description?: string;\n /** Home path to navigate to (default: \"/\") */\n homePath?: string;\n /** Back button handler (default: window.history.back) */\n onBack?: () => void;\n /** Home button handler — if provided, overrides the <a> link */\n onHome?: () => void;\n /** CSS class for the container */\n className?: string;\n}\n\nexport function NotFoundPage({\n title = \"Page Not Found\",\n description = \"The page you're looking for doesn't exist or has been moved.\",\n homePath = \"/\",\n onBack,\n onHome,\n className,\n}: NotFoundPageProps) {\n const handleBack = onBack || (() => {\n if (typeof window !== \"undefined\") {\n window.history.back();\n }\n });\n\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center min-h-screen p-8\",\n \"bg-[#0A0E1A] text-[#E2E8F0]\",\n className\n )}\n >\n <div className=\"flex flex-col items-center gap-6 max-w-md text-center\">\n {/* 404 number */}\n <div className=\"relative\">\n <span\n className=\"text-[120px] font-bold leading-none font-[Montserrat,sans-serif]\"\n style={{\n background: \"linear-gradient(135deg, #8C34E9 0%, #14b8a6 100%)\",\n WebkitBackgroundClip: \"text\",\n WebkitTextFillColor: \"transparent\",\n opacity: 0.15,\n }}\n >\n 404\n </span>\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div\n className=\"w-16 h-16 rounded-full flex items-center justify-center\"\n style={{ background: \"rgba(140, 52, 233, 0.1)\", border: \"1px solid rgba(140, 52, 233, 0.2)\" }}\n >\n <span className=\"text-2xl font-bold text-[#8C34E9] font-[Montserrat,sans-serif]\">?</span>\n </div>\n </div>\n </div>\n\n {/* Title */}\n <h1 className=\"text-2xl font-bold font-[Montserrat,sans-serif] text-[#E2E8F0]\">\n {title}\n </h1>\n\n {/* Description */}\n <p className=\"text-sm text-[#8890A0] font-[Space_Grotesk,sans-serif] leading-relaxed\">\n {description}\n </p>\n\n {/* Action buttons */}\n <div className=\"flex flex-col sm:flex-row gap-3 mt-2\">\n <button\n onClick={handleBack}\n className={cn(\n \"flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg\",\n \"border border-[#1E2738] text-[#8890A0] text-sm font-medium\",\n \"hover:bg-[#1E2738] hover:text-[#E2E8F0] transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Go Back\n </button>\n\n {onHome ? (\n <button\n onClick={onHome}\n className={cn(\n \"flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg\",\n \"bg-[#8C34E9] text-white text-sm font-medium\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]\"\n )}\n >\n <Home className=\"h-4 w-4\" />\n Go Home\n </button>\n ) : (\n <a\n href={homePath}\n className={cn(\n \"flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg\",\n \"bg-[#8C34E9] text-white text-sm font-medium no-underline\",\n \"hover:bg-[#7928d4] transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]\"\n )}\n >\n <Home className=\"h-4 w-4\" />\n Go Home\n </a>\n )}\n </div>\n </div>\n </div>\n );\n}\n","/**\n * ManusDialog — standard dialog/modal wrapper for Oplytics subdomains.\n *\n * Provides a consistent modal overlay with Oplytics branding.\n * Uses native HTML <dialog> element for accessibility.\n *\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\nimport { X } from \"lucide-react\";\nimport { useEffect, useRef, useCallback, type ReactNode } from \"react\";\n\nexport interface ManusDialogProps {\n /** Whether the dialog is open */\n open: boolean;\n /** Close handler */\n onClose: () => void;\n /** Dialog title */\n title: string;\n /** Optional subtitle */\n subtitle?: string;\n /** Optional logo/icon to show in the header */\n logo?: string | ReactNode;\n /** Dialog content */\n children: ReactNode;\n /** Footer content (e.g. action buttons) */\n footer?: ReactNode;\n /** Max width class (default: \"max-w-md\") */\n maxWidth?: string;\n /** CSS class for the dialog panel */\n className?: string;\n /** Whether clicking the overlay closes the dialog (default: true) */\n closeOnOverlay?: boolean;\n}\n\nexport function ManusDialog({\n open,\n onClose,\n title,\n subtitle,\n logo,\n children,\n footer,\n maxWidth = \"max-w-md\",\n className,\n closeOnOverlay = true,\n}: ManusDialogProps) {\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (open && !dialog.open) {\n dialog.showModal();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n }, [open]);\n\n const handleBackdropClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (closeOnOverlay && e.target === dialogRef.current) {\n onClose();\n }\n },\n [closeOnOverlay, onClose]\n );\n\n const handleCancel = useCallback(\n (e: React.SyntheticEvent) => {\n e.preventDefault();\n onClose();\n },\n [onClose]\n );\n\n return (\n <dialog\n ref={dialogRef}\n onClick={handleBackdropClick}\n onCancel={handleCancel}\n className={cn(\n \"backdrop:bg-black/60 backdrop:backdrop-blur-sm\",\n \"bg-transparent p-0 m-auto\",\n \"open:animate-in open:fade-in-0 open:zoom-in-95\",\n maxWidth,\n \"w-full\"\n )}\n >\n <div\n className={cn(\n \"bg-[#0F1525] border border-[#1E2738] rounded-xl shadow-xl\",\n \"text-[#E2E8F0] font-[Space_Grotesk,sans-serif]\",\n className\n )}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between p-5 border-b border-[#1E2738]\">\n <div className=\"flex items-center gap-3\">\n {logo && (\n <div className=\"shrink-0\">\n {typeof logo === \"string\" ? (\n <img\n src={logo}\n alt=\"Dialog graphic\"\n className=\"w-10 h-10 rounded-md\"\n />\n ) : (\n logo\n )}\n </div>\n )}\n <div>\n <h2 className=\"text-base font-semibold font-[Montserrat,sans-serif]\">\n {title}\n </h2>\n {subtitle && (\n <p className=\"text-xs text-[#8890A0] mt-0.5\">{subtitle}</p>\n )}\n </div>\n </div>\n <button\n onClick={onClose}\n className={cn(\n \"shrink-0 p-1.5 rounded-md -mt-1 -mr-1\",\n \"text-[#596475] hover:text-[#E2E8F0] hover:bg-[#1E2738]\",\n \"transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"p-5\">{children}</div>\n\n {/* Footer */}\n {footer && (\n <div className=\"px-5 pb-5 pt-2 flex items-center justify-end gap-2\">\n {footer}\n </div>\n )}\n </div>\n </dialog>\n );\n}\n","/**\n * AIChatBox — full-featured chat interface for Oplytics subdomains.\n *\n * Provides a chat UI with message history, input area, and streaming support.\n * The actual LLM integration is handled by the consuming app via callbacks.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent\n */\nimport { cn } from \"../utils/cn\";\nimport { Send, Bot, User, Loader2, X, Minimize2, Maximize2 } from \"lucide-react\";\nimport {\n useState,\n useRef,\n useCallback,\n useEffect,\n type ReactNode,\n type KeyboardEvent,\n} from \"react\";\n\n/* ── Message types ── */\n\nexport interface ChatMessage {\n id: string;\n role: \"user\" | \"assistant\" | \"system\";\n content: string;\n timestamp: number;\n /** Whether the message is still streaming */\n isStreaming?: boolean;\n}\n\n/* ── Component props ── */\n\nexport interface AIChatBoxProps {\n /** Chat messages to display */\n messages: ChatMessage[];\n /** Handler when user sends a message */\n onSend: (message: string) => void;\n /** Whether the assistant is currently generating a response */\n isLoading?: boolean;\n /** Placeholder text for the input */\n placeholder?: string;\n /** Title for the chat header */\n title?: string;\n /** Whether to show the chat in a floating panel (default: false — inline) */\n floating?: boolean;\n /** Whether the floating panel is open */\n isOpen?: boolean;\n /** Toggle floating panel open/closed */\n onToggle?: () => void;\n /** Render prop for message content (e.g. to use Streamdown for markdown) */\n renderContent?: (content: string, isStreaming?: boolean) => ReactNode;\n /** Max height for the messages area (default: \"400px\") */\n maxHeight?: string;\n /** CSS class for the container */\n className?: string;\n /** Whether to show the header (default: true) */\n showHeader?: boolean;\n}\n\n/* ── Message bubble ── */\n\nfunction MessageBubble({\n message,\n renderContent,\n}: {\n message: ChatMessage;\n renderContent?: (content: string, isStreaming?: boolean) => ReactNode;\n}) {\n const isUser = message.role === \"user\";\n\n return (\n <div\n className={cn(\n \"flex gap-3 px-4 py-3\",\n isUser ? \"flex-row-reverse\" : \"flex-row\"\n )}\n >\n {/* Avatar */}\n <div\n className={cn(\n \"shrink-0 w-7 h-7 rounded-full flex items-center justify-center\",\n isUser ? \"bg-[#8C34E9]/20\" : \"bg-[#14b8a6]/20\"\n )}\n >\n {isUser ? (\n <User className=\"h-3.5 w-3.5 text-[#A855F7]\" />\n ) : (\n <Bot className=\"h-3.5 w-3.5 text-[#14b8a6]\" />\n )}\n </div>\n\n {/* Content */}\n <div\n className={cn(\n \"max-w-[80%] rounded-xl px-3.5 py-2.5 text-sm\",\n \"font-[Space_Grotesk,sans-serif]\",\n isUser\n ? \"bg-[#8C34E9]/15 text-[#E2E8F0] rounded-tr-sm\"\n : \"bg-[#1E2738] text-[#C4CDD9] rounded-tl-sm\"\n )}\n >\n {renderContent ? (\n renderContent(message.content, message.isStreaming)\n ) : (\n <p className=\"whitespace-pre-wrap break-words\">{message.content}</p>\n )}\n {message.isStreaming && (\n <span className=\"inline-block w-1.5 h-4 bg-[#8C34E9] animate-pulse ml-0.5 align-text-bottom rounded-sm\" />\n )}\n </div>\n </div>\n );\n}\n\n/* ── Main component ── */\n\nexport function AIChatBox({\n messages,\n onSend,\n isLoading = false,\n placeholder = \"Type a message...\",\n title = \"AI Assistant\",\n floating = false,\n isOpen = true,\n onToggle,\n renderContent,\n maxHeight = \"400px\",\n className,\n showHeader = true,\n}: AIChatBoxProps) {\n const [input, setInput] = useState(\"\");\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n // Auto-scroll to bottom\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages]);\n\n const handleSend = useCallback(() => {\n const trimmed = input.trim();\n if (!trimmed || isLoading) return;\n onSend(trimmed);\n setInput(\"\");\n // Reset textarea height\n if (inputRef.current) {\n inputRef.current.style.height = \"auto\";\n }\n }, [input, isLoading, onSend]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend]\n );\n\n // Auto-resize textarea\n const handleInput = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setInput(e.target.value);\n const el = e.target;\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, 120)}px`;\n }, []);\n\n // Floating mode: toggle button\n if (floating && !isOpen) {\n return (\n <button\n onClick={onToggle}\n className={cn(\n \"fixed bottom-6 right-6 z-50\",\n \"w-12 h-12 rounded-full shadow-lg\",\n \"bg-[#8C34E9] text-white\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"flex items-center justify-center\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2\"\n )}\n title=\"Open chat\"\n >\n <Bot className=\"h-5 w-5\" />\n </button>\n );\n }\n\n const chatContent = (\n <div\n className={cn(\n \"flex flex-col bg-[#0F1525] border border-[#1E2738] rounded-xl overflow-hidden\",\n floating && \"fixed bottom-6 right-6 z-50 w-[380px] shadow-2xl\",\n className\n )}\n >\n {/* Header */}\n {showHeader && (\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-[#1E2738] bg-[#0D1220]\">\n <div className=\"flex items-center gap-2\">\n <Bot className=\"h-4 w-4 text-[#14b8a6]\" />\n <span className=\"text-sm font-semibold text-[#E2E8F0] font-[Montserrat,sans-serif]\">\n {title}\n </span>\n </div>\n {floating && onToggle && (\n <button\n onClick={onToggle}\n className=\"p-1 rounded-md text-[#596475] hover:text-[#E2E8F0] hover:bg-[#1E2738] transition-colors\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n )}\n\n {/* Messages */}\n <div\n className=\"flex-1 overflow-y-auto py-2\"\n style={{ maxHeight }}\n >\n {messages.length === 0 && (\n <div className=\"flex flex-col items-center justify-center h-full py-8 text-center\">\n <Bot className=\"h-8 w-8 text-[#596475] mb-3\" />\n <p className=\"text-sm text-[#596475] font-[Space_Grotesk,sans-serif]\">\n Start a conversation\n </p>\n </div>\n )}\n {messages.map((msg) => (\n <MessageBubble\n key={msg.id}\n message={msg}\n renderContent={renderContent}\n />\n ))}\n {isLoading && messages[messages.length - 1]?.role !== \"assistant\" && (\n <div className=\"flex gap-3 px-4 py-3\">\n <div className=\"shrink-0 w-7 h-7 rounded-full bg-[#14b8a6]/20 flex items-center justify-center\">\n <Bot className=\"h-3.5 w-3.5 text-[#14b8a6]\" />\n </div>\n <div className=\"flex items-center gap-1.5 px-3.5 py-2.5 rounded-xl bg-[#1E2738] rounded-tl-sm\">\n <Loader2 className=\"h-3.5 w-3.5 text-[#596475] animate-spin\" />\n <span className=\"text-xs text-[#596475]\">Thinking...</span>\n </div>\n </div>\n )}\n <div ref={messagesEndRef} />\n </div>\n\n {/* Input area */}\n <div className=\"border-t border-[#1E2738] p-3\">\n <div className=\"flex items-end gap-2\">\n <textarea\n ref={inputRef}\n value={input}\n onChange={handleInput}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n rows={1}\n className={cn(\n \"flex-1 resize-none rounded-lg px-3 py-2\",\n \"bg-[#0A0E1A] border border-[#1E2738] text-sm text-[#E2E8F0]\",\n \"placeholder:text-[#596475]\",\n \"focus:outline-none focus:border-[#8C34E9]\",\n \"font-[Space_Grotesk,sans-serif]\",\n \"transition-colors\"\n )}\n disabled={isLoading}\n />\n <button\n onClick={handleSend}\n disabled={!input.trim() || isLoading}\n className={cn(\n \"shrink-0 p-2.5 rounded-lg\",\n \"bg-[#8C34E9] text-white\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"disabled:opacity-40 disabled:cursor-not-allowed\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n >\n {isLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Send className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n </div>\n </div>\n );\n\n return chatContent;\n}\n","/**\n * MapView — Google Maps integration with Manus proxy authentication.\n *\n * Provides a MapView component with onMapReady callback for initialising\n * Google Maps services (Places, Geocoder, Directions, Drawing, etc.).\n * All map functionality works directly in the browser via the Manus proxy.\n *\n * Usage:\n * <MapView\n * apiKey={import.meta.env.VITE_FRONTEND_FORGE_API_KEY}\n * forgeBaseUrl={import.meta.env.VITE_FRONTEND_FORGE_API_URL}\n * initialCenter={{ lat: 40.7128, lng: -74.0060 }}\n * initialZoom={15}\n * onMapReady={(map) => { mapRef.current = map; }}\n * />\n */\n\n/// <reference types=\"@types/google.maps\" />\n\nimport { useEffect, useRef, useCallback } from \"react\";\nimport { cn } from \"../utils/cn\";\n\ndeclare global {\n interface Window {\n google?: typeof google;\n }\n}\n\n/* ── Script loader ── */\n\nlet loadPromise: Promise<void> | null = null;\n\nfunction loadMapScript(apiKey: string, forgeBaseUrl: string): Promise<void> {\n if (loadPromise) return loadPromise;\n if (typeof window !== \"undefined\" && window.google?.maps) {\n return Promise.resolve();\n }\n\n const proxyUrl = `${forgeBaseUrl}/v1/maps/proxy`;\n\n loadPromise = new Promise((resolve, reject) => {\n const script = document.createElement(\"script\");\n script.src = `${proxyUrl}/maps/api/js?key=${apiKey}&v=weekly&libraries=marker,places,geocoding,geometry`;\n script.async = true;\n script.crossOrigin = \"anonymous\";\n script.onload = () => {\n resolve();\n script.remove();\n };\n script.onerror = () => {\n loadPromise = null;\n reject(new Error(\"Failed to load Google Maps script\"));\n };\n document.head.appendChild(script);\n });\n\n return loadPromise;\n}\n\n/* ── Component ── */\n\nexport interface MapViewProps {\n /** Manus Forge API key (VITE_FRONTEND_FORGE_API_KEY) */\n apiKey: string;\n /** Manus Forge base URL (VITE_FRONTEND_FORGE_API_URL) */\n forgeBaseUrl?: string;\n /** Initial map center */\n initialCenter?: google.maps.LatLngLiteral;\n /** Initial zoom level */\n initialZoom?: number;\n /** Callback when the map is ready — use to store ref and initialise services */\n onMapReady?: (map: google.maps.Map) => void;\n /** Map ID for cloud-based map styling */\n mapId?: string;\n /** CSS class for the map container */\n className?: string;\n}\n\nexport function MapView({\n apiKey,\n forgeBaseUrl = \"https://forge.butterfly-effect.dev\",\n initialCenter = { lat: 37.7749, lng: -122.4194 },\n initialZoom = 12,\n onMapReady,\n mapId = \"DEMO_MAP_ID\",\n className,\n}: MapViewProps) {\n const mapContainer = useRef<HTMLDivElement>(null);\n const mapInstance = useRef<google.maps.Map | null>(null);\n const onMapReadyRef = useRef(onMapReady);\n onMapReadyRef.current = onMapReady;\n\n const init = useCallback(async () => {\n try {\n await loadMapScript(apiKey, forgeBaseUrl);\n } catch (err) {\n console.error(\"[MapView] Failed to load Google Maps:\", err);\n return;\n }\n\n if (!mapContainer.current || !window.google) return;\n\n mapInstance.current = new window.google.maps.Map(mapContainer.current, {\n zoom: initialZoom,\n center: initialCenter,\n mapTypeControl: true,\n fullscreenControl: true,\n zoomControl: true,\n streetViewControl: true,\n mapId,\n });\n\n onMapReadyRef.current?.(mapInstance.current);\n }, [apiKey, forgeBaseUrl, initialZoom, initialCenter, mapId]);\n\n useEffect(() => {\n init();\n }, [init]);\n\n return (\n <div ref={mapContainer} className={cn(\"w-full h-[500px]\", className)} />\n );\n}\n","/**\n * ViewOnlyBadge — displays a \"View Only\" indicator for read-only users.\n *\n * Used across subdomains to indicate when a user has view-only access\n * (e.g. Viewer role in the RBAC hierarchy).\n *\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\nimport { Eye } from \"lucide-react\";\n\nexport interface ViewOnlyBadgeProps {\n /** Custom label (default: \"View Only\") */\n label?: string;\n /** Size variant */\n size?: \"sm\" | \"md\";\n /** CSS class */\n className?: string;\n}\n\nexport function ViewOnlyBadge({\n label = \"View Only\",\n size = \"sm\",\n className,\n}: ViewOnlyBadgeProps) {\n return (\n <span\n className={cn(\n \"inline-flex items-center gap-1 rounded-full font-medium\",\n \"bg-[#f59e0b]/10 text-[#f59e0b] border border-[#f59e0b]/20\",\n size === \"sm\" ? \"px-2 py-0.5 text-[10px]\" : \"px-3 py-1 text-xs\",\n className\n )}\n >\n <Eye className={cn(size === \"sm\" ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n {label}\n </span>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","X","useRef","useCallback","useEffect","jsx","jsxs","useEffect","useRef","useCallback","jsx","useRef","useCallback","useEffect","jsx","jsxs"]}