@djangocfg/layouts 2.1.102 → 2.1.104

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.
Files changed (94) hide show
  1. package/package.json +33 -37
  2. package/src/components/RedirectPage/RedirectPage.tsx +2 -2
  3. package/src/components/core/ClientOnly.tsx +1 -1
  4. package/src/components/errors/ErrorLayout.tsx +1 -1
  5. package/src/components/errors/ErrorsTracker/components/ErrorButtons.tsx +1 -1
  6. package/src/components/index.ts +2 -0
  7. package/src/index.ts +2 -0
  8. package/src/layouts/AuthLayout/components/AuthHelp.tsx +1 -1
  9. package/src/layouts/AuthLayout/components/AuthSuccess.tsx +1 -1
  10. package/src/layouts/AuthLayout/components/IdentifierForm.tsx +1 -1
  11. package/src/layouts/AuthLayout/components/OTPForm.tsx +1 -1
  12. package/src/layouts/AuthLayout/components/TwoFactorForm.tsx +1 -1
  13. package/src/layouts/AuthLayout/components/TwoFactorSetup.tsx +1 -1
  14. package/src/layouts/AuthLayout/components/oauth/OAuthCallback.tsx +1 -1
  15. package/src/layouts/AuthLayout/components/oauth/OAuthProviders.tsx +1 -1
  16. package/src/layouts/PrivateLayout/PrivateLayout.tsx +3 -2
  17. package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +2 -2
  18. package/src/layouts/ProfileLayout/ProfileLayout.tsx +1 -1
  19. package/src/layouts/ProfileLayout/__tests__/TwoFactorSection.test.tsx +1 -1
  20. package/src/layouts/ProfileLayout/components/AvatarSection.tsx +1 -1
  21. package/src/layouts/ProfileLayout/components/DeleteAccountSection.tsx +1 -1
  22. package/src/layouts/ProfileLayout/components/ProfileForm.tsx +1 -1
  23. package/src/layouts/ProfileLayout/components/TwoFactorSection.tsx +1 -1
  24. package/src/layouts/PublicLayout/components/PublicFooter/PublicFooter.tsx +1 -1
  25. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
  26. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +2 -2
  27. package/src/layouts/_components/UserMenu.tsx +1 -1
  28. package/src/layouts/index.ts +2 -0
  29. package/src/pages/index.ts +2 -0
  30. package/src/pages/legal/LegalPage.tsx +1 -1
  31. package/src/snippets/AuthDialog/AuthDialog.tsx +3 -2
  32. package/src/snippets/McpChat/components/AIChatWidget.tsx +1 -1
  33. package/src/snippets/McpChat/components/AskAIButton.tsx +1 -1
  34. package/src/snippets/McpChat/components/ChatMessages.tsx +1 -1
  35. package/src/snippets/McpChat/components/ChatPanel.tsx +1 -1
  36. package/src/snippets/McpChat/components/ChatSidebar.tsx +1 -1
  37. package/src/snippets/McpChat/components/ChatWidget.tsx +1 -1
  38. package/src/snippets/McpChat/components/MessageBubble.tsx +1 -1
  39. package/src/snippets/McpChat/components/MessageInput.tsx +1 -1
  40. package/src/snippets/McpChat/context/AIChatContext.tsx +1 -1
  41. package/src/snippets/McpChat/context/ChatContext.tsx +1 -1
  42. package/src/snippets/McpChat/hooks/useChatLayout.ts +1 -1
  43. package/src/snippets/PWAInstall/components/A2HSHint.tsx +0 -1
  44. package/src/snippets/PWAInstall/components/DesktopGuide.tsx +1 -1
  45. package/src/snippets/PWAInstall/components/IOSGuide.tsx +1 -1
  46. package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +1 -1
  47. package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +1 -1
  48. package/src/snippets/PWAInstall/hooks/useInstallPrompt.ts +2 -2
  49. package/src/snippets/PushNotifications/components/PushPrompt.tsx +1 -1
  50. package/src/snippets/index.ts +1 -0
  51. package/dist/AIChatWidget-LUPM7S2O.mjs +0 -1644
  52. package/dist/AIChatWidget-LUPM7S2O.mjs.map +0 -1
  53. package/dist/AIChatWidget-O23TJJ7C.mjs +0 -3
  54. package/dist/AIChatWidget-O23TJJ7C.mjs.map +0 -1
  55. package/dist/chunk-53YKWR6F.mjs +0 -6
  56. package/dist/chunk-53YKWR6F.mjs.map +0 -1
  57. package/dist/chunk-EI7TDN2G.mjs +0 -1652
  58. package/dist/chunk-EI7TDN2G.mjs.map +0 -1
  59. package/dist/components.cjs +0 -925
  60. package/dist/components.cjs.map +0 -1
  61. package/dist/components.d.mts +0 -583
  62. package/dist/components.d.ts +0 -583
  63. package/dist/components.mjs +0 -879
  64. package/dist/components.mjs.map +0 -1
  65. package/dist/index.cjs +0 -7573
  66. package/dist/index.cjs.map +0 -1
  67. package/dist/index.d.mts +0 -2376
  68. package/dist/index.d.ts +0 -2376
  69. package/dist/index.mjs +0 -5673
  70. package/dist/index.mjs.map +0 -1
  71. package/dist/layouts.cjs +0 -6530
  72. package/dist/layouts.cjs.map +0 -1
  73. package/dist/layouts.d.mts +0 -748
  74. package/dist/layouts.d.ts +0 -748
  75. package/dist/layouts.mjs +0 -4741
  76. package/dist/layouts.mjs.map +0 -1
  77. package/dist/pages.cjs +0 -178
  78. package/dist/pages.cjs.map +0 -1
  79. package/dist/pages.d.mts +0 -57
  80. package/dist/pages.d.ts +0 -57
  81. package/dist/pages.mjs +0 -168
  82. package/dist/pages.mjs.map +0 -1
  83. package/dist/snippets.cjs +0 -3793
  84. package/dist/snippets.cjs.map +0 -1
  85. package/dist/snippets.d.mts +0 -1192
  86. package/dist/snippets.d.ts +0 -1192
  87. package/dist/snippets.mjs +0 -3738
  88. package/dist/snippets.mjs.map +0 -1
  89. package/dist/utils.cjs +0 -34
  90. package/dist/utils.cjs.map +0 -1
  91. package/dist/utils.d.mts +0 -40
  92. package/dist/utils.d.ts +0 -40
  93. package/dist/utils.mjs +0 -25
  94. package/dist/utils.mjs.map +0 -1
package/dist/layouts.mjs DELETED
@@ -1,4741 +0,0 @@
1
- import { __name } from './chunk-53YKWR6F.mjs';
2
- import * as LucideIcons from 'lucide-react';
3
- import { LogOut, Copy, Terminal, Bell, X, ArrowDownToLine, Menu, Check, Monitor, Search, Plus, Share, ArrowUpRight, ArrowDown, CheckCircle, Mail, MessageSquare, Instagram, Facebook, Youtube, MessageCircle, Twitter, Linkedin, Github, LogIn, ChevronRight, Download, HelpCircle, Loader2, AlertCircle, User, Phone, Send, ShieldCheck, ArrowLeft, RotateCw, EyeOff, Eye, KeyRound, Camera, Building2, Briefcase, Shield, Trash2 } from 'lucide-react';
4
- import Link from 'next/link';
5
- import React, { createContext, useState, useEffect, Suspense as Suspense$1, Component, useCallback, useContext, useMemo, useRef } from 'react';
6
- import { useAuth, AuthProvider, useAuthForm, useGithubAuth, useTwoFactorSetup, useTwoFactorStatus, PatchedUserProfileUpdateRequestSchema, useDeleteAccount } from '@djangocfg/api/auth';
7
- import { Button, Avatar, AvatarImage, AvatarFallback, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, DropdownMenuItem, Drawer as Drawer$1, DrawerContent as DrawerContent$1, DrawerHeader as DrawerHeader$1, DrawerTitle as DrawerTitle$1, DrawerClose, useSidebar, Sidebar, SidebarHeader, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarMenuBadge, SidebarTrigger, Separator, Preloader, SidebarProvider, SidebarInset, Dialog as Dialog$1, DialogContent as DialogContent$1, DialogHeader as DialogHeader$1, DialogTitle as DialogTitle$1, Card as Card$1, CardHeader, CardTitle, CardDescription, CardContent as CardContent$1, Tabs, TabsList, TabsTrigger, TabsContent, Label, Input, PhoneInput, Checkbox, OTPInput, Alert, AlertDescription, CardFooter, DialogFooter as DialogFooter$1, DialogDescription as DialogDescription$1 } from '@djangocfg/ui-nextjs/components';
8
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
- import { usePathname, useRouter, useSearchParams } from 'next/navigation';
10
- import { cn } from '@djangocfg/ui-core/lib';
11
- import dynamic from 'next/dynamic';
12
- import NextTopLoader from 'nextjs-toploader';
13
- import { SWRConfig } from 'swr';
14
- import { getCentrifugoAuthTokenRetrieve } from '@djangocfg/api';
15
- import { CentrifugoProvider } from '@djangocfg/centrifugo';
16
- import { TooltipProvider, Toaster } from '@djangocfg/ui-core/components';
17
- import { ThemeProvider, ThemeToggle } from '@djangocfg/ui-nextjs/theme';
18
- import { useCopy, toast, events } from '@djangocfg/ui-core/hooks';
19
- import { Button as Button$1, useBrowserDetect, useDeviceDetect, Card, CardContent, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription } from '@djangocfg/ui-nextjs';
20
- import consola, { createConsola, consola as consola$1 } from 'consola';
21
- import ReactGA from 'react-ga4';
22
- import { useIsMobile, useCfgRouter, useEventListener } from '@djangocfg/ui-nextjs/hooks';
23
- import { apiWebPush } from '@djangocfg/api/clients';
24
- import { QRCodeSVG } from 'qrcode.react';
25
- import moment from 'moment';
26
- import { useForm } from 'react-hook-form';
27
- import { zodResolver } from '@hookform/resolvers/zod';
28
-
29
- function UserMenu({
30
- variant = "desktop",
31
- groups,
32
- authPath = "/auth"
33
- }) {
34
- const { user, isAuthenticated, logout } = useAuth();
35
- const [mounted, setMounted] = React.useState(false);
36
- React.useEffect(() => {
37
- setMounted(true);
38
- }, []);
39
- const menuGroups = React.useMemo(() => {
40
- const allGroups = [];
41
- if (groups && groups.length > 0) {
42
- allGroups.push(...groups);
43
- }
44
- allGroups.push({
45
- items: [
46
- {
47
- label: "Sign Out",
48
- onClick: /* @__PURE__ */ __name(() => logout(), "onClick"),
49
- icon: LogOut,
50
- variant: "destructive"
51
- }
52
- ]
53
- });
54
- return allGroups;
55
- }, [groups, logout]);
56
- if (!mounted) {
57
- return null;
58
- }
59
- if (!isAuthenticated || !user) {
60
- if (variant === "mobile") {
61
- return /* @__PURE__ */ jsx(Link, { href: authPath, children: /* @__PURE__ */ jsx(Button, { variant: "default", className: "w-full", children: "Sign In" }) });
62
- }
63
- return /* @__PURE__ */ jsx(Link, { href: authPath, children: /* @__PURE__ */ jsx(Button, { variant: "default", size: "sm", children: "Sign In" }) });
64
- }
65
- const displayName = user.display_username || user.email || "User";
66
- const userInitial = displayName.charAt(0).toUpperCase();
67
- const userAvatar = user.avatar || "";
68
- if (variant === "mobile") {
69
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
70
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
71
- /* @__PURE__ */ jsxs(Avatar, { className: "h-10 w-10", children: [
72
- /* @__PURE__ */ jsx(AvatarImage, { src: userAvatar, alt: displayName }),
73
- /* @__PURE__ */ jsx(AvatarFallback, { children: userInitial })
74
- ] }),
75
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
76
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground truncate", children: displayName }),
77
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground truncate", children: user.email })
78
- ] })
79
- ] }),
80
- /* @__PURE__ */ jsx("div", { className: "space-y-1", children: menuGroups.map((group, groupIndex) => /* @__PURE__ */ jsxs("div", { children: [
81
- group.title && /* @__PURE__ */ jsx("div", { className: "px-4 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: group.title }) }),
82
- group.items.map((item, itemIndex) => {
83
- const Icon = item.icon;
84
- const isDestructive = item.variant === "destructive";
85
- const baseClasses = `flex items-center gap-3 px-4 py-3 text-sm font-medium rounded-sm transition-colors w-full text-left ${isDestructive ? "text-destructive hover:bg-destructive/10" : "text-foreground hover:bg-accent hover:text-accent-foreground"}`;
86
- if (item.onClick) {
87
- return /* @__PURE__ */ jsxs(
88
- "button",
89
- {
90
- onClick: item.onClick,
91
- className: baseClasses,
92
- children: [
93
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
94
- item.label
95
- ]
96
- },
97
- itemIndex
98
- );
99
- }
100
- if (item.href) {
101
- return /* @__PURE__ */ jsxs(Link, { href: item.href, className: baseClasses, children: [
102
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
103
- item.label
104
- ] }, itemIndex);
105
- }
106
- return null;
107
- })
108
- ] }, groupIndex)) })
109
- ] });
110
- }
111
- return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
112
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "icon", className: "rounded-full", children: [
113
- /* @__PURE__ */ jsxs(Avatar, { className: "h-8 w-8", children: [
114
- /* @__PURE__ */ jsx(AvatarImage, { src: userAvatar, alt: displayName }),
115
- /* @__PURE__ */ jsx(AvatarFallback, { children: userInitial })
116
- ] }),
117
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "User menu" })
118
- ] }) }),
119
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
120
- /* @__PURE__ */ jsx(DropdownMenuLabel, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-1", children: [
121
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-none", children: displayName }),
122
- /* @__PURE__ */ jsx("p", { className: "text-xs leading-none text-muted-foreground", children: user.email })
123
- ] }) }),
124
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
125
- menuGroups.map((group, groupIndex) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
126
- groupIndex > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
127
- /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
128
- group.title && /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: group.title }),
129
- group.items.map((item, itemIndex) => {
130
- const Icon = item.icon;
131
- const isDestructive = item.variant === "destructive";
132
- if (item.onClick) {
133
- return /* @__PURE__ */ jsxs(
134
- DropdownMenuItem,
135
- {
136
- onClick: item.onClick,
137
- className: isDestructive ? "text-destructive focus:text-destructive" : "",
138
- children: [
139
- Icon && /* @__PURE__ */ jsx(Icon, { className: "mr-2 h-4 w-4" }),
140
- /* @__PURE__ */ jsx("span", { children: item.label })
141
- ]
142
- },
143
- itemIndex
144
- );
145
- }
146
- if (item.href) {
147
- return /* @__PURE__ */ jsx(DropdownMenuItem, { asChild: true, children: /* @__PURE__ */ jsxs(
148
- Link,
149
- {
150
- href: item.href,
151
- className: `flex items-center ${isDestructive ? "text-destructive" : ""}`,
152
- children: [
153
- Icon && /* @__PURE__ */ jsx(Icon, { className: "mr-2 h-4 w-4" }),
154
- /* @__PURE__ */ jsx("span", { children: item.label })
155
- ]
156
- }
157
- ) }, itemIndex);
158
- }
159
- return null;
160
- })
161
- ] })
162
- ] }, groupIndex))
163
- ] })
164
- ] });
165
- }
166
- __name(UserMenu, "UserMenu");
167
- var defaultFallback = /* @__PURE__ */ jsx(
168
- Preloader,
169
- {
170
- variant: "fullscreen",
171
- text: "Loading...",
172
- size: "lg",
173
- backdrop: true,
174
- backdropOpacity: 80
175
- }
176
- );
177
- function ClientOnly({
178
- children,
179
- fallback = defaultFallback
180
- }) {
181
- const [mounted, setMounted] = useState(false);
182
- useEffect(() => {
183
- setMounted(true);
184
- }, []);
185
- if (!mounted) {
186
- return /* @__PURE__ */ jsx(Fragment, { children: fallback });
187
- }
188
- return /* @__PURE__ */ jsx(Fragment, { children });
189
- }
190
- __name(ClientOnly, "ClientOnly");
191
- function getLucideIcon(icon, fallback = LucideIcons.CloudLightning) {
192
- if (!icon) {
193
- return fallback;
194
- }
195
- if (typeof icon === "string") {
196
- const IconComponent = LucideIcons[icon];
197
- return IconComponent || fallback;
198
- }
199
- return icon;
200
- }
201
- __name(getLucideIcon, "getLucideIcon");
202
- function LucideIcon({
203
- icon,
204
- fallback = LucideIcons.CloudLightning,
205
- className,
206
- ...props
207
- }) {
208
- const IconComponent = getLucideIcon(icon, fallback);
209
- return /* @__PURE__ */ jsx(IconComponent, { className: cn(className), ...props });
210
- }
211
- __name(LucideIcon, "LucideIcon");
212
- var defaultFallback2 = /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
213
- /* @__PURE__ */ jsx("div", { className: "inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]" }),
214
- /* @__PURE__ */ jsx("p", { className: "mt-4 text-sm text-muted-foreground", children: "Loading..." })
215
- ] }) });
216
- function Suspense({ children, fallback = defaultFallback2 }) {
217
- return /* @__PURE__ */ jsx(Suspense$1, { fallback, children });
218
- }
219
- __name(Suspense, "Suspense");
220
- var _ErrorBoundary = class _ErrorBoundary extends Component {
221
- constructor(props) {
222
- super(props);
223
- this.state = { hasError: false, error: null };
224
- }
225
- static getDerivedStateFromError(error) {
226
- return { hasError: true, error };
227
- }
228
- componentDidCatch(error, errorInfo) {
229
- if (this.props.onError) {
230
- this.props.onError(error, errorInfo);
231
- }
232
- {
233
- console.error("ErrorBoundary caught an error:", error, errorInfo);
234
- }
235
- }
236
- render() {
237
- if (this.state.hasError) {
238
- return /* @__PURE__ */ jsx("div", { className: "flex min-h-screen items-center justify-center bg-background p-4", children: /* @__PURE__ */ jsxs("div", { className: "max-w-md w-full space-y-4 text-center", children: [
239
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-foreground", children: "Something went wrong" }),
240
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "We're sorry, but something unexpected happened. Please try refreshing the page." }),
241
- this.props.supportEmail && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
242
- "If the problem persists, please contact",
243
- " ",
244
- /* @__PURE__ */ jsx(
245
- "a",
246
- {
247
- href: `mailto:${this.props.supportEmail}`,
248
- className: "text-primary hover:underline",
249
- children: this.props.supportEmail
250
- }
251
- )
252
- ] }),
253
- /* @__PURE__ */ jsx(
254
- "button",
255
- {
256
- onClick: () => window.location.reload(),
257
- className: "px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90",
258
- children: "Refresh Page"
259
- }
260
- )
261
- ] }) });
262
- }
263
- return this.props.children;
264
- }
265
- };
266
- __name(_ErrorBoundary, "ErrorBoundary");
267
- var ErrorBoundary = _ErrorBoundary;
268
-
269
- // src/components/errors/ErrorsTracker/utils/formatters.ts
270
- function formatZodIssues(error, maxIssues = 3) {
271
- const issues = error.issues.slice(0, maxIssues);
272
- const formatted = issues.map((issue) => {
273
- const path = issue.path.join(".") || "root";
274
- return `${path}: ${issue.message}`;
275
- });
276
- if (error.issues.length > maxIssues) {
277
- formatted.push(`... and ${error.issues.length - maxIssues} more`);
278
- }
279
- return formatted.join(", ");
280
- }
281
- __name(formatZodIssues, "formatZodIssues");
282
- function formatValidationErrorForClipboard(detail) {
283
- const errorData = {
284
- type: "validation",
285
- timestamp: detail.timestamp.toISOString(),
286
- operation: detail.operation,
287
- endpoint: {
288
- method: detail.method,
289
- path: detail.path
290
- },
291
- validation_errors: detail.error.issues.map((issue) => ({
292
- path: issue.path.join(".") || "root",
293
- message: issue.message,
294
- code: issue.code,
295
- ..."expected" in issue && { expected: issue.expected },
296
- ..."received" in issue && { received: issue.received },
297
- ..."minimum" in issue && { minimum: issue.minimum },
298
- ..."maximum" in issue && { maximum: issue.maximum }
299
- })),
300
- response: detail.response,
301
- total_errors: detail.error.issues.length
302
- };
303
- return JSON.stringify(errorData, null, 2);
304
- }
305
- __name(formatValidationErrorForClipboard, "formatValidationErrorForClipboard");
306
- function formatCORSErrorForClipboard(detail) {
307
- const errorData = {
308
- type: "cors",
309
- timestamp: detail.timestamp.toISOString(),
310
- url: detail.url,
311
- method: detail.method,
312
- error: detail.error
313
- };
314
- return JSON.stringify(errorData, null, 2);
315
- }
316
- __name(formatCORSErrorForClipboard, "formatCORSErrorForClipboard");
317
- function formatNetworkErrorForClipboard(detail) {
318
- const errorData = {
319
- type: "network",
320
- timestamp: detail.timestamp.toISOString(),
321
- url: detail.url,
322
- method: detail.method,
323
- error: detail.error,
324
- ...detail.statusCode && { statusCode: detail.statusCode }
325
- };
326
- return JSON.stringify(errorData, null, 2);
327
- }
328
- __name(formatNetworkErrorForClipboard, "formatNetworkErrorForClipboard");
329
- function formatCentrifugoErrorForClipboard(detail) {
330
- const errorData = {
331
- type: "centrifugo",
332
- timestamp: detail.timestamp.toISOString(),
333
- method: detail.method,
334
- error: detail.error,
335
- ...detail.code !== void 0 && { code: detail.code },
336
- ...detail.data && { data: detail.data }
337
- };
338
- return JSON.stringify(errorData, null, 2);
339
- }
340
- __name(formatCentrifugoErrorForClipboard, "formatCentrifugoErrorForClipboard");
341
- function extractDomain(url) {
342
- try {
343
- const urlObj = new URL(url);
344
- return urlObj.origin;
345
- } catch {
346
- return url;
347
- }
348
- }
349
- __name(extractDomain, "extractDomain");
350
- function formatErrorTitle(detail) {
351
- switch (detail.type) {
352
- case "validation":
353
- return `\u274C Validation Error in ${detail.operation}`;
354
- case "cors":
355
- return "\u{1F6AB} CORS Error";
356
- case "network":
357
- return detail.statusCode ? `\u26A0\uFE0F Network Error (${detail.statusCode})` : "\u26A0\uFE0F Network Error";
358
- case "centrifugo":
359
- return detail.code !== void 0 ? `\u{1F50C} Centrifugo Error (${detail.code})` : "\u{1F50C} Centrifugo Error";
360
- default:
361
- return "\u274C Error";
362
- }
363
- }
364
- __name(formatErrorTitle, "formatErrorTitle");
365
- function getAuthToken() {
366
- if (typeof window === "undefined") return null;
367
- try {
368
- const token = localStorage.getItem("access_token") || localStorage.getItem("token") || localStorage.getItem("auth_token");
369
- return token;
370
- } catch (error) {
371
- consola.error("Failed to get auth token:", error);
372
- return null;
373
- }
374
- }
375
- __name(getAuthToken, "getAuthToken");
376
- function formatHeaders(headers) {
377
- return Object.entries(headers).map(
378
- ([key, value]) => `-H '${key}: ${value}'`
379
- );
380
- }
381
- __name(formatHeaders, "formatHeaders");
382
- function escapeShell(str) {
383
- return str.replace(/'/g, "'\\''");
384
- }
385
- __name(escapeShell, "escapeShell");
386
- function generateCurl(options) {
387
- const {
388
- method,
389
- path,
390
- token = getAuthToken() || void 0,
391
- // Auto-fetch if not provided
392
- body,
393
- headers = {},
394
- baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"
395
- } = options;
396
- const curlParts = ["curl"];
397
- const url = `${baseUrl}${path}`;
398
- curlParts.push(`'${url}'`);
399
- if (method.toUpperCase() !== "GET") {
400
- curlParts.push(`-X ${method.toUpperCase()}`);
401
- }
402
- const allHeaders = {
403
- "Accept": "*/*",
404
- "Content-Type": "application/json",
405
- ...headers
406
- };
407
- if (token) {
408
- allHeaders["Authorization"] = `Bearer ${token}`;
409
- }
410
- const headerStrings = formatHeaders(allHeaders);
411
- curlParts.push(...headerStrings);
412
- if (body && method.toUpperCase() !== "GET") {
413
- const bodyJson = typeof body === "string" ? body : JSON.stringify(body, null, 2);
414
- curlParts.push(`-d '${escapeShell(bodyJson)}'`);
415
- }
416
- return curlParts.join(" \\\n ");
417
- }
418
- __name(generateCurl, "generateCurl");
419
- function generateCurlFromError(detail) {
420
- return generateCurl({
421
- method: detail.method,
422
- path: detail.path
423
- // token is auto-fetched in generateCurl
424
- });
425
- }
426
- __name(generateCurlFromError, "generateCurlFromError");
427
- function formatErrorForClipboard(detail) {
428
- switch (detail.type) {
429
- case "validation":
430
- return formatValidationErrorForClipboard(detail);
431
- case "cors":
432
- return formatCORSErrorForClipboard(detail);
433
- case "network":
434
- return formatNetworkErrorForClipboard(detail);
435
- case "centrifugo":
436
- return formatCentrifugoErrorForClipboard(detail);
437
- default:
438
- return JSON.stringify(detail, null, 2);
439
- }
440
- }
441
- __name(formatErrorForClipboard, "formatErrorForClipboard");
442
- function supportsCurl(detail) {
443
- return detail.type === "validation";
444
- }
445
- __name(supportsCurl, "supportsCurl");
446
- function ErrorButtons({ detail }) {
447
- const { copyToClipboard } = useCopy();
448
- const handleCopyError = /* @__PURE__ */ __name(async (e) => {
449
- e.preventDefault();
450
- e.stopPropagation();
451
- const formattedError = formatErrorForClipboard(detail);
452
- await copyToClipboard(formattedError, "\u2705 Error details copied");
453
- }, "handleCopyError");
454
- const handleCopyCurl = /* @__PURE__ */ __name(async (e) => {
455
- e.preventDefault();
456
- e.stopPropagation();
457
- if (detail.type === "validation") {
458
- const curl = generateCurlFromError({
459
- method: detail.method,
460
- path: detail.path,
461
- response: detail.response
462
- });
463
- await copyToClipboard(curl, "\u2705 cURL command copied");
464
- }
465
- }, "handleCopyCurl");
466
- return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 mt-2", children: [
467
- /* @__PURE__ */ jsxs(
468
- Button$1,
469
- {
470
- size: "sm",
471
- variant: "secondary",
472
- onClick: handleCopyError,
473
- className: "h-8 text-xs bg-background hover:bg-background/80 text-foreground border border-border gap-1.5",
474
- children: [
475
- /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }),
476
- "Copy Error"
477
- ]
478
- }
479
- ),
480
- supportsCurl(detail) && /* @__PURE__ */ jsxs(
481
- Button$1,
482
- {
483
- size: "sm",
484
- variant: "secondary",
485
- onClick: handleCopyCurl,
486
- className: "h-8 text-xs bg-background hover:bg-background/80 text-foreground border border-border gap-1.5",
487
- children: [
488
- /* @__PURE__ */ jsx(Terminal, { className: "h-3.5 w-3.5" }),
489
- "Copy cURL"
490
- ]
491
- }
492
- )
493
- ] });
494
- }
495
- __name(ErrorButtons, "ErrorButtons");
496
- function buildValidationDescription(detail, config2) {
497
- const descriptionParts = [];
498
- if (config2.showPath) {
499
- descriptionParts.push(`${detail.method} ${detail.path}`);
500
- }
501
- if (config2.showErrorCount) {
502
- const count = detail.error.issues.length;
503
- const plural = count === 1 ? "error" : "errors";
504
- descriptionParts.push(`${count} ${plural}`);
505
- }
506
- const issuesText = formatZodIssues(detail.error, config2.maxIssuesInToast);
507
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 text-sm", children: [
508
- descriptionParts.length > 0 && /* @__PURE__ */ jsx("div", { className: "font-mono text-xs opacity-90", children: descriptionParts.join(" \u2022 ") }),
509
- /* @__PURE__ */ jsx("div", { className: "opacity-90", children: issuesText }),
510
- /* @__PURE__ */ jsx(ErrorButtons, { detail })
511
- ] });
512
- }
513
- __name(buildValidationDescription, "buildValidationDescription");
514
- function buildCORSDescription(detail, config2) {
515
- const domain = extractDomain(detail.url);
516
- const parts = [];
517
- if (config2.showMethod && config2.showUrl) {
518
- parts.push(`${detail.method} ${detail.url}`);
519
- } else if (config2.showUrl) {
520
- parts.push(detail.url);
521
- } else if (config2.showMethod) {
522
- parts.push(`${detail.method} request blocked`);
523
- }
524
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 text-sm", children: [
525
- parts.length > 0 && /* @__PURE__ */ jsx("div", { className: "font-mono text-xs opacity-90", children: parts.join(" \u2022 ") }),
526
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
527
- /* @__PURE__ */ jsx("div", { className: "font-medium", children: "Request blocked by CORS policy" }),
528
- /* @__PURE__ */ jsxs("div", { className: "text-xs opacity-75", children: [
529
- "Check CORS configuration on ",
530
- domain
531
- ] })
532
- ] }),
533
- /* @__PURE__ */ jsx(ErrorButtons, { detail })
534
- ] });
535
- }
536
- __name(buildCORSDescription, "buildCORSDescription");
537
- function buildNetworkDescription(detail, config2) {
538
- const parts = [];
539
- if (config2.showMethod && config2.showUrl) {
540
- parts.push(`${detail.method} ${detail.url}`);
541
- } else if (config2.showUrl) {
542
- parts.push(detail.url);
543
- } else if (config2.showMethod) {
544
- parts.push(`${detail.method} request failed`);
545
- }
546
- if (config2.showStatusCode && detail.statusCode) {
547
- parts.push(`Status: ${detail.statusCode}`);
548
- }
549
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 text-sm", children: [
550
- parts.length > 0 && /* @__PURE__ */ jsx("div", { className: "font-mono text-xs opacity-90", children: parts.join(" \u2022 ") }),
551
- /* @__PURE__ */ jsx("div", { className: "opacity-90", children: detail.error }),
552
- /* @__PURE__ */ jsx(ErrorButtons, { detail })
553
- ] });
554
- }
555
- __name(buildNetworkDescription, "buildNetworkDescription");
556
- function buildCentrifugoDescription(detail, config2) {
557
- const parts = [];
558
- if (config2.showMethod) {
559
- parts.push(`RPC: ${detail.method}`);
560
- }
561
- if (config2.showCode && detail.code !== void 0) {
562
- parts.push(`Code: ${detail.code}`);
563
- }
564
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 text-sm", children: [
565
- parts.length > 0 && /* @__PURE__ */ jsx("div", { className: "font-mono text-xs opacity-90", children: parts.join(" \u2022 ") }),
566
- /* @__PURE__ */ jsx("div", { className: "opacity-90", children: detail.error }),
567
- /* @__PURE__ */ jsx(ErrorButtons, { detail })
568
- ] });
569
- }
570
- __name(buildCentrifugoDescription, "buildCentrifugoDescription");
571
- function createErrorToast(detail, config2) {
572
- let description;
573
- if (detail.type === "validation") {
574
- description = buildValidationDescription(detail, config2);
575
- } else if (detail.type === "cors") {
576
- description = buildCORSDescription(detail, config2);
577
- } else if (detail.type === "centrifugo") {
578
- description = buildCentrifugoDescription(detail, config2);
579
- } else {
580
- description = buildNetworkDescription(detail, config2);
581
- }
582
- return {
583
- title: formatErrorTitle(detail),
584
- description,
585
- variant: "destructive",
586
- duration: config2.duration
587
- };
588
- }
589
- __name(createErrorToast, "createErrorToast");
590
-
591
- // src/components/errors/ErrorsTracker/types.ts
592
- var ERROR_EVENTS = {
593
- VALIDATION: "zod-validation-error",
594
- CORS: "cors-error",
595
- NETWORK: "network-error",
596
- /** Unified Centrifugo event - filter by detail.type === 'error' */
597
- CENTRIFUGO: "centrifugo"
598
- };
599
- var DEFAULT_ERROR_CONFIG = {
600
- enabled: true,
601
- showToast: true,
602
- maxErrors: 50,
603
- duration: 8e3
604
- };
605
- var DEFAULT_VALIDATION_CONFIG = {
606
- ...DEFAULT_ERROR_CONFIG,
607
- showOperation: true,
608
- showPath: true,
609
- showErrorCount: true,
610
- maxIssuesInToast: 3
611
- };
612
- var DEFAULT_CORS_CONFIG = {
613
- ...DEFAULT_ERROR_CONFIG,
614
- duration: 0,
615
- // Don't auto-dismiss CORS errors
616
- showUrl: true,
617
- showMethod: true
618
- };
619
- var DEFAULT_NETWORK_CONFIG = {
620
- ...DEFAULT_ERROR_CONFIG,
621
- duration: 0,
622
- // Don't auto-dismiss network errors
623
- showUrl: true,
624
- showMethod: true,
625
- showStatusCode: true
626
- };
627
- var DEFAULT_CENTRIFUGO_CONFIG = {
628
- ...DEFAULT_ERROR_CONFIG,
629
- duration: 0,
630
- // Don't auto-dismiss centrifugo errors
631
- showMethod: true,
632
- showCode: true
633
- };
634
- var ErrorTrackingContext = createContext(void 0);
635
- var errorIdCounter = 0;
636
- function generateErrorId(type) {
637
- return `${type}-error-${Date.now()}-${++errorIdCounter}`;
638
- }
639
- __name(generateErrorId, "generateErrorId");
640
- function ErrorTrackingProvider({
641
- children,
642
- validation: userValidationConfig,
643
- cors: userCorsConfig,
644
- network: userNetworkConfig,
645
- centrifugo: userCentrifugoConfig,
646
- onError
647
- }) {
648
- const [errors, setErrors] = useState([]);
649
- const validationConfig = {
650
- ...DEFAULT_VALIDATION_CONFIG,
651
- ...userValidationConfig
652
- };
653
- const corsConfig = {
654
- ...DEFAULT_CORS_CONFIG,
655
- ...userCorsConfig
656
- };
657
- const networkConfig = {
658
- ...DEFAULT_NETWORK_CONFIG,
659
- ...userNetworkConfig
660
- };
661
- const centrifugoConfig = {
662
- ...DEFAULT_CENTRIFUGO_CONFIG,
663
- ...userCentrifugoConfig
664
- };
665
- const clearErrors = useCallback(() => {
666
- setErrors([]);
667
- }, []);
668
- const clearErrorsByType = useCallback((type) => {
669
- setErrors((prev) => prev.filter((error) => error.type !== type));
670
- }, []);
671
- const clearError = useCallback((id) => {
672
- setErrors((prev) => prev.filter((error) => error.id !== id));
673
- }, []);
674
- const handleError = useCallback(
675
- (detail, config2) => {
676
- const storedError = {
677
- ...detail,
678
- id: generateErrorId(detail.type)
679
- };
680
- setErrors((prev) => {
681
- const updated = [storedError, ...prev];
682
- return updated.slice(0, config2.maxErrors);
683
- });
684
- const shouldShowToast = onError?.(detail) !== false;
685
- if (config2.showToast && shouldShowToast) {
686
- const toastOptions = createErrorToast(detail, config2);
687
- toast.error(toastOptions.title, {
688
- description: toastOptions.description,
689
- duration: toastOptions.duration
690
- });
691
- }
692
- },
693
- [onError]
694
- );
695
- useEffect(() => {
696
- if (typeof window === "undefined") return;
697
- const handlers = [];
698
- if (validationConfig.enabled) {
699
- const handler = /* @__PURE__ */ __name((event) => {
700
- if (!(event instanceof CustomEvent)) return;
701
- const detail = {
702
- ...event.detail,
703
- type: "validation"
704
- };
705
- handleError(detail, validationConfig);
706
- }, "handler");
707
- window.addEventListener(ERROR_EVENTS.VALIDATION, handler);
708
- handlers.push({ event: ERROR_EVENTS.VALIDATION, handler });
709
- }
710
- if (corsConfig.enabled) {
711
- const handler = /* @__PURE__ */ __name((event) => {
712
- if (!(event instanceof CustomEvent)) return;
713
- const detail = {
714
- ...event.detail,
715
- type: "cors"
716
- };
717
- handleError(detail, corsConfig);
718
- }, "handler");
719
- window.addEventListener(ERROR_EVENTS.CORS, handler);
720
- handlers.push({ event: ERROR_EVENTS.CORS, handler });
721
- }
722
- if (networkConfig.enabled) {
723
- const handler = /* @__PURE__ */ __name((event) => {
724
- if (!(event instanceof CustomEvent)) return;
725
- const detail = {
726
- ...event.detail,
727
- type: "network"
728
- };
729
- handleError(detail, networkConfig);
730
- }, "handler");
731
- window.addEventListener(ERROR_EVENTS.NETWORK, handler);
732
- handlers.push({ event: ERROR_EVENTS.NETWORK, handler });
733
- }
734
- if (centrifugoConfig.enabled) {
735
- const handler = /* @__PURE__ */ __name((event) => {
736
- if (!(event instanceof CustomEvent)) return;
737
- if (event.detail?.type !== "error") return;
738
- const detail = {
739
- type: "centrifugo",
740
- method: event.detail.data?.method || "unknown",
741
- error: event.detail.data?.error || "Unknown error",
742
- code: event.detail.data?.code,
743
- data: event.detail.data?.data,
744
- timestamp: event.detail.timestamp || /* @__PURE__ */ new Date()
745
- };
746
- handleError(detail, centrifugoConfig);
747
- }, "handler");
748
- window.addEventListener(ERROR_EVENTS.CENTRIFUGO, handler);
749
- handlers.push({ event: ERROR_EVENTS.CENTRIFUGO, handler });
750
- }
751
- return () => {
752
- handlers.forEach(({ event, handler }) => {
753
- window.removeEventListener(event, handler);
754
- });
755
- };
756
- }, [handleError, validationConfig, corsConfig, networkConfig, centrifugoConfig]);
757
- const validationErrors = errors.filter((e) => e.type === "validation");
758
- const corsErrors = errors.filter((e) => e.type === "cors");
759
- const networkErrors = errors.filter((e) => e.type === "network");
760
- const centrifugoErrors = errors.filter((e) => e.type === "centrifugo");
761
- const value = {
762
- errors,
763
- validationErrors,
764
- corsErrors,
765
- networkErrors,
766
- centrifugoErrors,
767
- clearErrors,
768
- clearErrorsByType,
769
- clearError,
770
- errorCount: errors.length,
771
- latestError: errors[0] || null,
772
- config: {
773
- validation: validationConfig,
774
- cors: corsConfig,
775
- network: networkConfig,
776
- centrifugo: centrifugoConfig
777
- }
778
- };
779
- return /* @__PURE__ */ jsx(ErrorTrackingContext.Provider, { value, children });
780
- }
781
- __name(ErrorTrackingProvider, "ErrorTrackingProvider");
782
- var isProduction = false;
783
- var Analytics = {
784
- /**
785
- * Initialize Google Analytics (called automatically by useAnalytics hook)
786
- */
787
- init: /* @__PURE__ */ __name((trackingId) => {
788
- return;
789
- }, "init"),
790
- /**
791
- * Check if Analytics is enabled and initialized
792
- */
793
- isEnabled: /* @__PURE__ */ __name(() => isProduction, "isEnabled"),
794
- /**
795
- * Track a page view
796
- */
797
- pageview: /* @__PURE__ */ __name((path) => {
798
- if (!Analytics.isEnabled()) return;
799
- ReactGA.send({ hitType: "pageview", page: path });
800
- }, "pageview"),
801
- /**
802
- * Track a custom event
803
- * @param name - Event name (action)
804
- * @param params - Optional event parameters
805
- */
806
- event: /* @__PURE__ */ __name((name, params = {}) => {
807
- if (!Analytics.isEnabled()) return;
808
- ReactGA.event(name, params);
809
- }, "event"),
810
- /**
811
- * Set user ID for tracking
812
- */
813
- setUser: /* @__PURE__ */ __name((userId) => {
814
- if (!Analytics.isEnabled()) return;
815
- ReactGA.set({ user_id: userId });
816
- }, "setUser"),
817
- /**
818
- * Set custom dimensions/metrics
819
- */
820
- set: /* @__PURE__ */ __name((fieldsObject) => {
821
- if (!Analytics.isEnabled()) return;
822
- ReactGA.set(fieldsObject);
823
- }, "set")
824
- };
825
- function useAnalytics(trackingIdProp) {
826
- const pathname = usePathname();
827
- const { user, isAuthenticated } = useAuth();
828
- const trackingId = trackingIdProp;
829
- const isEnabled = isProduction;
830
- useEffect(() => {
831
- return;
832
- }, [isEnabled, trackingId]);
833
- useEffect(() => {
834
- return;
835
- }, [isEnabled, isAuthenticated, user?.id]);
836
- useEffect(() => {
837
- return;
838
- }, [pathname, isEnabled]);
839
- return {
840
- isEnabled,
841
- trackingId,
842
- pageview: Analytics.pageview,
843
- event: Analytics.event,
844
- setUser: Analytics.setUser,
845
- set: Analytics.set
846
- };
847
- }
848
- __name(useAnalytics, "useAnalytics");
849
- function AnalyticsProvider({ children, trackingId }) {
850
- useAnalytics(trackingId);
851
- return /* @__PURE__ */ jsx(Fragment, { children });
852
- }
853
- __name(AnalyticsProvider, "AnalyticsProvider");
854
- var DIALOG_EVENTS = {
855
- OPEN_AUTH_DIALOG: "OPEN_AUTH_DIALOG",
856
- CLOSE_AUTH_DIALOG: "CLOSE_AUTH_DIALOG"};
857
- var AuthDialog = /* @__PURE__ */ __name(({
858
- onAuthRequired,
859
- authPath = "/auth"
860
- }) => {
861
- const [open, setOpen] = useState(false);
862
- const [message, setMessage] = useState("Please sign in to continue");
863
- const router = useCfgRouter();
864
- useEventListener(DIALOG_EVENTS.OPEN_AUTH_DIALOG, (payload) => {
865
- if (payload?.message) {
866
- setMessage(payload.message);
867
- }
868
- setOpen(true);
869
- });
870
- useEventListener(DIALOG_EVENTS.CLOSE_AUTH_DIALOG, () => {
871
- setOpen(false);
872
- });
873
- const handleClose = /* @__PURE__ */ __name(() => {
874
- setMessage("Please sign in to continue");
875
- setOpen(false);
876
- }, "handleClose");
877
- const handleGoToAuth = /* @__PURE__ */ __name(() => {
878
- if (typeof window !== "undefined") {
879
- sessionStorage.setItem("redirectAfterAuth", window.location.pathname);
880
- }
881
- if (onAuthRequired) {
882
- onAuthRequired();
883
- } else {
884
- router.push(authPath);
885
- }
886
- handleClose();
887
- }, "handleGoToAuth");
888
- return /* @__PURE__ */ jsx(Dialog$1, { open, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(DialogContent$1, { className: "max-w-sm", children: [
889
- /* @__PURE__ */ jsx(DialogHeader$1, { children: /* @__PURE__ */ jsx(DialogTitle$1, { children: "Authentication Required" }) }),
890
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
891
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: message }),
892
- /* @__PURE__ */ jsxs(Button, { onClick: handleGoToAuth, className: "w-full", children: [
893
- /* @__PURE__ */ jsx(LogIn, { className: "h-4 w-4 mr-2" }),
894
- "Go to Sign In"
895
- ] })
896
- ] })
897
- ] }) });
898
- }, "AuthDialog");
899
-
900
- // src/snippets/AuthDialog/events.ts
901
- var AUTH_EVENTS = {
902
- OPEN_AUTH_DIALOG: "OPEN_AUTH_DIALOG",
903
- CLOSE_AUTH_DIALOG: "CLOSE_AUTH_DIALOG"};
904
- function useAuthDialog() {
905
- const openAuthDialog = useCallback((options) => {
906
- events.publish({
907
- type: AUTH_EVENTS.OPEN_AUTH_DIALOG,
908
- payload: options
909
- });
910
- }, []);
911
- const closeAuthDialog = useCallback(() => {
912
- events.publish({
913
- type: AUTH_EVENTS.CLOSE_AUTH_DIALOG
914
- });
915
- }, []);
916
- return {
917
- openAuthDialog,
918
- closeAuthDialog
919
- };
920
- }
921
- __name(useAuthDialog, "useAuthDialog");
922
- function isDebugEnabled() {
923
- if (typeof window === "undefined") return false;
924
- try {
925
- return localStorage.getItem("pwa_debug") === "true";
926
- } catch {
927
- return false;
928
- }
929
- }
930
- __name(isDebugEnabled, "isDebugEnabled");
931
- var pwaLogger = {
932
- /**
933
- * Info level logging
934
- * Only logs in development or when debug is enabled
935
- */
936
- info: /* @__PURE__ */ __name((...args) => {
937
- {
938
- consola$1.info(...args);
939
- }
940
- }, "info"),
941
- /**
942
- * Warning level logging
943
- * Only logs in development or when debug is enabled
944
- */
945
- warn: /* @__PURE__ */ __name((...args) => {
946
- {
947
- consola$1.warn(...args);
948
- }
949
- }, "warn"),
950
- /**
951
- * Error level logging
952
- * Always logs (production + development)
953
- */
954
- error: /* @__PURE__ */ __name((...args) => {
955
- consola$1.error(...args);
956
- }, "error"),
957
- /**
958
- * Debug level logging
959
- * Only logs when debug is explicitly enabled
960
- */
961
- debug: /* @__PURE__ */ __name((...args) => {
962
- if (isDebugEnabled()) {
963
- consola$1.debug(...args);
964
- }
965
- }, "debug"),
966
- /**
967
- * Success level logging
968
- * Only logs in development or when debug is enabled
969
- */
970
- success: /* @__PURE__ */ __name((...args) => {
971
- {
972
- consola$1.success(...args);
973
- }
974
- }, "success")
975
- };
976
-
977
- // src/snippets/PushNotifications/utils/platform.ts
978
- function checkBrowserPushSupport() {
979
- if (typeof window === "undefined") {
980
- return { isSupported: false, browserName: "unknown", reason: "Server-side rendering" };
981
- }
982
- const ua = window.navigator.userAgent.toLowerCase();
983
- if (ua.includes("fban") || ua.includes("fbav") || ua.includes("fb_iab")) {
984
- return { isSupported: false, browserName: "Facebook In-App", reason: "In-app browsers do not support push notifications" };
985
- }
986
- if (ua.includes("instagram")) {
987
- return { isSupported: false, browserName: "Instagram In-App", reason: "In-app browsers do not support push notifications" };
988
- }
989
- if (ua.includes("tiktok") || ua.includes("bytedancewebview") || ua.includes("bytelocale")) {
990
- return { isSupported: false, browserName: "TikTok In-App", reason: "In-app browsers do not support push notifications" };
991
- }
992
- if (ua.includes("snapchat")) {
993
- return { isSupported: false, browserName: "Snapchat In-App", reason: "In-app browsers do not support push notifications" };
994
- }
995
- if (ua.includes("micromessenger")) {
996
- return { isSupported: false, browserName: "WeChat In-App", reason: "In-app browsers do not support push notifications" };
997
- }
998
- if (ua.includes("barcelona")) {
999
- return { isSupported: false, browserName: "Threads In-App", reason: "In-app browsers do not support push notifications" };
1000
- }
1001
- if (ua.includes("pinterest")) {
1002
- return { isSupported: false, browserName: "Pinterest In-App", reason: "In-app browsers do not support push notifications" };
1003
- }
1004
- if (ua.includes("telegram")) {
1005
- return { isSupported: false, browserName: "Telegram In-App", reason: "In-app browsers do not support push notifications" };
1006
- }
1007
- if (ua.includes("line/")) {
1008
- return { isSupported: false, browserName: "Line In-App", reason: "In-app browsers do not support push notifications" };
1009
- }
1010
- if (ua.includes("kakaotalk")) {
1011
- return { isSupported: false, browserName: "KakaoTalk In-App", reason: "In-app browsers do not support push notifications" };
1012
- }
1013
- const isIOSDevice = ua.includes("iphone") || ua.includes("ipad") || ua.includes("ipod");
1014
- if (ua.includes("linkedinapp") && isIOSDevice) {
1015
- return { isSupported: false, browserName: "LinkedIn In-App", reason: "LinkedIn In-App on iOS does not support push notifications" };
1016
- }
1017
- if (ua.includes("twitter") && isIOSDevice) {
1018
- return { isSupported: false, browserName: "Twitter In-App", reason: "Twitter In-App on iOS does not support push notifications" };
1019
- }
1020
- if (ua.includes("opera mini") || ua.includes("opios")) {
1021
- return { isSupported: false, browserName: "Opera Mini", reason: "Opera Mini does not support service workers" };
1022
- }
1023
- if (ua.includes("msie") || ua.includes("trident/")) {
1024
- return { isSupported: false, browserName: "Internet Explorer", reason: "Internet Explorer does not support Push API" };
1025
- }
1026
- if (ua.includes("ucbrowser") || ua.includes("uc browser")) {
1027
- return { isSupported: false, browserName: "UC Browser", reason: "UC Browser has unreliable push notification support" };
1028
- }
1029
- const isWebView = ua.includes("wv)") || ua.includes("webview") || ua.includes("; wv") || ua.includes("iphone") && !ua.includes("safari") || ua.includes("ipad") && !ua.includes("safari");
1030
- if (isWebView) {
1031
- return { isSupported: false, browserName: "WebView", reason: "WebViews do not support push notifications" };
1032
- }
1033
- let browserName = "unknown";
1034
- if (ua.includes("comet") || ua.includes("perplexity")) browserName = "Comet";
1035
- else if (ua.includes("edg/") || ua.includes("edge/")) browserName = "Edge";
1036
- else if (window.navigator.brave) browserName = "Brave";
1037
- else if (ua.includes("arc/")) browserName = "Arc";
1038
- else if (ua.includes("vivaldi")) browserName = "Vivaldi";
1039
- else if (ua.includes("yabrowser")) browserName = "Yandex";
1040
- else if (ua.includes("samsungbrowser")) browserName = "Samsung Internet";
1041
- else if (ua.includes("opr/") || ua.includes("opera")) browserName = "Opera";
1042
- else if (ua.includes("firefox")) browserName = "Firefox";
1043
- else if (ua.includes("chrome")) browserName = "Chrome";
1044
- else if (ua.includes("safari") && ua.includes("version/")) browserName = "Safari";
1045
- return { isSupported: true, browserName };
1046
- }
1047
- __name(checkBrowserPushSupport, "checkBrowserPushSupport");
1048
- function isStandalone() {
1049
- if (typeof window === "undefined") return false;
1050
- if (!window.matchMedia) {
1051
- const nav2 = navigator;
1052
- return nav2.standalone === true;
1053
- }
1054
- const isStandaloneDisplay = window.matchMedia("(display-mode: standalone)").matches;
1055
- const nav = navigator;
1056
- const isStandaloneNavigator = nav.standalone === true;
1057
- return isStandaloneDisplay || isStandaloneNavigator;
1058
- }
1059
- __name(isStandalone, "isStandalone");
1060
-
1061
- // src/snippets/PushNotifications/utils/vapid.ts
1062
- var _VapidKeyError = class _VapidKeyError extends Error {
1063
- constructor(message, code) {
1064
- super(message);
1065
- this.code = code;
1066
- this.name = "VapidKeyError";
1067
- }
1068
- };
1069
- __name(_VapidKeyError, "VapidKeyError");
1070
- var VapidKeyError = _VapidKeyError;
1071
- function urlBase64ToUint8Array(base64String) {
1072
- if (!base64String) {
1073
- throw new VapidKeyError("VAPID public key is required", "VAPID_EMPTY");
1074
- }
1075
- if (typeof base64String !== "string") {
1076
- throw new VapidKeyError("VAPID public key must be a string", "VAPID_INVALID_TYPE");
1077
- }
1078
- const padding = "=".repeat((4 - base64String.length % 4) % 4);
1079
- const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
1080
- let rawData;
1081
- try {
1082
- rawData = window.atob(base64);
1083
- } catch (e) {
1084
- throw new VapidKeyError(
1085
- `Invalid base64url format: ${e instanceof Error ? e.message : String(e)}`,
1086
- "VAPID_INVALID_BASE64"
1087
- );
1088
- }
1089
- const outputArray = new Uint8Array(rawData.length);
1090
- for (let i = 0; i < rawData.length; i++) {
1091
- outputArray[i] = rawData.charCodeAt(i);
1092
- }
1093
- if (outputArray.length !== 65) {
1094
- throw new VapidKeyError(
1095
- `Invalid key length: expected 65 bytes (P-256 uncompressed), got ${outputArray.length} bytes`,
1096
- "VAPID_INVALID_LENGTH"
1097
- );
1098
- }
1099
- if (outputArray[0] !== 4) {
1100
- throw new VapidKeyError(
1101
- `Invalid key format: must start with 0x04 (uncompressed P-256 point), got 0x${outputArray[0].toString(16).padStart(2, "0")}`,
1102
- "VAPID_INVALID_FORMAT"
1103
- );
1104
- }
1105
- return outputArray;
1106
- }
1107
- __name(urlBase64ToUint8Array, "urlBase64ToUint8Array");
1108
-
1109
- // src/snippets/PushNotifications/hooks/usePushNotifications.ts
1110
- function usePushNotifications(options) {
1111
- const [state, setState] = useState({
1112
- isSupported: false,
1113
- permission: "default",
1114
- isSubscribed: false,
1115
- subscription: null
1116
- });
1117
- useEffect(() => {
1118
- if (typeof window === "undefined") return;
1119
- const browserSupport = checkBrowserPushSupport();
1120
- if (!browserSupport.isSupported) {
1121
- pwaLogger.info(`[usePushNotifications] Browser does not support push: ${browserSupport.browserName} - ${browserSupport.reason}`);
1122
- setState((prev) => ({
1123
- ...prev,
1124
- isSupported: false,
1125
- permission: "denied"
1126
- }));
1127
- return;
1128
- }
1129
- const isSupported = "serviceWorker" in navigator && "PushManager" in window && "Notification" in window;
1130
- setState((prev) => ({
1131
- ...prev,
1132
- isSupported,
1133
- permission: isSupported ? Notification.permission : "denied"
1134
- }));
1135
- if (isSupported) {
1136
- navigator.serviceWorker.ready.then((registration) => registration.pushManager.getSubscription()).then((subscription) => {
1137
- setState((prev) => ({
1138
- ...prev,
1139
- isSubscribed: !!subscription,
1140
- subscription
1141
- }));
1142
- }).catch((error) => {
1143
- pwaLogger.error("[usePushNotifications] Failed to get subscription:", error);
1144
- });
1145
- }
1146
- }, []);
1147
- const subscribe = /* @__PURE__ */ __name(async () => {
1148
- if (!state.isSupported) {
1149
- pwaLogger.warn("[usePushNotifications] Push notifications not supported");
1150
- return null;
1151
- }
1152
- if (!options?.vapidPublicKey) {
1153
- pwaLogger.error("[usePushNotifications] VAPID public key required");
1154
- return null;
1155
- }
1156
- try {
1157
- pwaLogger.debug("[usePushNotifications] Running pre-flight checks...");
1158
- if (!navigator.onLine) {
1159
- pwaLogger.error("[usePushNotifications] No internet connection");
1160
- throw new Error("No internet connection. Please check your network and try again.");
1161
- }
1162
- const permission = await Notification.requestPermission();
1163
- setState((prev) => ({ ...prev, permission }));
1164
- if (permission !== "granted") {
1165
- pwaLogger.warn("[usePushNotifications] Permission not granted:", permission);
1166
- return null;
1167
- }
1168
- const registration = await navigator.serviceWorker.ready;
1169
- let applicationServerKey;
1170
- try {
1171
- pwaLogger.debug("[usePushNotifications] Converting VAPID key...");
1172
- applicationServerKey = urlBase64ToUint8Array(options.vapidPublicKey);
1173
- pwaLogger.info("[usePushNotifications] VAPID key validated successfully");
1174
- } catch (e) {
1175
- if (e instanceof VapidKeyError) {
1176
- pwaLogger.error(`[usePushNotifications] Invalid VAPID key: ${e.message} (code: ${e.code})`);
1177
- } else {
1178
- pwaLogger.error("[usePushNotifications] Failed to convert VAPID key:", e);
1179
- }
1180
- return null;
1181
- }
1182
- pwaLogger.debug("[usePushNotifications] Service Worker state:", {
1183
- controller: navigator.serviceWorker.controller ? "active" : "none",
1184
- registrationActive: registration.active ? "yes" : "no",
1185
- permission: Notification.permission
1186
- });
1187
- const existingSub = await registration.pushManager.getSubscription();
1188
- if (existingSub) {
1189
- pwaLogger.debug("[usePushNotifications] Unsubscribing from existing subscription...");
1190
- await existingSub.unsubscribe();
1191
- }
1192
- const subscribeOptions = {
1193
- userVisibleOnly: true,
1194
- applicationServerKey
1195
- };
1196
- pwaLogger.debug("[usePushNotifications] Subscribing with VAPID key...");
1197
- const subscription = await registration.pushManager.subscribe(subscribeOptions);
1198
- if (options.subscribeEndpoint) {
1199
- await fetch(options.subscribeEndpoint, {
1200
- method: "POST",
1201
- headers: { "Content-Type": "application/json" },
1202
- body: JSON.stringify(subscription)
1203
- });
1204
- }
1205
- setState((prev) => ({
1206
- ...prev,
1207
- isSubscribed: true,
1208
- subscription
1209
- }));
1210
- pwaLogger.success("[usePushNotifications] Successfully subscribed to push notifications");
1211
- return subscription;
1212
- } catch (error) {
1213
- pwaLogger.error("[usePushNotifications] Subscribe failed:", error);
1214
- if (error.name === "AbortError" || error.message?.includes("push service error")) {
1215
- pwaLogger.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1216
- pwaLogger.error("\u274C PUSH SERVICE ERROR - Cannot connect to FCM");
1217
- pwaLogger.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1218
- pwaLogger.error("");
1219
- pwaLogger.error("\u{1F50D} This is NOT a code bug - it's a network/security block.");
1220
- pwaLogger.error("");
1221
- pwaLogger.error("\u2705 Quick Fixes (try in order):");
1222
- pwaLogger.error(" 1. Disable VPN/Proxy and refresh page");
1223
- pwaLogger.error(" 2. Open Incognito window (Cmd+Shift+N)");
1224
- pwaLogger.error(" 3. Try different browser (Safari, Firefox)");
1225
- pwaLogger.error(" 4. Use mobile hotspot instead of WiFi");
1226
- pwaLogger.error("");
1227
- pwaLogger.error("\u{1F527} Technical Details:");
1228
- pwaLogger.error(" \u2022 Browser tries to connect to FCM (ports 5228-5230)");
1229
- pwaLogger.error(" \u2022 VPN/Firewall/Privacy settings may block these ports");
1230
- pwaLogger.error(' \u2022 Check browser console for "AbortError" details');
1231
- pwaLogger.error("");
1232
- pwaLogger.error("\u{1F4DA} Learn more: https://web.dev/push-notifications-overview/");
1233
- pwaLogger.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1234
- } else if (error.message?.includes("No internet connection")) {
1235
- pwaLogger.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1236
- pwaLogger.error("\u274C NO INTERNET CONNECTION");
1237
- pwaLogger.error("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1238
- pwaLogger.error("Please check your network connection and try again.");
1239
- } else {
1240
- pwaLogger.error("Unknown error:", error.name, error.message);
1241
- }
1242
- return null;
1243
- }
1244
- }, "subscribe");
1245
- const unsubscribe = /* @__PURE__ */ __name(async () => {
1246
- if (!state.subscription) {
1247
- pwaLogger.warn("[usePushNotifications] No active subscription to unsubscribe");
1248
- return false;
1249
- }
1250
- try {
1251
- await state.subscription.unsubscribe();
1252
- setState((prev) => ({
1253
- ...prev,
1254
- isSubscribed: false,
1255
- subscription: null
1256
- }));
1257
- pwaLogger.info("[usePushNotifications] Successfully unsubscribed from push notifications");
1258
- return true;
1259
- } catch (error) {
1260
- pwaLogger.error("[usePushNotifications] Unsubscribe failed:", error);
1261
- return false;
1262
- }
1263
- }, "unsubscribe");
1264
- return {
1265
- ...state,
1266
- subscribe,
1267
- unsubscribe
1268
- };
1269
- }
1270
- __name(usePushNotifications, "usePushNotifications");
1271
-
1272
- // src/snippets/PushNotifications/hooks/useDjangoPush.ts
1273
- function useDjangoPush(options) {
1274
- const { onSubscribed, onSubscribeError, onUnsubscribed, ...pushOptions } = options;
1275
- const [isLoading, setIsLoading] = useState(false);
1276
- const [error, setError] = useState(null);
1277
- const { isAuthenticated } = useAuth();
1278
- const { openAuthDialog } = useAuthDialog();
1279
- const pushNotifications = usePushNotifications(pushOptions);
1280
- const subscribe = useCallback(async () => {
1281
- if (!isAuthenticated) {
1282
- openAuthDialog({
1283
- message: "Please sign in to enable push notifications"
1284
- });
1285
- return false;
1286
- }
1287
- setIsLoading(true);
1288
- setError(null);
1289
- try {
1290
- const subscription = await pushNotifications.subscribe();
1291
- if (!subscription) {
1292
- const err = new Error("Browser subscription failed");
1293
- setError(err);
1294
- onSubscribeError?.(err);
1295
- return false;
1296
- }
1297
- pwaLogger.info("[useDjangoPush] Browser subscription created");
1298
- const result = await apiWebPush.web_push.webpushSubscribeCreate({
1299
- endpoint: subscription.endpoint,
1300
- keys: {
1301
- p256dh: arrayBufferToBase64(subscription.getKey("p256dh")),
1302
- auth: arrayBufferToBase64(subscription.getKey("auth"))
1303
- }
1304
- });
1305
- pwaLogger.info("[useDjangoPush] Subscription saved to Django:", result);
1306
- onSubscribed?.(subscription);
1307
- toast.success("Push notifications enabled");
1308
- return true;
1309
- } catch (err) {
1310
- const error2 = err instanceof Error ? err : new Error(String(err));
1311
- pwaLogger.error("[useDjangoPush] Subscribe failed:", error2);
1312
- setError(error2);
1313
- onSubscribeError?.(error2);
1314
- toast.error("Subscription Failed", {
1315
- description: "Could not save subscription to server. Please try again.",
1316
- duration: 5e3
1317
- });
1318
- return false;
1319
- } finally {
1320
- setIsLoading(false);
1321
- }
1322
- }, [isAuthenticated, openAuthDialog, pushNotifications.subscribe, onSubscribed, onSubscribeError]);
1323
- const unsubscribe = useCallback(async () => {
1324
- setIsLoading(true);
1325
- setError(null);
1326
- try {
1327
- const success = await pushNotifications.unsubscribe();
1328
- if (!success) {
1329
- return false;
1330
- }
1331
- pwaLogger.info("[useDjangoPush] Browser unsubscribed");
1332
- onUnsubscribed?.();
1333
- toast.success("Push notifications disabled");
1334
- return true;
1335
- } catch (err) {
1336
- const error2 = err instanceof Error ? err : new Error(String(err));
1337
- pwaLogger.error("[useDjangoPush] Unsubscribe failed:", error2);
1338
- setError(error2);
1339
- return false;
1340
- } finally {
1341
- setIsLoading(false);
1342
- }
1343
- }, [pushNotifications.unsubscribe, onUnsubscribed]);
1344
- const sendTestPush = useCallback(
1345
- async (message) => {
1346
- if (!pushNotifications.isSubscribed) {
1347
- const err = new Error("Not subscribed");
1348
- setError(err);
1349
- return false;
1350
- }
1351
- setIsLoading(true);
1352
- setError(null);
1353
- try {
1354
- const result = await apiWebPush.web_push.webpushSendCreate({
1355
- title: message.title,
1356
- body: message.body,
1357
- url: message.url || "/",
1358
- icon: "/icon.png"
1359
- });
1360
- pwaLogger.info("[useDjangoPush] Test push sent:", result);
1361
- return result.success;
1362
- } catch (err) {
1363
- const error2 = err instanceof Error ? err : new Error(String(err));
1364
- pwaLogger.error("[useDjangoPush] Send test push failed:", error2);
1365
- setError(error2);
1366
- return false;
1367
- } finally {
1368
- setIsLoading(false);
1369
- }
1370
- },
1371
- [pushNotifications.isSubscribed]
1372
- );
1373
- return {
1374
- // State from usePushNotifications
1375
- isSupported: pushNotifications.isSupported,
1376
- permission: pushNotifications.permission,
1377
- isSubscribed: pushNotifications.isSubscribed,
1378
- subscription: pushNotifications.subscription,
1379
- // Local state
1380
- isLoading,
1381
- error,
1382
- // Actions
1383
- subscribe,
1384
- unsubscribe,
1385
- sendTestPush
1386
- };
1387
- }
1388
- __name(useDjangoPush, "useDjangoPush");
1389
- function arrayBufferToBase64(buffer) {
1390
- if (!buffer) return "";
1391
- const bytes = new Uint8Array(buffer);
1392
- let binary = "";
1393
- for (let i = 0; i < bytes.byteLength; i++) {
1394
- binary += String.fromCharCode(bytes[i]);
1395
- }
1396
- return window.btoa(binary);
1397
- }
1398
- __name(arrayBufferToBase64, "arrayBufferToBase64");
1399
- var DjangoPushContext = createContext(void 0);
1400
- function DjangoPushProvider({
1401
- children,
1402
- vapidPublicKey,
1403
- autoSubscribe = false,
1404
- onSubscribed,
1405
- onSubscribeError,
1406
- onUnsubscribed
1407
- }) {
1408
- const djangoPush = useDjangoPush({
1409
- vapidPublicKey,
1410
- onSubscribed,
1411
- onSubscribeError,
1412
- onUnsubscribed
1413
- });
1414
- useEffect(() => {
1415
- if (autoSubscribe && djangoPush.isSupported && djangoPush.permission === "granted" && !djangoPush.isSubscribed && !djangoPush.isLoading) {
1416
- pwaLogger.info("[DjangoPushProvider] Auto-subscribing (permission already granted)");
1417
- djangoPush.subscribe();
1418
- }
1419
- }, [autoSubscribe, djangoPush.isSupported, djangoPush.permission, djangoPush.isSubscribed, djangoPush.isLoading]);
1420
- const [pushes, setPushes] = useState([]);
1421
- useEffect(() => {
1422
- if (typeof window === "undefined" || !("serviceWorker" in navigator)) {
1423
- return;
1424
- }
1425
- const handleMessage = /* @__PURE__ */ __name((event) => {
1426
- if (event.data && event.data.type === "PUSH_RECEIVED") {
1427
- const push = {
1428
- id: crypto.randomUUID(),
1429
- timestamp: Date.now(),
1430
- ...event.data.notification
1431
- };
1432
- setPushes((prev) => [push, ...prev]);
1433
- pwaLogger.info("[DjangoPushProvider] Push received:", push);
1434
- }
1435
- }, "handleMessage");
1436
- navigator.serviceWorker.addEventListener("message", handleMessage);
1437
- return () => navigator.serviceWorker.removeEventListener("message", handleMessage);
1438
- }, []);
1439
- const sendPush = useCallback(
1440
- async (message) => {
1441
- await djangoPush.sendTestPush({
1442
- title: message.title,
1443
- body: message.body,
1444
- url: message.data?.url
1445
- });
1446
- },
1447
- [djangoPush]
1448
- );
1449
- const clearPushes = useCallback(() => {
1450
- setPushes([]);
1451
- pwaLogger.info("[DjangoPushProvider] Push history cleared");
1452
- }, []);
1453
- const removePush = useCallback((id) => {
1454
- setPushes((prev) => prev.filter((p) => p.id !== id));
1455
- pwaLogger.info("[DjangoPushProvider] Push removed:", id);
1456
- }, []);
1457
- const value = {
1458
- ...djangoPush,
1459
- pushes,
1460
- sendPush,
1461
- clearPushes,
1462
- removePush
1463
- };
1464
- return /* @__PURE__ */ jsx(DjangoPushContext.Provider, { value, children });
1465
- }
1466
- __name(DjangoPushProvider, "DjangoPushProvider");
1467
-
1468
- // src/snippets/PushNotifications/utils/localStorage.ts
1469
- var STORAGE_KEYS = {
1470
- PUSH_DISMISSED: "pwa_push_dismissed_at"
1471
- };
1472
- function isPushDismissedRecently(resetDays = 7) {
1473
- return isDismissedRecentlyHelper(resetDays, STORAGE_KEYS.PUSH_DISMISSED);
1474
- }
1475
- __name(isPushDismissedRecently, "isPushDismissedRecently");
1476
- function markPushDismissed() {
1477
- if (typeof window === "undefined") return;
1478
- try {
1479
- localStorage.setItem(STORAGE_KEYS.PUSH_DISMISSED, Date.now().toString());
1480
- } catch {
1481
- }
1482
- }
1483
- __name(markPushDismissed, "markPushDismissed");
1484
- function isDismissedRecentlyHelper(resetDays, key) {
1485
- if (typeof window === "undefined") return false;
1486
- try {
1487
- const dismissed = localStorage.getItem(key);
1488
- if (!dismissed) return false;
1489
- const dismissedAt = parseInt(dismissed, 10);
1490
- const daysSince = (Date.now() - dismissedAt) / (1e3 * 60 * 60 * 24);
1491
- return daysSince < resetDays;
1492
- } catch {
1493
- return false;
1494
- }
1495
- }
1496
- __name(isDismissedRecentlyHelper, "isDismissedRecentlyHelper");
1497
- var DEFAULT_RESET_DAYS = 7;
1498
- function PushPrompt({
1499
- vapidPublicKey,
1500
- subscribeEndpoint = "/api/push/subscribe",
1501
- requirePWA = true,
1502
- delayMs = 5e3,
1503
- resetAfterDays = DEFAULT_RESET_DAYS,
1504
- onEnabled,
1505
- onDismissed
1506
- }) {
1507
- const { isAuthenticated, isLoading: isAuthLoading } = useAuth();
1508
- const { isSupported, permission, isSubscribed, subscribe } = usePushNotifications({
1509
- vapidPublicKey,
1510
- subscribeEndpoint
1511
- });
1512
- const [show, setShow] = useState(false);
1513
- const [enabling, setEnabling] = useState(false);
1514
- useEffect(() => {
1515
- if (isAuthLoading || !isAuthenticated) {
1516
- return;
1517
- }
1518
- if (!isSupported || isSubscribed || permission === "denied") {
1519
- return;
1520
- }
1521
- if (requirePWA && !isStandalone()) {
1522
- return;
1523
- }
1524
- if (typeof window !== "undefined") {
1525
- if (isPushDismissedRecently(resetAfterDays)) {
1526
- return;
1527
- }
1528
- }
1529
- const timer = setTimeout(() => setShow(true), delayMs);
1530
- return () => clearTimeout(timer);
1531
- }, [isAuthLoading, isAuthenticated, isSupported, isSubscribed, permission, requirePWA, resetAfterDays, delayMs]);
1532
- const handleEnable = /* @__PURE__ */ __name(async () => {
1533
- setEnabling(true);
1534
- try {
1535
- const success = await subscribe();
1536
- if (success) {
1537
- setShow(false);
1538
- onEnabled?.();
1539
- }
1540
- } catch (error) {
1541
- pwaLogger.error("[PushPrompt] Enable failed:", error);
1542
- } finally {
1543
- setEnabling(false);
1544
- }
1545
- }, "handleEnable");
1546
- const handleDismiss = /* @__PURE__ */ __name(() => {
1547
- setShow(false);
1548
- markPushDismissed();
1549
- onDismissed?.();
1550
- }, "handleDismiss");
1551
- if (!show) return null;
1552
- return /* @__PURE__ */ jsx("div", { className: "fixed bottom-4 left-4 right-4 z-50 animate-in slide-in-from-bottom-4 duration-300", children: /* @__PURE__ */ jsx("div", { className: "bg-zinc-900 border border-zinc-700 rounded-lg p-4 shadow-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
1553
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(Bell, { className: "w-5 h-5 text-blue-400" }) }),
1554
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
1555
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-white mb-1", children: "Enable notifications" }),
1556
- /* @__PURE__ */ jsx("p", { className: "text-xs text-zinc-400 mb-3", children: "Stay updated with important updates and alerts" }),
1557
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1558
- /* @__PURE__ */ jsx(
1559
- Button$1,
1560
- {
1561
- onClick: handleEnable,
1562
- loading: enabling,
1563
- size: "sm",
1564
- variant: "default",
1565
- children: "Enable"
1566
- }
1567
- ),
1568
- /* @__PURE__ */ jsx(
1569
- Button$1,
1570
- {
1571
- onClick: handleDismiss,
1572
- size: "sm",
1573
- variant: "ghost",
1574
- children: "Not now"
1575
- }
1576
- )
1577
- ] })
1578
- ] }),
1579
- /* @__PURE__ */ jsx(
1580
- Button$1,
1581
- {
1582
- onClick: handleDismiss,
1583
- size: "sm",
1584
- variant: "ghost",
1585
- className: "flex-shrink-0 p-1",
1586
- "aria-label": "Dismiss",
1587
- children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
1588
- }
1589
- )
1590
- ] }) }) });
1591
- }
1592
- __name(PushPrompt, "PushPrompt");
1593
-
1594
- // src/snippets/PushNotifications/config.ts
1595
- process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || "";
1596
-
1597
- // src/snippets/PWAInstall/utils/localStorage.ts
1598
- var STORAGE_KEYS2 = {
1599
- IOS_GUIDE_DISMISSED: "pwa_ios_guide_dismissed_at",
1600
- APP_INSTALLED: "pwa_app_installed",
1601
- A2HS_DISMISSED: "pwa_a2hs_dismissed_at"
1602
- };
1603
- function clearIOSGuideDismissal() {
1604
- if (typeof window === "undefined") return;
1605
- try {
1606
- localStorage.removeItem(STORAGE_KEYS2.IOS_GUIDE_DISMISSED);
1607
- } catch {
1608
- }
1609
- }
1610
- __name(clearIOSGuideDismissal, "clearIOSGuideDismissal");
1611
- function markAppInstalled() {
1612
- if (typeof window === "undefined") return;
1613
- try {
1614
- localStorage.setItem(STORAGE_KEYS2.APP_INSTALLED, "true");
1615
- clearIOSGuideDismissal();
1616
- } catch {
1617
- }
1618
- }
1619
- __name(markAppInstalled, "markAppInstalled");
1620
- function isA2HSDismissedRecently(resetDays = 3) {
1621
- return isDismissedRecentlyHelper2(resetDays, STORAGE_KEYS2.A2HS_DISMISSED);
1622
- }
1623
- __name(isA2HSDismissedRecently, "isA2HSDismissedRecently");
1624
- function markA2HSDismissed() {
1625
- if (typeof window === "undefined") return;
1626
- try {
1627
- localStorage.setItem(STORAGE_KEYS2.A2HS_DISMISSED, Date.now().toString());
1628
- } catch {
1629
- }
1630
- }
1631
- __name(markA2HSDismissed, "markA2HSDismissed");
1632
- function isDismissedRecentlyHelper2(resetDays, key) {
1633
- if (typeof window === "undefined") return false;
1634
- try {
1635
- const dismissed = localStorage.getItem(key);
1636
- if (!dismissed) return false;
1637
- const dismissedAt = parseInt(dismissed, 10);
1638
- const daysSince = (Date.now() - dismissedAt) / (1e3 * 60 * 60 * 24);
1639
- return daysSince < resetDays;
1640
- } catch {
1641
- return false;
1642
- }
1643
- }
1644
- __name(isDismissedRecentlyHelper2, "isDismissedRecentlyHelper");
1645
- function isDebugEnabled2() {
1646
- if (typeof window === "undefined") return false;
1647
- try {
1648
- return localStorage.getItem("pwa_debug") === "true";
1649
- } catch {
1650
- return false;
1651
- }
1652
- }
1653
- __name(isDebugEnabled2, "isDebugEnabled");
1654
- var pwaLogger2 = {
1655
- /**
1656
- * Info level logging
1657
- * Only logs in development or when debug is enabled
1658
- */
1659
- info: /* @__PURE__ */ __name((...args) => {
1660
- {
1661
- consola$1.info(...args);
1662
- }
1663
- }, "info"),
1664
- /**
1665
- * Warning level logging
1666
- * Only logs in development or when debug is enabled
1667
- */
1668
- warn: /* @__PURE__ */ __name((...args) => {
1669
- {
1670
- consola$1.warn(...args);
1671
- }
1672
- }, "warn"),
1673
- /**
1674
- * Error level logging
1675
- * Always logs (production + development)
1676
- */
1677
- error: /* @__PURE__ */ __name((...args) => {
1678
- consola$1.error(...args);
1679
- }, "error"),
1680
- /**
1681
- * Debug level logging
1682
- * Only logs when debug is explicitly enabled
1683
- */
1684
- debug: /* @__PURE__ */ __name((...args) => {
1685
- if (isDebugEnabled2()) {
1686
- consola$1.debug(...args);
1687
- }
1688
- }, "debug"),
1689
- /**
1690
- * Success level logging
1691
- * Only logs in development or when debug is enabled
1692
- */
1693
- success: /* @__PURE__ */ __name((...args) => {
1694
- {
1695
- consola$1.success(...args);
1696
- }
1697
- }, "success")
1698
- };
1699
-
1700
- // src/snippets/PWAInstall/utils/platform.ts
1701
- function isStandalone2() {
1702
- if (typeof window === "undefined") return false;
1703
- if (!window.matchMedia) {
1704
- const nav2 = navigator;
1705
- return nav2.standalone === true;
1706
- }
1707
- const isStandaloneDisplay = window.matchMedia("(display-mode: standalone)").matches;
1708
- const nav = navigator;
1709
- const isStandaloneNavigator = nav.standalone === true;
1710
- return isStandaloneDisplay || isStandaloneNavigator;
1711
- }
1712
- __name(isStandalone2, "isStandalone");
1713
- function isMobileDevice2() {
1714
- if (typeof window === "undefined") return false;
1715
- return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
1716
- }
1717
- __name(isMobileDevice2, "isMobileDevice");
1718
- function hasValidManifest() {
1719
- if (typeof document === "undefined") return false;
1720
- const manifestLink = document.querySelector('link[rel="manifest"]');
1721
- return !!manifestLink;
1722
- }
1723
- __name(hasValidManifest, "hasValidManifest");
1724
- function isStandaloneReliable2() {
1725
- const standalone = isStandalone2();
1726
- if (!standalone) return false;
1727
- if (isMobileDevice2()) return true;
1728
- return hasValidManifest();
1729
- }
1730
- __name(isStandaloneReliable2, "isStandaloneReliable");
1731
- function onDisplayModeChange(callback) {
1732
- if (typeof window === "undefined" || !window.matchMedia) {
1733
- return () => {
1734
- };
1735
- }
1736
- const mediaQuery = window.matchMedia("(display-mode: standalone)");
1737
- const handleChange = /* @__PURE__ */ __name((e) => {
1738
- callback(e.matches);
1739
- }, "handleChange");
1740
- mediaQuery.addEventListener("change", handleChange);
1741
- return () => {
1742
- mediaQuery.removeEventListener("change", handleChange);
1743
- };
1744
- }
1745
- __name(onDisplayModeChange, "onDisplayModeChange");
1746
-
1747
- // src/snippets/PWAInstall/hooks/useInstallPrompt.ts
1748
- function useInstallPrompt() {
1749
- const browser = useBrowserDetect();
1750
- const device = useDeviceDetect();
1751
- const [state, setState] = useState(() => {
1752
- if (typeof window === "undefined") {
1753
- return {
1754
- isIOS: false,
1755
- isAndroid: false,
1756
- isSafari: false,
1757
- isChrome: false,
1758
- isInstalled: false,
1759
- canPrompt: false,
1760
- deferredPrompt: null
1761
- };
1762
- }
1763
- const isSafari = browser.isSafari && !browser.isChromium;
1764
- return {
1765
- isIOS: device.isIOS,
1766
- isAndroid: device.isAndroid,
1767
- isSafari,
1768
- isChrome: browser.isChrome || browser.isChromium,
1769
- isInstalled: isStandalone2(),
1770
- canPrompt: false,
1771
- deferredPrompt: null
1772
- };
1773
- });
1774
- useEffect(() => {
1775
- const isSafari = browser.isSafari && !browser.isChromium;
1776
- setState((prev) => ({
1777
- ...prev,
1778
- isIOS: device.isIOS,
1779
- isAndroid: device.isAndroid,
1780
- isSafari,
1781
- isChrome: browser.isChrome || browser.isChromium,
1782
- isInstalled: isStandalone2()
1783
- }));
1784
- }, [browser, device]);
1785
- useEffect(() => {
1786
- if (typeof window === "undefined") return;
1787
- const handleBeforeInstallPrompt = /* @__PURE__ */ __name((e) => {
1788
- e.preventDefault();
1789
- const event = e;
1790
- setState((prev) => ({
1791
- ...prev,
1792
- canPrompt: true,
1793
- deferredPrompt: event
1794
- }));
1795
- }, "handleBeforeInstallPrompt");
1796
- window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
1797
- return () => {
1798
- window.removeEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
1799
- };
1800
- }, []);
1801
- useEffect(() => {
1802
- if (typeof window === "undefined") return;
1803
- const handleAppInstalled = /* @__PURE__ */ __name(() => {
1804
- setState((prev) => ({
1805
- ...prev,
1806
- canPrompt: false,
1807
- deferredPrompt: null,
1808
- isInstalled: true
1809
- }));
1810
- markAppInstalled();
1811
- }, "handleAppInstalled");
1812
- window.addEventListener("appinstalled", handleAppInstalled);
1813
- return () => {
1814
- window.removeEventListener("appinstalled", handleAppInstalled);
1815
- };
1816
- }, []);
1817
- useEffect(() => {
1818
- const cleanup = onDisplayModeChange((isStandaloneMode) => {
1819
- if (isStandaloneMode) {
1820
- setState((prev) => ({
1821
- ...prev,
1822
- isInstalled: true,
1823
- canPrompt: false,
1824
- deferredPrompt: null
1825
- }));
1826
- markAppInstalled();
1827
- }
1828
- });
1829
- return cleanup;
1830
- }, []);
1831
- const promptInstall = /* @__PURE__ */ __name(async () => {
1832
- if (!state.deferredPrompt) {
1833
- pwaLogger2.warn("[PWA Install] No deferred prompt available");
1834
- return null;
1835
- }
1836
- try {
1837
- await state.deferredPrompt.prompt();
1838
- const { outcome } = await state.deferredPrompt.userChoice;
1839
- pwaLogger2.info("[PWA Install] User choice:", outcome);
1840
- setState((prev) => ({
1841
- ...prev,
1842
- deferredPrompt: null,
1843
- canPrompt: false
1844
- }));
1845
- return outcome;
1846
- } catch (error) {
1847
- pwaLogger2.error("[PWA Install] Error showing install prompt:", error);
1848
- return null;
1849
- }
1850
- }, "promptInstall");
1851
- return {
1852
- ...state,
1853
- promptInstall,
1854
- // Expose full browser info
1855
- browser,
1856
- device
1857
- };
1858
- }
1859
- __name(useInstallPrompt, "useInstallPrompt");
1860
- var PwaContext = createContext(void 0);
1861
- function PwaProvider({ children, ...config2 }) {
1862
- if (config2.enabled === false) {
1863
- return /* @__PURE__ */ jsx(Fragment, { children });
1864
- }
1865
- const prompt = useInstallPrompt();
1866
- const value = {
1867
- // Platform
1868
- isIOS: prompt.isIOS,
1869
- isAndroid: prompt.isAndroid,
1870
- isDesktop: !prompt.isIOS && !prompt.isAndroid,
1871
- // Browsers (from useBrowserDetect)
1872
- isSafari: prompt.browser.isSafari && !prompt.browser.isChromium,
1873
- // Real Safari only
1874
- isChrome: prompt.browser.isChrome,
1875
- isFirefox: prompt.browser.isFirefox,
1876
- isEdge: prompt.browser.isEdge,
1877
- isOpera: prompt.browser.isOpera,
1878
- isBrave: prompt.browser.isBrave,
1879
- isArc: prompt.browser.isArc,
1880
- isVivaldi: prompt.browser.isVivaldi,
1881
- isYandex: prompt.browser.isYandex,
1882
- isSamsungBrowser: prompt.browser.isSamsungBrowser,
1883
- isUCBrowser: prompt.browser.isUCBrowser,
1884
- isChromium: prompt.browser.isChromium,
1885
- browserName: prompt.browser.browserName,
1886
- // State
1887
- isInstalled: prompt.isInstalled,
1888
- canPrompt: prompt.canPrompt,
1889
- // Actions
1890
- install: prompt.promptInstall
1891
- };
1892
- return /* @__PURE__ */ jsx(PwaContext.Provider, { value, children });
1893
- }
1894
- __name(PwaProvider, "PwaProvider");
1895
- function useInstall() {
1896
- const context = useContext(PwaContext);
1897
- if (context === void 0) {
1898
- throw new Error("useInstall must be used within <PwaProvider>");
1899
- }
1900
- return context;
1901
- }
1902
- __name(useInstall, "useInstall");
1903
- function getBrowserCategory(browser) {
1904
- if (browser.isChromium) return "chromium";
1905
- if (browser.isFirefox) return "firefox";
1906
- if (browser.isSafari) return "safari";
1907
- return "unknown";
1908
- }
1909
- __name(getBrowserCategory, "getBrowserCategory");
1910
- function getBrowserSteps(category, browserName) {
1911
- switch (category) {
1912
- case "chromium":
1913
- return [
1914
- {
1915
- number: 1,
1916
- title: "Find Install Icon",
1917
- icon: ArrowDownToLine,
1918
- description: "Look for install icon in address bar (right side)"
1919
- },
1920
- {
1921
- number: 2,
1922
- title: "Click Install",
1923
- icon: Plus,
1924
- description: 'Click the icon and select "Install"'
1925
- },
1926
- {
1927
- number: 3,
1928
- title: "Confirm",
1929
- icon: Check,
1930
- description: 'Click "Install" in the popup dialog'
1931
- }
1932
- ];
1933
- case "firefox":
1934
- return [
1935
- {
1936
- number: 1,
1937
- title: "Open Menu",
1938
- icon: Menu,
1939
- description: "Click the menu button (three lines)"
1940
- },
1941
- {
1942
- number: 2,
1943
- title: "Find Install Option",
1944
- icon: Search,
1945
- description: 'Look for "Install" or "Add to Home Screen"'
1946
- },
1947
- {
1948
- number: 3,
1949
- title: "Confirm",
1950
- icon: Check,
1951
- description: "Follow the installation prompts"
1952
- }
1953
- ];
1954
- case "safari":
1955
- return [
1956
- {
1957
- number: 1,
1958
- title: "Limited Support",
1959
- icon: Monitor,
1960
- description: "Safari on macOS has limited PWA support"
1961
- },
1962
- {
1963
- number: 2,
1964
- title: "Use Chromium Browser",
1965
- icon: ArrowDownToLine,
1966
- description: "Consider using Chrome, Edge, or Brave for full PWA experience"
1967
- }
1968
- ];
1969
- default:
1970
- return [
1971
- {
1972
- number: 1,
1973
- title: "Check Address Bar",
1974
- icon: ArrowDownToLine,
1975
- description: "Look for an install or download icon"
1976
- },
1977
- {
1978
- number: 2,
1979
- title: "Or Use Menu",
1980
- icon: Menu,
1981
- description: "Check browser menu for install option"
1982
- },
1983
- {
1984
- number: 3,
1985
- title: "Confirm",
1986
- icon: Check,
1987
- description: "Follow the installation prompts"
1988
- }
1989
- ];
1990
- }
1991
- }
1992
- __name(getBrowserSteps, "getBrowserSteps");
1993
- function StepCard({ step }) {
1994
- return /* @__PURE__ */ jsx(Card, { className: "border border-border", children: /* @__PURE__ */ jsx(CardContent, { className: "p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
1995
- /* @__PURE__ */ jsx(
1996
- "div",
1997
- {
1998
- className: "flex items-center justify-center rounded-full bg-primary text-primary-foreground flex-shrink-0",
1999
- style: { width: "32px", height: "32px" },
2000
- children: /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: step.number })
2001
- }
2002
- ),
2003
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2004
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
2005
- /* @__PURE__ */ jsx(step.icon, { className: "w-5 h-5 text-primary" }),
2006
- /* @__PURE__ */ jsx("h3", { className: "font-semibold text-foreground", children: step.title })
2007
- ] }),
2008
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: step.description })
2009
- ] })
2010
- ] }) }) });
2011
- }
2012
- __name(StepCard, "StepCard");
2013
- function DesktopGuide({ onDismiss, open = true }) {
2014
- const {
2015
- browserName,
2016
- isChromium,
2017
- isFirefox,
2018
- isSafari,
2019
- isEdge,
2020
- isBrave,
2021
- isArc,
2022
- isVivaldi,
2023
- isOpera,
2024
- isYandex
2025
- } = useInstall();
2026
- const category = useMemo(
2027
- () => getBrowserCategory({ isChromium, isFirefox, isSafari }),
2028
- [isChromium, isFirefox, isSafari]
2029
- );
2030
- const steps3 = useMemo(() => getBrowserSteps(category), [category, browserName]);
2031
- const displayName = useMemo(() => {
2032
- if (isEdge) return "Edge";
2033
- if (isBrave) return "Brave";
2034
- if (isArc) return "Arc";
2035
- if (isVivaldi) return "Vivaldi";
2036
- if (isOpera) return "Opera";
2037
- if (isYandex) return "Yandex Browser";
2038
- return browserName;
2039
- }, [browserName, isEdge, isBrave, isArc, isVivaldi, isOpera, isYandex]);
2040
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (isOpen) => !isOpen && onDismiss(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
2041
- /* @__PURE__ */ jsxs(DialogHeader, { className: "text-left", children: [
2042
- /* @__PURE__ */ jsxs(DialogTitle, { className: "flex items-center gap-2", children: [
2043
- /* @__PURE__ */ jsx(Monitor, { className: "w-5 h-5 text-primary" }),
2044
- "Install App on Desktop"
2045
- ] }),
2046
- /* @__PURE__ */ jsx(DialogDescription, { className: "text-left", children: isSafari ? "Safari on macOS has limited PWA support. For the best experience, use Chrome, Edge, or Brave." : `Install this app on ${displayName} for quick access from your desktop` })
2047
- ] }),
2048
- /* @__PURE__ */ jsx("div", { className: "space-y-3 py-4", children: steps3.map((step) => /* @__PURE__ */ jsx(StepCard, { step }, step.number)) }),
2049
- category === "chromium" && /* @__PURE__ */ jsxs("div", { className: "p-3 bg-muted/30 rounded-lg text-xs text-muted-foreground", children: [
2050
- '\u{1F4A1} Tip: You can also right-click the page and look for "Install" option in ',
2051
- displayName
2052
- ] }),
2053
- category === "firefox" && /* @__PURE__ */ jsx("div", { className: "p-3 bg-amber-500/10 rounded-lg text-xs text-amber-700 dark:text-amber-400", children: "\u2139\uFE0F Note: Firefox has limited PWA support. Some features may not work as expected." }),
2054
- /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsxs(Button$1, { onClick: onDismiss, variant: "default", className: "w-full", children: [
2055
- /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 mr-2" }),
2056
- "Got It"
2057
- ] }) })
2058
- ] }) });
2059
- }
2060
- __name(DesktopGuide, "DesktopGuide");
2061
- var steps = [
2062
- {
2063
- number: 1,
2064
- title: "Tap Share",
2065
- icon: ArrowUpRight,
2066
- description: "At the bottom of Safari"
2067
- },
2068
- {
2069
- number: 2,
2070
- title: "Scroll & Tap",
2071
- icon: ArrowDown,
2072
- description: '"Add to Home Screen"'
2073
- },
2074
- {
2075
- number: 3,
2076
- title: "Confirm",
2077
- icon: CheckCircle,
2078
- description: 'Tap "Add" in top-right'
2079
- }
2080
- ];
2081
- function StepCard2({ step }) {
2082
- return /* @__PURE__ */ jsx(Card, { className: "border border-border", children: /* @__PURE__ */ jsx(CardContent, { className: "p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
2083
- /* @__PURE__ */ jsx(
2084
- "div",
2085
- {
2086
- className: "flex items-center justify-center rounded-full bg-primary text-primary-foreground flex-shrink-0",
2087
- style: { width: "32px", height: "32px" },
2088
- children: /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: step.number })
2089
- }
2090
- ),
2091
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2092
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
2093
- /* @__PURE__ */ jsx(step.icon, { className: "w-5 h-5 text-primary" }),
2094
- /* @__PURE__ */ jsx("h3", { className: "font-semibold text-foreground", children: step.title })
2095
- ] }),
2096
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: step.description })
2097
- ] })
2098
- ] }) }) });
2099
- }
2100
- __name(StepCard2, "StepCard");
2101
- function IOSGuideDrawer({ onDismiss, open = true }) {
2102
- return /* @__PURE__ */ jsx(Drawer, { open, onOpenChange: (isOpen) => !isOpen && onDismiss(), children: /* @__PURE__ */ jsxs(DrawerContent, { children: [
2103
- /* @__PURE__ */ jsxs(DrawerHeader, { className: "text-left", children: [
2104
- /* @__PURE__ */ jsxs(DrawerTitle, { className: "flex items-center gap-2", children: [
2105
- /* @__PURE__ */ jsx(Share, { className: "w-5 h-5 text-primary" }),
2106
- "Add to Home Screen"
2107
- ] }),
2108
- /* @__PURE__ */ jsx(DrawerDescription, { className: "text-left", children: "Install this app on your iPhone for quick access and a better experience" })
2109
- ] }),
2110
- /* @__PURE__ */ jsx("div", { className: "space-y-3 p-4", children: steps.map((step) => /* @__PURE__ */ jsx(StepCard2, { step }, step.number)) }),
2111
- /* @__PURE__ */ jsx("div", { className: "p-4 pt-0", children: /* @__PURE__ */ jsxs(Button$1, { onClick: onDismiss, variant: "default", className: "w-full", children: [
2112
- /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 mr-2" }),
2113
- "Got It"
2114
- ] }) })
2115
- ] }) });
2116
- }
2117
- __name(IOSGuideDrawer, "IOSGuideDrawer");
2118
- var steps2 = [
2119
- {
2120
- number: 1,
2121
- title: "Tap Share",
2122
- icon: ArrowUpRight,
2123
- description: "At the bottom of Safari"
2124
- },
2125
- {
2126
- number: 2,
2127
- title: "Scroll & Tap",
2128
- icon: ArrowDown,
2129
- description: '"Add to Home Screen"'
2130
- },
2131
- {
2132
- number: 3,
2133
- title: "Confirm",
2134
- icon: CheckCircle,
2135
- description: 'Tap "Add" in top-right'
2136
- }
2137
- ];
2138
- function StepCard3({ step }) {
2139
- return /* @__PURE__ */ jsx(Card, { className: "border border-border", children: /* @__PURE__ */ jsx(CardContent, { className: "p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
2140
- /* @__PURE__ */ jsx(
2141
- "div",
2142
- {
2143
- className: "flex items-center justify-center rounded-full bg-primary text-primary-foreground flex-shrink-0",
2144
- style: { width: "32px", height: "32px" },
2145
- children: /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: step.number })
2146
- }
2147
- ),
2148
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2149
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-1", children: [
2150
- /* @__PURE__ */ jsx(step.icon, { className: "w-5 h-5 text-primary" }),
2151
- /* @__PURE__ */ jsx("h3", { className: "font-semibold text-foreground", children: step.title })
2152
- ] }),
2153
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: step.description })
2154
- ] })
2155
- ] }) }) });
2156
- }
2157
- __name(StepCard3, "StepCard");
2158
- function IOSGuideModal({ onDismiss, open = true }) {
2159
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (isOpen) => !isOpen && onDismiss(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
2160
- /* @__PURE__ */ jsxs(DialogHeader, { className: "text-left", children: [
2161
- /* @__PURE__ */ jsxs(DialogTitle, { className: "flex items-center gap-2", children: [
2162
- /* @__PURE__ */ jsx(Share, { className: "w-5 h-5 text-primary" }),
2163
- "Add to Home Screen"
2164
- ] }),
2165
- /* @__PURE__ */ jsx(DialogDescription, { className: "text-left", children: "Install this app on your iPhone for quick access and a better experience" })
2166
- ] }),
2167
- /* @__PURE__ */ jsx("div", { className: "space-y-3 py-4", children: steps2.map((step) => /* @__PURE__ */ jsx(StepCard3, { step }, step.number)) }),
2168
- /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsxs(Button$1, { onClick: onDismiss, variant: "default", className: "w-full", children: [
2169
- /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 mr-2" }),
2170
- "Got It"
2171
- ] }) })
2172
- ] }) });
2173
- }
2174
- __name(IOSGuideModal, "IOSGuideModal");
2175
- function IOSGuide(props) {
2176
- const isMobile = useIsMobile();
2177
- if (isMobile) {
2178
- return /* @__PURE__ */ jsx(IOSGuideDrawer, { ...props });
2179
- }
2180
- return /* @__PURE__ */ jsx(IOSGuideModal, { ...props });
2181
- }
2182
- __name(IOSGuide, "IOSGuide");
2183
- var DEFAULT_RESET_DAYS2 = 3;
2184
- function A2HSHint({
2185
- className,
2186
- resetAfterDays = DEFAULT_RESET_DAYS2,
2187
- delayMs = 3e3,
2188
- demo = false,
2189
- logo
2190
- } = {}) {
2191
- const { isIOS, isDesktop, isInstalled, canPrompt, install } = useInstall();
2192
- const [show, setShow] = useState(false);
2193
- const [showGuide, setShowGuide] = useState(false);
2194
- const [installing, setInstalling] = useState(false);
2195
- const shouldShow = demo ? !isInstalled : !isInstalled && (isIOS || canPrompt);
2196
- useEffect(() => {
2197
- if (!shouldShow) return;
2198
- if (!demo && typeof window !== "undefined") {
2199
- if (resetAfterDays === null) {
2200
- if (isA2HSDismissedRecently(Number.MAX_SAFE_INTEGER)) {
2201
- return;
2202
- }
2203
- } else if (isA2HSDismissedRecently(resetAfterDays)) {
2204
- return;
2205
- }
2206
- }
2207
- const timer = setTimeout(() => setShow(true), delayMs);
2208
- return () => clearTimeout(timer);
2209
- }, [shouldShow, resetAfterDays, delayMs, demo]);
2210
- const handleDismiss = /* @__PURE__ */ __name(() => {
2211
- setShow(false);
2212
- if (!demo) {
2213
- markA2HSDismissed();
2214
- }
2215
- }, "handleDismiss");
2216
- const handleGuideDismiss = /* @__PURE__ */ __name(() => {
2217
- setShowGuide(false);
2218
- if (!demo) {
2219
- handleDismiss();
2220
- }
2221
- }, "handleGuideDismiss");
2222
- const handleClick = /* @__PURE__ */ __name(async () => {
2223
- if (isIOS || isDesktop) {
2224
- setShowGuide(true);
2225
- } else if (canPrompt) {
2226
- setInstalling(true);
2227
- try {
2228
- await install();
2229
- handleDismiss();
2230
- } catch (error) {
2231
- pwaLogger2.error("[A2HSHint] Install error:", error);
2232
- } finally {
2233
- setInstalling(false);
2234
- }
2235
- }
2236
- }, "handleClick");
2237
- if (!show) return null;
2238
- let title;
2239
- let subtitle;
2240
- if (isIOS) {
2241
- title = "Add to Home Screen";
2242
- subtitle = /* @__PURE__ */ jsxs(Fragment, { children: [
2243
- "Tap to learn how ",
2244
- /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3" })
2245
- ] });
2246
- } else if (isDesktop) {
2247
- title = "Install App";
2248
- subtitle = /* @__PURE__ */ jsxs(Fragment, { children: [
2249
- "Click to see desktop guide ",
2250
- /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3" })
2251
- ] });
2252
- } else {
2253
- title = "Install App";
2254
- subtitle = /* @__PURE__ */ jsxs(Fragment, { children: [
2255
- "Tap to install ",
2256
- /* @__PURE__ */ jsx(Download, { className: "w-3 h-3" })
2257
- ] });
2258
- }
2259
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2260
- /* @__PURE__ */ jsx("div", { className: cn(
2261
- "fixed bottom-4 left-4 right-4 z-50 animate-in slide-in-from-bottom-4 duration-300",
2262
- demo && "relative inset-auto z-auto",
2263
- // Demo mode: remove fixed positioning
2264
- className
2265
- ), children: /* @__PURE__ */ jsx(
2266
- "div",
2267
- {
2268
- role: "button",
2269
- tabIndex: 0,
2270
- onClick: handleClick,
2271
- onKeyDown: (e) => {
2272
- if (e.key === "Enter" || e.key === " ") {
2273
- e.preventDefault();
2274
- handleClick();
2275
- }
2276
- },
2277
- className: cn(
2278
- "w-full bg-zinc-900 border border-zinc-700 rounded-lg p-4 shadow-lg cursor-pointer hover:bg-zinc-800 transition-colors",
2279
- installing && "opacity-70 cursor-not-allowed"
2280
- ),
2281
- "aria-disabled": installing,
2282
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2283
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: logo ? /* @__PURE__ */ jsx("img", { src: logo, alt: "App logo", className: "w-10 h-10 rounded-lg" }) : /* @__PURE__ */ jsx(Share, { className: "w-5 h-5 text-blue-400" }) }),
2284
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2285
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-white mb-1", children: title }),
2286
- /* @__PURE__ */ jsx("p", { className: "text-xs text-zinc-400 flex items-center gap-1", children: installing ? "Installing..." : subtitle })
2287
- ] }),
2288
- /* @__PURE__ */ jsx(
2289
- "button",
2290
- {
2291
- onClick: (e) => {
2292
- e.stopPropagation();
2293
- handleDismiss();
2294
- },
2295
- className: "flex-shrink-0 p-1 hover:bg-zinc-700 rounded transition-colors",
2296
- "aria-label": "Dismiss",
2297
- children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4 text-zinc-400" })
2298
- }
2299
- )
2300
- ] })
2301
- }
2302
- ) }),
2303
- isIOS && /* @__PURE__ */ jsx(IOSGuide, { open: showGuide, onDismiss: handleGuideDismiss }),
2304
- isDesktop && /* @__PURE__ */ jsx(DesktopGuide, { open: showGuide, onDismiss: handleGuideDismiss })
2305
- ] });
2306
- }
2307
- __name(A2HSHint, "A2HSHint");
2308
- var CACHE_KEY = "pwa_is_standalone";
2309
- function useIsPWA(options) {
2310
- const checkFunction = options?.reliable ? isStandaloneReliable2 : isStandalone2;
2311
- const [isPWA, setIsPWA] = useState(() => {
2312
- if (typeof window !== "undefined") {
2313
- try {
2314
- const cached = sessionStorage.getItem(CACHE_KEY);
2315
- if (cached !== null) {
2316
- return cached === "true";
2317
- }
2318
- } catch {
2319
- }
2320
- }
2321
- return checkFunction();
2322
- });
2323
- useEffect(() => {
2324
- const isStandaloneMode = checkFunction();
2325
- setIsPWA(isStandaloneMode);
2326
- if (typeof window !== "undefined") {
2327
- try {
2328
- sessionStorage.setItem(CACHE_KEY, String(isStandaloneMode));
2329
- } catch {
2330
- }
2331
- }
2332
- const cleanup = onDisplayModeChange((newValue) => {
2333
- setIsPWA(newValue);
2334
- if (typeof window !== "undefined") {
2335
- try {
2336
- sessionStorage.setItem(CACHE_KEY, String(newValue));
2337
- } catch {
2338
- }
2339
- }
2340
- });
2341
- return cleanup;
2342
- }, [checkFunction]);
2343
- return isPWA;
2344
- }
2345
- __name(useIsPWA, "useIsPWA");
2346
- var STORAGE_KEY = "pwa_last_page";
2347
- var TTL_24_HOURS = 24 * 60 * 60 * 1e3;
2348
- var DEFAULT_EXCLUDE_PATTERNS = [
2349
- "/auth",
2350
- "/login",
2351
- "/register",
2352
- "/error",
2353
- "/404",
2354
- "/500",
2355
- "/oauth",
2356
- "/callback"
2357
- ];
2358
- function isExcludedPath(pathname) {
2359
- return DEFAULT_EXCLUDE_PATTERNS.some(
2360
- (pattern) => pathname === pattern || pathname.startsWith(`${pattern}/`)
2361
- );
2362
- }
2363
- __name(isExcludedPath, "isExcludedPath");
2364
- function readLastPage() {
2365
- if (typeof window === "undefined") return null;
2366
- try {
2367
- const item = localStorage.getItem(STORAGE_KEY);
2368
- if (!item) return null;
2369
- const parsed = JSON.parse(item);
2370
- if (parsed && typeof parsed === "object" && "_meta" in parsed && "_value" in parsed) {
2371
- const age = Date.now() - parsed._meta.createdAt;
2372
- if (age > parsed._meta.ttl) {
2373
- localStorage.removeItem(STORAGE_KEY);
2374
- return null;
2375
- }
2376
- return parsed._value;
2377
- }
2378
- return item;
2379
- } catch {
2380
- return null;
2381
- }
2382
- }
2383
- __name(readLastPage, "readLastPage");
2384
- function saveLastPage(pathname) {
2385
- if (typeof window === "undefined") return;
2386
- try {
2387
- const wrapped = {
2388
- _meta: {
2389
- createdAt: Date.now(),
2390
- ttl: TTL_24_HOURS
2391
- },
2392
- _value: pathname
2393
- };
2394
- localStorage.setItem(STORAGE_KEY, JSON.stringify(wrapped));
2395
- } catch {
2396
- }
2397
- }
2398
- __name(saveLastPage, "saveLastPage");
2399
- function usePWAPageResume(options = {}) {
2400
- const { enabled = true } = options;
2401
- const pathname = usePathname();
2402
- const router = useRouter();
2403
- const isPWA = useIsPWA();
2404
- const hasResumed = useRef(false);
2405
- useEffect(() => {
2406
- if (!enabled || !isPWA || hasResumed.current) return;
2407
- hasResumed.current = true;
2408
- const lastPage = readLastPage();
2409
- if (lastPage && lastPage !== pathname && !isExcludedPath(lastPage)) {
2410
- router.replace(lastPage);
2411
- }
2412
- }, [isPWA, enabled]);
2413
- useEffect(() => {
2414
- if (!enabled) return;
2415
- if (isExcludedPath(pathname)) return;
2416
- saveLastPage(pathname);
2417
- }, [pathname, enabled]);
2418
- return { isPWA };
2419
- }
2420
- __name(usePWAPageResume, "usePWAPageResume");
2421
-
2422
- // src/snippets/PWAInstall/components/PWAPageResumeManager.tsx
2423
- function PWAPageResumeManager({ enabled = true }) {
2424
- usePWAPageResume({ enabled });
2425
- return null;
2426
- }
2427
- __name(PWAPageResumeManager, "PWAPageResumeManager");
2428
- var AIChatWidget = dynamic(
2429
- () => import('./AIChatWidget-LUPM7S2O.mjs').then((mod) => ({ default: mod.AIChatWidget })),
2430
- { ssr: false }
2431
- );
2432
- function BaseApp({
2433
- children,
2434
- theme,
2435
- auth,
2436
- analytics,
2437
- centrifugo,
2438
- errorTracking,
2439
- errorBoundary,
2440
- swr,
2441
- mcpChat,
2442
- pwaInstall,
2443
- pushNotifications
2444
- }) {
2445
- const enableErrorBoundary = errorBoundary?.enabled !== false;
2446
- const pwaInstallEnabled = pwaInstall?.enabled !== false;
2447
- const showInstallHint = pwaInstallEnabled && pwaInstall?.showInstallHint !== false;
2448
- const pushEnabled = pushNotifications?.enabled !== false && !!pushNotifications?.vapidPublicKey;
2449
- const centrifugoUrl = centrifugo?.url || process.env.NEXT_PUBLIC_CENTRIFUGO_URL;
2450
- const centrifugoEnabled = centrifugo?.enabled !== false;
2451
- const content = /* @__PURE__ */ jsx(
2452
- ThemeProvider,
2453
- {
2454
- defaultTheme: theme?.defaultTheme || "system",
2455
- storageKey: theme?.storageKey,
2456
- children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(
2457
- SWRConfig,
2458
- {
2459
- value: {
2460
- revalidateOnFocus: swr?.revalidateOnFocus ?? true,
2461
- revalidateOnReconnect: swr?.revalidateOnReconnect ?? true,
2462
- dedupingInterval: swr?.dedupingInterval ?? 2e3
2463
- },
2464
- children: /* @__PURE__ */ jsx(AuthProvider, { config: auth, children: /* @__PURE__ */ jsx(AnalyticsProvider, { trackingId: analytics?.googleTrackingId, children: /* @__PURE__ */ jsx(
2465
- CentrifugoProvider,
2466
- {
2467
- enabled: centrifugoEnabled,
2468
- autoConnect: centrifugoEnabled && centrifugo?.autoConnect,
2469
- url: centrifugoUrl,
2470
- onTokenRefresh: async () => {
2471
- const response = await getCentrifugoAuthTokenRetrieve();
2472
- return response.token;
2473
- },
2474
- children: /* @__PURE__ */ jsx(PwaProvider, { enabled: pwaInstallEnabled, children: /* @__PURE__ */ jsx(
2475
- DjangoPushProvider,
2476
- {
2477
- vapidPublicKey: pushNotifications?.vapidPublicKey || "",
2478
- autoSubscribe: pushNotifications?.autoSubscribe,
2479
- children: /* @__PURE__ */ jsxs(
2480
- ErrorTrackingProvider,
2481
- {
2482
- validation: errorTracking?.validation,
2483
- cors: errorTracking?.cors,
2484
- network: errorTracking?.network,
2485
- onError: errorTracking?.onError,
2486
- children: [
2487
- children,
2488
- /* @__PURE__ */ jsx(
2489
- NextTopLoader,
2490
- {
2491
- color: "hsl(var(--primary))",
2492
- height: 3,
2493
- showSpinner: false,
2494
- shadow: "0 0 10px hsl(var(--primary)), 0 0 5px hsl(var(--primary))"
2495
- }
2496
- ),
2497
- /* @__PURE__ */ jsx(Toaster, {}),
2498
- showInstallHint && /* @__PURE__ */ jsx(
2499
- A2HSHint,
2500
- {
2501
- resetAfterDays: pwaInstall?.resetAfterDays,
2502
- delayMs: pwaInstall?.delayMs,
2503
- logo: pwaInstall?.logo
2504
- }
2505
- ),
2506
- pwaInstallEnabled && pwaInstall?.resumeLastPage && /* @__PURE__ */ jsx(PWAPageResumeManager, { enabled: true }),
2507
- pushEnabled && /* @__PURE__ */ jsx(
2508
- PushPrompt,
2509
- {
2510
- vapidPublicKey: pushNotifications.vapidPublicKey,
2511
- subscribeEndpoint: pushNotifications?.subscribeEndpoint,
2512
- requirePWA: pushNotifications?.requirePWA ?? true,
2513
- delayMs: pushNotifications?.delayMs,
2514
- resetAfterDays: pushNotifications?.resetAfterDays
2515
- }
2516
- ),
2517
- mcpChat?.enabled && /* @__PURE__ */ jsx(
2518
- AIChatWidget,
2519
- {
2520
- apiEndpoint: mcpChat.apiEndpoint,
2521
- title: mcpChat.title,
2522
- placeholder: mcpChat.placeholder,
2523
- greeting: mcpChat.greeting,
2524
- position: mcpChat.position,
2525
- variant: mcpChat.variant,
2526
- enableStreaming: mcpChat.enableStreaming,
2527
- autoDetectEnvironment: mcpChat.autoDetectEnvironment,
2528
- className: mcpChat.className
2529
- }
2530
- ),
2531
- /* @__PURE__ */ jsx(AuthDialog, { authPath: auth?.routes?.auth })
2532
- ]
2533
- }
2534
- )
2535
- }
2536
- ) })
2537
- }
2538
- ) }) })
2539
- }
2540
- ) })
2541
- }
2542
- );
2543
- if (enableErrorBoundary) {
2544
- return /* @__PURE__ */ jsx(
2545
- ErrorBoundary,
2546
- {
2547
- supportEmail: errorBoundary?.supportEmail,
2548
- onError: errorBoundary?.onError,
2549
- children: content
2550
- }
2551
- );
2552
- }
2553
- return content;
2554
- }
2555
- __name(BaseApp, "BaseApp");
2556
- function matchesPath(pathname, enabledPath) {
2557
- if (!enabledPath) return false;
2558
- if (typeof enabledPath === "string") {
2559
- return pathname === enabledPath || pathname.startsWith(enabledPath + "/");
2560
- }
2561
- return enabledPath.some((path) => pathname === path || pathname.startsWith(path + "/"));
2562
- }
2563
- __name(matchesPath, "matchesPath");
2564
- function determineLayoutMode(pathname, adminLayout, privateLayout, publicLayout) {
2565
- if (adminLayout && matchesPath(pathname, adminLayout.enabledPath)) return "admin";
2566
- if (privateLayout && matchesPath(pathname, privateLayout.enabledPath)) return "private";
2567
- if (publicLayout && matchesPath(pathname, publicLayout.enabledPath)) return "public";
2568
- return "public";
2569
- }
2570
- __name(determineLayoutMode, "determineLayoutMode");
2571
- function AppLayoutContent({
2572
- children,
2573
- publicLayout,
2574
- privateLayout,
2575
- adminLayout,
2576
- noLayoutPaths
2577
- }) {
2578
- const pathname = usePathname();
2579
- const shouldSkipLayout = useMemo(
2580
- () => matchesPath(pathname, noLayoutPaths),
2581
- [pathname, noLayoutPaths]
2582
- );
2583
- const layoutMode = useMemo(
2584
- () => determineLayoutMode(
2585
- pathname,
2586
- adminLayout,
2587
- privateLayout,
2588
- publicLayout
2589
- ),
2590
- [pathname, adminLayout, privateLayout, publicLayout]
2591
- );
2592
- const renderLayout = /* @__PURE__ */ __name(() => {
2593
- if (shouldSkipLayout) {
2594
- return children;
2595
- }
2596
- switch (layoutMode) {
2597
- case "admin":
2598
- if (!adminLayout && privateLayout) {
2599
- return /* @__PURE__ */ jsx(ClientOnly, { children: /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(privateLayout.component, { children }) }) });
2600
- }
2601
- if (!adminLayout) {
2602
- return children;
2603
- }
2604
- return /* @__PURE__ */ jsx(ClientOnly, { children: /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(adminLayout.component, { children }) }) });
2605
- case "private":
2606
- if (!privateLayout) {
2607
- if (publicLayout) {
2608
- return /* @__PURE__ */ jsx(publicLayout.component, { children });
2609
- }
2610
- return children;
2611
- }
2612
- return /* @__PURE__ */ jsx(ClientOnly, { children: /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(privateLayout.component, { children }) }) });
2613
- case "public":
2614
- default:
2615
- if (!publicLayout) {
2616
- return children;
2617
- }
2618
- return /* @__PURE__ */ jsx(publicLayout.component, { children });
2619
- }
2620
- }, "renderLayout");
2621
- return renderLayout();
2622
- }
2623
- __name(AppLayoutContent, "AppLayoutContent");
2624
- function AppLayout(props) {
2625
- const {
2626
- theme,
2627
- auth,
2628
- analytics,
2629
- centrifugo,
2630
- errorTracking,
2631
- errorBoundary,
2632
- swr,
2633
- mcpChat,
2634
- pwaInstall,
2635
- pushNotifications
2636
- } = props;
2637
- return /* @__PURE__ */ jsx(
2638
- BaseApp,
2639
- {
2640
- theme,
2641
- auth,
2642
- analytics,
2643
- centrifugo,
2644
- errorTracking,
2645
- errorBoundary,
2646
- swr,
2647
- mcpChat,
2648
- pwaInstall,
2649
- pushNotifications,
2650
- children: /* @__PURE__ */ jsx(AppLayoutContent, { ...props })
2651
- }
2652
- );
2653
- }
2654
- __name(AppLayout, "AppLayout");
2655
- function PublicNavigation({
2656
- logo,
2657
- siteName,
2658
- navigation,
2659
- userMenu,
2660
- onMobileMenuClick
2661
- }) {
2662
- const { isAuthenticated } = useAuth();
2663
- const isMobile = useIsMobile();
2664
- const navClass = "sticky top-0 w-full z-50 border-b bg-background";
2665
- return /* @__PURE__ */ jsx("nav", { className: navClass, style: { backgroundColor: "hsl(var(--background) / 0.6)", backdropFilter: "blur(10px)" }, children: /* @__PURE__ */ jsx("div", { className: "w-full px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-4", children: [
2666
- /* @__PURE__ */ jsxs(Link, { href: "/", className: "flex items-center gap-2", children: [
2667
- logo && /* @__PURE__ */ jsx("img", { src: logo, alt: siteName, className: "h-6 w-auto object-contain" }),
2668
- /* @__PURE__ */ jsx("span", { className: "font-bold text-lg", children: siteName })
2669
- ] }),
2670
- /* @__PURE__ */ jsx("div", { className: "hidden md:flex items-center gap-6", children: navigation.map((item) => /* @__PURE__ */ jsx(
2671
- Link,
2672
- {
2673
- href: item.href,
2674
- className: "text-sm font-medium hover:text-primary transition-colors",
2675
- children: item.label
2676
- },
2677
- item.href
2678
- )) }),
2679
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
2680
- !isMobile && /* @__PURE__ */ jsxs(Fragment, { children: [
2681
- /* @__PURE__ */ jsx(ThemeToggle, {}),
2682
- /* @__PURE__ */ jsx(
2683
- UserMenu,
2684
- {
2685
- variant: "desktop",
2686
- groups: userMenu?.groups,
2687
- authPath: userMenu?.authPath
2688
- }
2689
- )
2690
- ] }),
2691
- isMobile && /* @__PURE__ */ jsx(
2692
- Button,
2693
- {
2694
- variant: "ghost",
2695
- size: "icon",
2696
- onClick: onMobileMenuClick,
2697
- "aria-label": "Toggle mobile menu",
2698
- children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
2699
- }
2700
- )
2701
- ] })
2702
- ] }) }) });
2703
- }
2704
- __name(PublicNavigation, "PublicNavigation");
2705
- function PublicMobileDrawer({
2706
- isOpen,
2707
- onClose,
2708
- logo,
2709
- siteName,
2710
- navigation,
2711
- userMenu
2712
- }) {
2713
- const { isAuthenticated } = useAuth();
2714
- return /* @__PURE__ */ jsx(Drawer$1, { open: isOpen, onOpenChange: (open) => !open && onClose(), direction: "right", children: /* @__PURE__ */ jsxs(DrawerContent$1, { direction: "right", className: "w-80 lg:hidden", children: [
2715
- /* @__PURE__ */ jsxs(DrawerHeader$1, { className: "flex flex-row items-center justify-between p-4 border-b border-border/30", children: [
2716
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2717
- logo && /* @__PURE__ */ jsx(
2718
- "img",
2719
- {
2720
- src: logo,
2721
- alt: `${siteName} Logo`,
2722
- className: "h-6 w-auto object-contain"
2723
- }
2724
- ),
2725
- /* @__PURE__ */ jsx(DrawerTitle$1, { className: "text-lg font-bold text-foreground", children: siteName })
2726
- ] }),
2727
- /* @__PURE__ */ jsxs(DrawerClose, { className: "p-2 rounded-sm transition-colors hover:bg-accent/50", children: [
2728
- /* @__PURE__ */ jsx(X, { className: "size-5" }),
2729
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close menu" })
2730
- ] })
2731
- ] }),
2732
- /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-6", children: [
2733
- /* @__PURE__ */ jsx(
2734
- UserMenu,
2735
- {
2736
- variant: "mobile",
2737
- groups: userMenu?.groups,
2738
- authPath: userMenu?.authPath
2739
- }
2740
- ),
2741
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2742
- /* @__PURE__ */ jsx("div", { className: "px-4 py-2", children: /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: "Menu" }) }),
2743
- /* @__PURE__ */ jsx("div", { className: "space-y-1", children: navigation.map((item) => /* @__PURE__ */ jsx(
2744
- Link,
2745
- {
2746
- href: item.href,
2747
- className: "block px-4 py-3 rounded-sm text-sm font-medium transition-colors text-foreground hover:bg-accent hover:text-accent-foreground",
2748
- children: item.label
2749
- },
2750
- item.href
2751
- )) })
2752
- ] })
2753
- ] }),
2754
- /* @__PURE__ */ jsx("div", { className: "border-t border-border/30 p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [
2755
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: "Theme" }),
2756
- /* @__PURE__ */ jsx(ThemeToggle, {})
2757
- ] }) })
2758
- ] }) });
2759
- }
2760
- __name(PublicMobileDrawer, "PublicMobileDrawer");
2761
- function DjangoCFGLogo({ className, size }) {
2762
- return /* @__PURE__ */ jsxs(
2763
- "svg",
2764
- {
2765
- width: size,
2766
- height: size,
2767
- viewBox: "0 0 541 536",
2768
- fill: "none",
2769
- xmlns: "http://www.w3.org/2000/svg",
2770
- className,
2771
- children: [
2772
- /* @__PURE__ */ jsx(
2773
- "path",
2774
- {
2775
- d: "M159.958 408.555C129.071 408.555 102.501 403.407 80.2482 393.111C57.9958 382.483 40.8913 366.707 28.9348 345.783C16.9783 324.527 11 297.791 11 265.575C11 236.348 16.9783 211.272 28.9348 190.348C40.8913 169.092 57.6637 152.818 79.2519 141.526C100.84 130.233 126.248 124.421 155.475 124.089C167.431 124.089 177.893 125.085 186.861 127.078C196.16 128.739 202.969 130.566 207.286 132.558L206.29 185.864C201.972 184.204 196.492 182.543 189.85 180.883C183.207 179.222 174.904 178.392 164.94 178.392C137.374 178.392 116.616 186.031 102.667 201.308C89.0496 216.586 82.241 238.008 82.241 265.575C82.241 284.506 85.0641 300.614 90.7102 313.899C96.6885 327.184 105.656 337.314 117.612 344.289C129.569 350.931 144.847 354.252 163.446 354.252C173.41 354.252 182.045 353.422 189.352 351.761C196.991 350.101 203.633 348.108 209.279 345.783L202.803 364.216C200.146 358.57 198.485 351.761 197.821 343.79C197.157 335.819 196.824 327.184 196.824 317.885V113.627C196.824 105.656 196.658 96.0244 196.326 84.7322C195.994 73.4399 195.496 62.4797 194.832 51.8517C194.167 41.2237 193.171 33.2526 191.843 27.9386L193.337 23.9531H266.571V304.433C266.571 311.076 266.571 319.711 266.571 330.339C266.903 340.635 267.235 350.765 267.567 360.729C268.231 370.693 269.228 378.664 270.556 384.642L269.062 388.627C255.112 394.273 239.004 398.923 220.737 402.577C202.803 406.562 182.543 408.555 159.958 408.555Z",
2776
- fill: "currentColor"
2777
- }
2778
- ),
2779
- /* @__PURE__ */ jsx(
2780
- "path",
2781
- {
2782
- d: "M193.337 23.9531L191.843 27.9385C193.171 33.2525 194.168 41.2236 194.832 51.8516C195.496 62.4796 195.994 73.4401 196.326 84.7324C196.658 96.0245 196.824 105.656 196.824 113.627V129.152C193.901 128.435 190.58 127.742 186.86 127.078C177.893 125.085 167.431 124.089 155.475 124.089C126.248 124.421 100.84 130.233 79.252 141.525L78.2432 142.059C57.1405 153.328 40.7042 169.425 28.9346 190.349C16.9782 211.272 11 236.348 11 265.575L11.0039 267.081C11.1878 298.625 17.1649 324.859 28.9346 345.783C40.8911 366.707 57.9956 382.483 80.248 393.111C102.5 403.407 129.07 408.555 159.958 408.555C182.543 408.555 202.803 406.562 220.737 402.576C239.004 398.923 255.112 394.273 269.062 388.627L270.557 384.642C269.228 378.663 268.232 370.692 267.567 360.729C267.235 350.765 266.903 340.635 266.571 330.339V23.9531H193.337ZM82.2412 265.575C82.2412 238.009 89.0498 216.586 102.667 201.309C116.616 186.031 137.374 178.392 164.94 178.392L166.789 178.401C175.935 178.499 183.622 179.326 189.85 180.883C192.337 181.505 194.663 182.126 196.824 182.748V317.885L196.828 319.62C196.867 328.261 197.199 336.317 197.821 343.79C197.985 345.757 198.21 347.653 198.495 349.478C195.625 350.297 192.577 351.06 189.352 351.762C182.045 353.422 173.409 354.252 163.445 354.252V343.252C172.677 343.252 180.348 342.49 186.584 341.106C186.076 333.821 185.824 326.078 185.824 317.885V191.237C180.514 190.065 173.614 189.392 164.94 189.392C139.406 189.392 122.05 196.41 110.814 208.698C99.5767 221.349 93.2412 239.809 93.2412 265.575C93.2413 283.41 95.8996 297.933 100.788 309.487C105.861 320.71 113.267 328.992 123.06 334.729C132.888 340.16 146.134 343.252 163.445 343.252V354.252C144.846 354.252 129.569 350.931 117.612 344.288C105.656 337.313 96.6882 327.184 90.71 313.899C85.2402 301.029 82.4199 285.51 82.249 267.341L82.2412 265.575ZM277.571 330.152C277.899 340.322 278.227 350.329 278.555 360.174C279.202 369.794 280.145 377.085 281.294 382.256L282.003 385.445L277.666 397.012L273.188 398.824C258.493 404.772 241.727 409.592 222.969 413.348C204.136 417.518 183.107 419.555 159.958 419.555C127.877 419.555 99.6539 414.211 75.6289 403.095L75.5684 403.066L75.5078 403.037C51.2848 391.468 32.4787 374.156 19.3838 351.24L19.3652 351.208L19.3477 351.176C6.21456 327.828 6.03515e-05 299.093 0 265.575C0 234.84 6.29459 207.797 19.3838 184.891C32.3805 161.813 50.6989 144.047 74.1533 131.778C97.5551 119.537 124.732 113.438 155.35 113.09L155.412 113.089H155.475C166.591 113.089 176.734 113.908 185.824 115.636V113.627C185.824 105.798 185.661 96.2805 185.331 85.0557C185.002 73.8772 184.51 63.0382 183.854 52.5381C183.2 42.0782 182.249 34.9186 181.171 30.6064L180.34 27.2832L185.714 12.9531H277.571V330.152Z",
2783
- fill: "currentColor",
2784
- opacity: "0.2"
2785
- }
2786
- ),
2787
- /* @__PURE__ */ jsx(
2788
- "path",
2789
- {
2790
- d: "M326.086 273.578L489.086 2.57812L415.586 204.578H532.086L333.586 533.078L415.586 273.578H326.086Z",
2791
- fill: "#FFEF0B"
2792
- }
2793
- ),
2794
- /* @__PURE__ */ jsx(
2795
- "path",
2796
- {
2797
- d: "M493.784 4.28711L422.726 199.577H540.949L337.865 535.663L328.818 531.57L408.763 278.577H317.244L484.801 0L493.784 4.28711ZM334.929 268.577H422.409L350.98 494.621L523.223 209.577H408.446L466.683 49.5254L334.929 268.577Z",
2798
- fill: "currentColor"
2799
- }
2800
- )
2801
- ]
2802
- }
2803
- );
2804
- }
2805
- __name(DjangoCFGLogo, "DjangoCFGLogo");
2806
- function FooterBottom({
2807
- copyright,
2808
- credits,
2809
- links = [],
2810
- variant = "desktop"
2811
- }) {
2812
- const isMobile = variant === "mobile";
2813
- if (isMobile) {
2814
- return /* @__PURE__ */ jsx("div", { className: "border-t border-border pt-4", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
2815
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: copyright }),
2816
- credits && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground flex items-center justify-center gap-1.5", children: credits.url ? /* @__PURE__ */ jsxs(
2817
- "a",
2818
- {
2819
- href: credits.url,
2820
- target: "_blank",
2821
- rel: "noopener noreferrer",
2822
- className: "hover:text-primary transition-colors flex items-center gap-1.5",
2823
- children: [
2824
- /* @__PURE__ */ jsx(DjangoCFGLogo, { size: 12, className: "text-foreground" }),
2825
- credits.text
2826
- ]
2827
- }
2828
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
2829
- /* @__PURE__ */ jsx(DjangoCFGLogo, { size: 12, className: "text-foreground" }),
2830
- credits.text
2831
- ] }) })
2832
- ] }) });
2833
- }
2834
- return /* @__PURE__ */ jsx("div", { className: "border-t border-border mt-8 pt-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row justify-between items-center space-y-3 md:space-y-0 gap-4", children: [
2835
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: copyright }),
2836
- credits && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground flex items-center gap-1.5", children: credits.url ? /* @__PURE__ */ jsxs(
2837
- "a",
2838
- {
2839
- href: credits.url,
2840
- target: "_blank",
2841
- rel: "noopener noreferrer",
2842
- className: "hover:text-primary transition-colors flex items-center gap-1.5",
2843
- children: [
2844
- /* @__PURE__ */ jsx(DjangoCFGLogo, { size: 14, className: "text-foreground" }),
2845
- credits.text
2846
- ]
2847
- }
2848
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
2849
- /* @__PURE__ */ jsx(DjangoCFGLogo, { size: 14, className: "text-foreground" }),
2850
- credits.text
2851
- ] }) }),
2852
- links.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-3 md:gap-4 justify-center md:justify-end", children: links.map(
2853
- (link) => link.external ? /* @__PURE__ */ jsx(
2854
- "a",
2855
- {
2856
- href: link.path,
2857
- target: "_blank",
2858
- rel: "noopener noreferrer",
2859
- className: "text-xs text-muted-foreground hover:text-primary transition-colors",
2860
- children: link.label
2861
- },
2862
- link.path
2863
- ) : /* @__PURE__ */ jsx(
2864
- Link,
2865
- {
2866
- href: link.path,
2867
- className: "text-xs text-muted-foreground hover:text-primary transition-colors",
2868
- children: link.label
2869
- },
2870
- link.path
2871
- )
2872
- ) })
2873
- ] }) });
2874
- }
2875
- __name(FooterBottom, "FooterBottom");
2876
- function FooterMenuSections({ menuSections }) {
2877
- if (menuSections.length === 0) return null;
2878
- const gapPx = 48;
2879
- const sectionCount = menuSections.length;
2880
- const totalGap = (sectionCount - 1) * gapPx;
2881
- const sectionWidth = `calc(25% - ${totalGap / sectionCount}px)`;
2882
- return /* @__PURE__ */ jsx("div", { className: "flex flex-1 gap-8 lg:gap-x-12 justify-end", children: menuSections.map((section) => /* @__PURE__ */ jsxs(
2883
- "div",
2884
- {
2885
- className: "flex-shrink-0 min-w-0",
2886
- style: { width: sectionWidth },
2887
- children: [
2888
- /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-foreground mb-3", children: section.title }),
2889
- /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: section.items.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
2890
- Link,
2891
- {
2892
- href: item.path,
2893
- className: "text-muted-foreground hover:text-primary text-sm transition-colors",
2894
- children: item.label
2895
- }
2896
- ) }, item.path)) })
2897
- ]
2898
- },
2899
- section.title
2900
- )) });
2901
- }
2902
- __name(FooterMenuSections, "FooterMenuSections");
2903
- var socialIconsMap = {
2904
- github: { icon: Github, title: "GitHub" },
2905
- linkedin: { icon: Linkedin, title: "LinkedIn" },
2906
- twitter: { icon: Twitter, title: "Twitter" },
2907
- telegram: { icon: MessageCircle, title: "Telegram" },
2908
- youtube: { icon: Youtube, title: "YouTube" },
2909
- facebook: { icon: Facebook, title: "Facebook" },
2910
- instagram: { icon: Instagram, title: "Instagram" },
2911
- whatsapp: { icon: MessageSquare, title: "WhatsApp" },
2912
- email: { icon: Mail, title: "Email" }
2913
- };
2914
- function FooterSocialLinksComponent({
2915
- socialLinks,
2916
- className = "flex space-x-4",
2917
- iconClassName = "w-5 h-5"
2918
- }) {
2919
- const socialLinksData = socialLinks ? Object.entries(socialLinks).filter(([_, url]) => url).map(([platform, url]) => {
2920
- const social = socialIconsMap[platform];
2921
- if (!social) return null;
2922
- return {
2923
- platform,
2924
- url,
2925
- icon: social.icon,
2926
- title: social.title
2927
- };
2928
- }).filter((item) => item !== null) : [];
2929
- if (socialLinksData.length === 0) return null;
2930
- return /* @__PURE__ */ jsx("div", { className, children: socialLinksData.map((social) => {
2931
- const Icon = social.icon;
2932
- return /* @__PURE__ */ jsx(
2933
- "a",
2934
- {
2935
- href: social.url,
2936
- target: "_blank",
2937
- rel: "noopener noreferrer",
2938
- className: "text-muted-foreground hover:text-primary transition-colors",
2939
- title: social.title,
2940
- children: /* @__PURE__ */ jsx(Icon, { className: iconClassName })
2941
- },
2942
- social.platform
2943
- );
2944
- }) });
2945
- }
2946
- __name(FooterSocialLinksComponent, "FooterSocialLinksComponent");
2947
- function FooterProjectInfo({
2948
- siteName,
2949
- description,
2950
- logo,
2951
- badge,
2952
- socialLinks,
2953
- variant = "desktop"
2954
- }) {
2955
- const isMobile = variant === "mobile";
2956
- return /* @__PURE__ */ jsxs("div", { className: isMobile ? "text-center space-y-4 mb-6" : "space-y-4 lg:flex-shrink-0 lg:w-80", children: [
2957
- /* @__PURE__ */ jsxs("div", { className: isMobile ? "flex items-center justify-center gap-2" : "flex items-center gap-2", children: [
2958
- logo ? /* @__PURE__ */ jsx("div", { className: isMobile ? "w-6 h-6 flex items-center justify-center" : "w-8 h-8 flex items-center justify-center", children: /* @__PURE__ */ jsx(
2959
- "img",
2960
- {
2961
- src: logo,
2962
- alt: `${siteName} Logo`,
2963
- className: "w-full h-full object-contain"
2964
- }
2965
- ) }) : /* @__PURE__ */ jsx(DjangoCFGLogo, { size: isMobile ? 24 : 32, className: "text-foreground" }),
2966
- /* @__PURE__ */ jsx("span", { className: isMobile ? "text-lg font-bold text-foreground" : "text-xl font-bold text-foreground", children: siteName })
2967
- ] }),
2968
- description && /* @__PURE__ */ jsx("p", { className: isMobile ? "text-muted-foreground text-sm leading-relaxed max-w-md mx-auto" : "text-muted-foreground text-sm leading-relaxed", children: description }),
2969
- badge && !isMobile && /* @__PURE__ */ jsx("div", { className: "pt-2", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 px-3 py-1 rounded-full bg-gradient-to-r from-primary/80 to-secondary/60 border border-primary/30 shadow-brand text-xs font-semibold text-primary-foreground", children: [
2970
- /* @__PURE__ */ jsx(badge.icon, { className: "w-4 h-4" }),
2971
- badge.text
2972
- ] }) }),
2973
- socialLinks && /* @__PURE__ */ jsx(
2974
- FooterSocialLinksComponent,
2975
- {
2976
- socialLinks,
2977
- className: isMobile ? "flex justify-center space-x-6" : "flex space-x-4 pt-4"
2978
- }
2979
- )
2980
- ] });
2981
- }
2982
- __name(FooterProjectInfo, "FooterProjectInfo");
2983
- function PublicFooter({
2984
- siteName,
2985
- description,
2986
- logo,
2987
- badge,
2988
- socialLinks,
2989
- links = [],
2990
- menuSections = [],
2991
- copyright: copyrightProp,
2992
- credits: creditsProp,
2993
- variant = "full"
2994
- }) {
2995
- const isMobile = useIsMobile();
2996
- const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
2997
- const copyright = copyrightProp || `\xA9 ${currentYear} ${siteName}. All rights reserved.`;
2998
- const credits = creditsProp || {
2999
- text: "Built with DjangoCFG",
3000
- url: "https://djangocfg.com"
3001
- };
3002
- if (variant === "simple") {
3003
- return /* @__PURE__ */ jsx("footer", { className: "bg-background border-t border-border mt-auto", children: /* @__PURE__ */ jsx("div", { className: "w-full px-4 py-4", children: /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: copyright }) }) }) });
3004
- }
3005
- if (isMobile) {
3006
- return /* @__PURE__ */ jsx("footer", { className: "lg:hidden bg-background border-t border-border mt-auto", children: /* @__PURE__ */ jsxs("div", { className: "w-full px-4 py-8", children: [
3007
- /* @__PURE__ */ jsx(
3008
- FooterProjectInfo,
3009
- {
3010
- siteName,
3011
- description,
3012
- logo,
3013
- socialLinks,
3014
- variant: "mobile"
3015
- }
3016
- ),
3017
- links.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap justify-center gap-3 mb-6", children: links.map(
3018
- (link) => link.external ? /* @__PURE__ */ jsx(
3019
- "a",
3020
- {
3021
- href: link.path,
3022
- target: "_blank",
3023
- rel: "noopener noreferrer",
3024
- className: "text-xs text-muted-foreground hover:text-primary transition-colors",
3025
- children: link.label
3026
- },
3027
- link.path
3028
- ) : /* @__PURE__ */ jsx(
3029
- Link,
3030
- {
3031
- href: link.path,
3032
- className: "text-xs text-muted-foreground hover:text-primary transition-colors",
3033
- children: link.label
3034
- },
3035
- link.path
3036
- )
3037
- ) }),
3038
- /* @__PURE__ */ jsx(
3039
- FooterBottom,
3040
- {
3041
- copyright,
3042
- credits,
3043
- variant: "mobile"
3044
- }
3045
- )
3046
- ] }) });
3047
- }
3048
- return /* @__PURE__ */ jsx("footer", { className: "max-lg:hidden bg-background border-t border-border mt-auto", children: /* @__PURE__ */ jsxs("div", { className: "w-full px-4 sm:px-6 lg:px-8 py-12", children: [
3049
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col lg:flex-row gap-8 lg:gap-12", children: [
3050
- /* @__PURE__ */ jsx(
3051
- FooterProjectInfo,
3052
- {
3053
- siteName,
3054
- description,
3055
- logo,
3056
- badge,
3057
- socialLinks,
3058
- variant: "desktop"
3059
- }
3060
- ),
3061
- /* @__PURE__ */ jsx(FooterMenuSections, { menuSections })
3062
- ] }),
3063
- /* @__PURE__ */ jsx(
3064
- FooterBottom,
3065
- {
3066
- copyright,
3067
- credits,
3068
- links,
3069
- variant: "desktop"
3070
- }
3071
- )
3072
- ] }) });
3073
- }
3074
- __name(PublicFooter, "PublicFooter");
3075
- function PublicLayout({
3076
- children,
3077
- logo,
3078
- siteName = "App",
3079
- navigation = [],
3080
- userMenu
3081
- }) {
3082
- const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
3083
- const pathname = usePathname();
3084
- useEffect(() => {
3085
- setMobileMenuOpen(false);
3086
- }, [pathname]);
3087
- return /* @__PURE__ */ jsxs("div", { className: "min-h-screen flex flex-col", children: [
3088
- /* @__PURE__ */ jsx(
3089
- PublicNavigation,
3090
- {
3091
- logo,
3092
- siteName,
3093
- navigation,
3094
- userMenu,
3095
- onMobileMenuClick: () => setMobileMenuOpen(true)
3096
- }
3097
- ),
3098
- /* @__PURE__ */ jsx(
3099
- PublicMobileDrawer,
3100
- {
3101
- isOpen: mobileMenuOpen,
3102
- onClose: () => setMobileMenuOpen(false),
3103
- logo,
3104
- siteName,
3105
- navigation,
3106
- userMenu
3107
- }
3108
- ),
3109
- /* @__PURE__ */ jsx("main", { className: "flex-1", children })
3110
- ] });
3111
- }
3112
- __name(PublicLayout, "PublicLayout");
3113
- function PrivateSidebar({ sidebar }) {
3114
- const pathname = usePathname();
3115
- const { state, isMobile } = useSidebar();
3116
- const homeHref = sidebar.homeHref || "/";
3117
- const isActive = /* @__PURE__ */ __name((href) => {
3118
- const matches = pathname === href || pathname.startsWith(href + "/");
3119
- if (!matches) return false;
3120
- return !sidebar.items.some(
3121
- (otherItem) => otherItem.href !== href && otherItem.href.startsWith(href + "/") && (pathname === otherItem.href || pathname.startsWith(otherItem.href + "/"))
3122
- );
3123
- }, "isActive");
3124
- return /* @__PURE__ */ jsxs(Sidebar, { collapsible: "icon", children: [
3125
- /* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsx(
3126
- "div",
3127
- {
3128
- className: "flex items-center gap-3",
3129
- style: state === "collapsed" ? {
3130
- paddingLeft: "7px",
3131
- paddingTop: "0.5rem",
3132
- paddingBottom: "0.5rem",
3133
- transition: "padding 200ms ease-in-out"
3134
- } : {
3135
- padding: "0.5rem",
3136
- transition: "padding 200ms ease-in-out"
3137
- },
3138
- children: /* @__PURE__ */ jsx(Link, { href: homeHref, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3139
- /* @__PURE__ */ jsx(
3140
- "div",
3141
- {
3142
- className: cn(
3143
- "bg-primary rounded-sm flex items-center justify-center flex-shrink-0",
3144
- isMobile ? "h-10 w-10" : "h-8 w-8"
3145
- ),
3146
- children: /* @__PURE__ */ jsx("span", { className: "text-primary-foreground font-bold text-sm", children: "D" })
3147
- }
3148
- ),
3149
- state !== "collapsed" && /* @__PURE__ */ jsx(
3150
- "span",
3151
- {
3152
- className: cn(
3153
- "font-semibold text-foreground truncate",
3154
- isMobile && "text-base"
3155
- ),
3156
- style: { whiteSpace: "nowrap" },
3157
- children: "Dashboard"
3158
- }
3159
- )
3160
- ] }) })
3161
- }
3162
- ) }),
3163
- /* @__PURE__ */ jsx(SidebarContent, { children: /* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarGroupContent, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: sidebar.items.map((item) => {
3164
- const active = isActive(item.href);
3165
- return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(
3166
- SidebarMenuButton,
3167
- {
3168
- asChild: true,
3169
- isActive: active,
3170
- tooltip: item.label,
3171
- size: isMobile ? "lg" : "default",
3172
- children: /* @__PURE__ */ jsxs(Link, { href: item.href, children: [
3173
- item.icon && /* @__PURE__ */ jsx(
3174
- LucideIcon,
3175
- {
3176
- icon: typeof item.icon === "string" ? item.icon : item.icon,
3177
- className: isMobile ? "h-5 w-5" : "h-4 w-4"
3178
- }
3179
- ),
3180
- /* @__PURE__ */ jsx("span", { className: isMobile ? "text-base" : "", children: item.label }),
3181
- item.badge && /* @__PURE__ */ jsx(SidebarMenuBadge, { children: item.badge })
3182
- ] })
3183
- }
3184
- ) }, item.href);
3185
- }) }) }) }) })
3186
- ] });
3187
- }
3188
- __name(PrivateSidebar, "PrivateSidebar");
3189
- function PrivateHeader({ header }) {
3190
- const { user, logout } = useAuth();
3191
- return /* @__PURE__ */ jsxs(
3192
- "header",
3193
- {
3194
- className: "sticky top-0 z-10 flex items-center justify-between px-4 shrink-0 bg-background border-b border-border",
3195
- style: { height: "64px", minHeight: "64px" },
3196
- children: [
3197
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
3198
- /* @__PURE__ */ jsx(SidebarTrigger, { className: "-ml-1" }),
3199
- /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mr-2 h-4" }),
3200
- header?.title && /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold text-foreground", children: header.title })
3201
- ] }),
3202
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3203
- /* @__PURE__ */ jsx(ThemeToggle, {}),
3204
- /* @__PURE__ */ jsx(
3205
- UserMenu,
3206
- {
3207
- variant: "desktop",
3208
- groups: header?.groups,
3209
- authPath: header?.authPath
3210
- }
3211
- )
3212
- ] })
3213
- ]
3214
- }
3215
- );
3216
- }
3217
- __name(PrivateHeader, "PrivateHeader");
3218
- function PrivateContent({
3219
- children,
3220
- padding = "default"
3221
- }) {
3222
- return /* @__PURE__ */ jsx(
3223
- "main",
3224
- {
3225
- className: cn(
3226
- "flex-1 overflow-y-auto",
3227
- padding === "default" && "p-4 sm:p-6 lg:p-8"
3228
- ),
3229
- children
3230
- }
3231
- );
3232
- }
3233
- __name(PrivateContent, "PrivateContent");
3234
- function PrivateLayout({
3235
- children,
3236
- sidebar,
3237
- header,
3238
- contentPadding = "default"
3239
- }) {
3240
- const { isAuthenticated, isLoading, saveRedirectUrl } = useAuth();
3241
- const router = useRouter();
3242
- const [isRedirecting, setIsRedirecting] = useState(false);
3243
- useEffect(() => {
3244
- if (!isLoading && !isAuthenticated && !isRedirecting) {
3245
- const currentUrl = window.location.pathname + window.location.search;
3246
- saveRedirectUrl(currentUrl);
3247
- setIsRedirecting(true);
3248
- router.push(header?.authPath || "/auth");
3249
- }
3250
- }, [isAuthenticated, isLoading, isRedirecting, router, saveRedirectUrl, header?.authPath]);
3251
- if (isLoading || isRedirecting || !isAuthenticated) {
3252
- return /* @__PURE__ */ jsx(
3253
- Preloader,
3254
- {
3255
- variant: "fullscreen",
3256
- text: isRedirecting ? "Redirecting to login..." : "Authenticating...",
3257
- size: "lg",
3258
- backdrop: true,
3259
- backdropOpacity: 80
3260
- }
3261
- );
3262
- }
3263
- return /* @__PURE__ */ jsxs(SidebarProvider, { defaultOpen: true, children: [
3264
- sidebar && /* @__PURE__ */ jsx(PrivateSidebar, { sidebar }),
3265
- /* @__PURE__ */ jsxs(SidebarInset, { className: "flex flex-col", children: [
3266
- (header || isAuthenticated) && /* @__PURE__ */ jsx(PrivateHeader, { header }),
3267
- /* @__PURE__ */ jsx(PrivateContent, { padding: contentPadding, children })
3268
- ] })
3269
- ] });
3270
- }
3271
- __name(PrivateLayout, "PrivateLayout");
3272
- var AuthFormContext = createContext(void 0);
3273
- var AuthFormProvider = /* @__PURE__ */ __name(({
3274
- children,
3275
- sourceUrl: sourceUrlProp,
3276
- supportUrl,
3277
- termsUrl,
3278
- privacyUrl,
3279
- enablePhoneAuth = false,
3280
- enableGithubAuth = false,
3281
- enable2FASetup = true,
3282
- logoUrl,
3283
- redirectUrl,
3284
- onIdentifierSuccess,
3285
- onOTPSuccess,
3286
- onError
3287
- }) => {
3288
- const sourceUrl = sourceUrlProp || (typeof window !== "undefined" ? window.location.origin : "");
3289
- const requireTermsAcceptance = Boolean(termsUrl || privacyUrl);
3290
- const authForm = useAuthForm({
3291
- onIdentifierSuccess,
3292
- onOTPSuccess,
3293
- onError,
3294
- sourceUrl,
3295
- redirectUrl,
3296
- requireTermsAcceptance,
3297
- enable2FASetup
3298
- });
3299
- const value = {
3300
- ...authForm,
3301
- // UI-specific configuration
3302
- sourceUrl,
3303
- supportUrl,
3304
- termsUrl,
3305
- privacyUrl,
3306
- enablePhoneAuth,
3307
- enableGithubAuth,
3308
- enable2FASetup,
3309
- logoUrl,
3310
- redirectUrl
3311
- };
3312
- return /* @__PURE__ */ jsx(AuthFormContext.Provider, { value, children });
3313
- }, "AuthFormProvider");
3314
- var useAuthFormContext = /* @__PURE__ */ __name(() => {
3315
- const context = useContext(AuthFormContext);
3316
- if (context === void 0) {
3317
- throw new Error("useAuthFormContext must be used within an AuthFormProvider");
3318
- }
3319
- return context;
3320
- }, "useAuthFormContext");
3321
- var AuthHelp = /* @__PURE__ */ __name(({
3322
- className = "",
3323
- variant = "default"
3324
- }) => {
3325
- const { supportUrl, channel } = useAuthFormContext();
3326
- const getChannelIcon = /* @__PURE__ */ __name(() => {
3327
- return channel === "phone" ? /* @__PURE__ */ jsx(MessageCircle, { className: "w-4 h-4 text-muted-foreground" }) : /* @__PURE__ */ jsx(Mail, { className: "w-4 h-4 text-muted-foreground" });
3328
- }, "getChannelIcon");
3329
- const getHelpText = /* @__PURE__ */ __name(() => {
3330
- return channel === "phone" ? "Check WhatsApp/SMS" : "Check spam folder";
3331
- }, "getHelpText");
3332
- const getDetailedHelp = /* @__PURE__ */ __name(() => {
3333
- if (channel === "phone") {
3334
- return {
3335
- title: "Didn't receive the code?",
3336
- tips: [
3337
- "\u2022 Check your WhatsApp messages",
3338
- "\u2022 Look for SMS messages",
3339
- "\u2022 Ensure you have signal/internet",
3340
- "\u2022 Wait a few minutes for delivery"
3341
- ]
3342
- };
3343
- } else {
3344
- return {
3345
- title: "Didn't receive the email?",
3346
- tips: [
3347
- "\u2022 Check your spam or junk folder",
3348
- "\u2022 Make sure you entered the correct email address",
3349
- "\u2022 Wait a few minutes for the email to arrive"
3350
- ]
3351
- };
3352
- }
3353
- }, "getDetailedHelp");
3354
- if (variant === "compact") {
3355
- return /* @__PURE__ */ jsxs(
3356
- "div",
3357
- {
3358
- className: `flex items-center justify-between p-3 bg-muted/30 rounded-sm border border-border ${className}`,
3359
- children: [
3360
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3361
- getChannelIcon(),
3362
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: getHelpText() })
3363
- ] }),
3364
- supportUrl && /* @__PURE__ */ jsx(
3365
- Button,
3366
- {
3367
- asChild: true,
3368
- variant: "ghost",
3369
- size: "sm",
3370
- className: "text-xs",
3371
- children: /* @__PURE__ */ jsxs("a", { href: supportUrl, target: "_blank", rel: "noopener noreferrer", className: "flex items-center gap-1", children: [
3372
- /* @__PURE__ */ jsx(HelpCircle, { className: "w-3 h-3" }),
3373
- "Need help?"
3374
- ] })
3375
- }
3376
- )
3377
- ]
3378
- }
3379
- );
3380
- }
3381
- const helpData = getDetailedHelp();
3382
- return /* @__PURE__ */ jsxs(
3383
- "div",
3384
- {
3385
- className: `flex flex-col gap-3 p-3 bg-muted/30 rounded-sm border border-border ${className}`,
3386
- children: [
3387
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3388
- getChannelIcon(),
3389
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
3390
- /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium text-foreground", children: helpData.title }),
3391
- /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5 text-xs text-muted-foreground", children: helpData.tips.map((tip, index) => /* @__PURE__ */ jsx("p", { children: tip }, index)) })
3392
- ] })
3393
- ] }),
3394
- supportUrl && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-2 border-t border-border", children: [
3395
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Still having trouble?" }),
3396
- /* @__PURE__ */ jsx(
3397
- Button,
3398
- {
3399
- asChild: true,
3400
- variant: "ghost",
3401
- size: "sm",
3402
- className: "text-xs h-7 px-2",
3403
- children: /* @__PURE__ */ jsxs("a", { href: supportUrl, target: "_blank", rel: "noopener noreferrer", className: "flex items-center gap-1", children: [
3404
- /* @__PURE__ */ jsx(HelpCircle, { className: "w-3 h-3" }),
3405
- "Get Help"
3406
- ] })
3407
- }
3408
- )
3409
- ] })
3410
- ]
3411
- }
3412
- );
3413
- }, "AuthHelp");
3414
- var OAuthProviders = /* @__PURE__ */ __name(() => {
3415
- const { enableGithubAuth, sourceUrl, setError } = useAuthFormContext();
3416
- const { isLoading, startGithubAuth } = useGithubAuth({
3417
- sourceUrl,
3418
- onError: setError
3419
- });
3420
- if (!enableGithubAuth) {
3421
- return null;
3422
- }
3423
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
3424
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3425
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t border-border" }) }),
3426
- /* @__PURE__ */ jsx("div", { className: "relative flex justify-center text-xs uppercase", children: /* @__PURE__ */ jsx("span", { className: "bg-card px-2 text-muted-foreground", children: "Or continue with" }) })
3427
- ] }),
3428
- /* @__PURE__ */ jsxs(
3429
- Button,
3430
- {
3431
- type: "button",
3432
- variant: "outline",
3433
- size: "lg",
3434
- className: "w-full",
3435
- onClick: startGithubAuth,
3436
- loading: isLoading,
3437
- children: [
3438
- /* @__PURE__ */ jsx(Github, { className: "w-5 h-5" }),
3439
- "Continue with GitHub"
3440
- ]
3441
- }
3442
- )
3443
- ] });
3444
- }, "OAuthProviders");
3445
- var OAuthCallback = /* @__PURE__ */ __name(({
3446
- onSuccess,
3447
- onError,
3448
- redirectUrl
3449
- }) => {
3450
- const searchParams = useSearchParams();
3451
- const { setStep, setTwoFactorSessionId, setShouldPrompt2FA } = useAuthFormContext();
3452
- const [status, setStatus] = useState(null);
3453
- const [errorMessage, setErrorMessage] = useState(null);
3454
- const provider = searchParams.get("provider");
3455
- const code = searchParams.get("code");
3456
- const state = searchParams.get("state");
3457
- const error = searchParams.get("error");
3458
- const errorDescription = searchParams.get("error_description");
3459
- const {
3460
- handleGithubCallback,
3461
- isLoading,
3462
- error: githubError
3463
- } = useGithubAuth({
3464
- onSuccess: /* @__PURE__ */ __name((user, isNewUser) => {
3465
- setStep("success");
3466
- onSuccess?.(user, isNewUser, "github");
3467
- }, "onSuccess"),
3468
- onError: /* @__PURE__ */ __name((err) => {
3469
- setStatus("error");
3470
- setErrorMessage(err);
3471
- onError?.(err);
3472
- }, "onError"),
3473
- onRequires2FA: /* @__PURE__ */ __name((sessionId, shouldPrompt) => {
3474
- setTwoFactorSessionId(sessionId);
3475
- setShouldPrompt2FA(shouldPrompt);
3476
- setStep("2fa");
3477
- }, "onRequires2FA"),
3478
- redirectUrl,
3479
- skipRedirect: true
3480
- // We handle navigation via success screen
3481
- });
3482
- useEffect(() => {
3483
- if (!provider || !code || !state) {
3484
- return;
3485
- }
3486
- if (error) {
3487
- setStatus("error");
3488
- setErrorMessage(errorDescription || error);
3489
- onError?.(errorDescription || error);
3490
- return;
3491
- }
3492
- const processCallback = /* @__PURE__ */ __name(async () => {
3493
- setStatus("processing");
3494
- if (provider === "github") {
3495
- await handleGithubCallback(code, state);
3496
- } else {
3497
- setStatus("error");
3498
- setErrorMessage(`Unsupported OAuth provider: ${provider}`);
3499
- onError?.(`Unsupported OAuth provider: ${provider}`);
3500
- }
3501
- }, "processCallback");
3502
- processCallback();
3503
- }, [provider, code, state, error]);
3504
- if (!provider || !code && !error) {
3505
- return null;
3506
- }
3507
- return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center", children: /* @__PURE__ */ jsxs(Card$1, { className: "w-full max-w-md mx-4 shadow-lg", children: [
3508
- /* @__PURE__ */ jsxs(CardHeader, { className: "text-center", children: [
3509
- status === "processing" && /* @__PURE__ */ jsxs(Fragment, { children: [
3510
- /* @__PURE__ */ jsx("div", { className: "mx-auto w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-4", children: /* @__PURE__ */ jsx(Loader2, { className: "w-6 h-6 text-primary animate-spin" }) }),
3511
- /* @__PURE__ */ jsx(CardTitle, { children: "Signing you in..." }),
3512
- /* @__PURE__ */ jsxs(CardDescription, { children: [
3513
- "Please wait while we complete your ",
3514
- provider,
3515
- " authentication."
3516
- ] })
3517
- ] }),
3518
- status === "error" && /* @__PURE__ */ jsxs(Fragment, { children: [
3519
- /* @__PURE__ */ jsx("div", { className: "mx-auto w-12 h-12 bg-destructive/10 rounded-full flex items-center justify-center mb-4", children: /* @__PURE__ */ jsx(AlertCircle, { className: "w-6 h-6 text-destructive" }) }),
3520
- /* @__PURE__ */ jsx(CardTitle, { children: "Authentication Failed" }),
3521
- /* @__PURE__ */ jsx(CardDescription, { className: "text-destructive", children: errorMessage || githubError || "An error occurred during authentication." })
3522
- ] })
3523
- ] }),
3524
- status === "error" && /* @__PURE__ */ jsx(CardContent$1, { className: "text-center", children: /* @__PURE__ */ jsx(
3525
- "a",
3526
- {
3527
- href: "/auth",
3528
- className: "text-primary hover:underline text-sm",
3529
- children: "Try again"
3530
- }
3531
- ) })
3532
- ] }) });
3533
- }, "OAuthCallback");
3534
- var IdentifierForm = /* @__PURE__ */ __name(() => {
3535
- const {
3536
- identifier,
3537
- channel,
3538
- isLoading,
3539
- acceptedTerms,
3540
- termsUrl,
3541
- privacyUrl,
3542
- enablePhoneAuth,
3543
- setIdentifier,
3544
- setChannel,
3545
- setAcceptedTerms,
3546
- handleIdentifierSubmit,
3547
- detectChannelFromIdentifier,
3548
- validateIdentifier,
3549
- error
3550
- } = useAuthFormContext();
3551
- const [localChannel, setLocalChannel] = useState(channel);
3552
- useEffect(() => {
3553
- setLocalChannel(channel);
3554
- }, [channel]);
3555
- useEffect(() => {
3556
- if (!enablePhoneAuth && localChannel === "phone") {
3557
- setLocalChannel("email");
3558
- setChannel("email");
3559
- if (identifier && detectChannelFromIdentifier(identifier) === "phone") {
3560
- setIdentifier("");
3561
- }
3562
- }
3563
- }, [
3564
- enablePhoneAuth,
3565
- localChannel,
3566
- identifier,
3567
- setChannel,
3568
- setIdentifier,
3569
- detectChannelFromIdentifier
3570
- ]);
3571
- const handleIdentifierChange = /* @__PURE__ */ __name((value) => {
3572
- setIdentifier(value);
3573
- const detectedChannel = detectChannelFromIdentifier(value);
3574
- if (detectedChannel && detectedChannel !== localChannel) {
3575
- if (detectedChannel === "phone" && !enablePhoneAuth) {
3576
- return;
3577
- }
3578
- setLocalChannel(detectedChannel);
3579
- setChannel(detectedChannel);
3580
- }
3581
- }, "handleIdentifierChange");
3582
- const handleChannelChange = /* @__PURE__ */ __name((newChannel) => {
3583
- if (newChannel === "phone" && !enablePhoneAuth) {
3584
- return;
3585
- }
3586
- setLocalChannel(newChannel);
3587
- setChannel(newChannel);
3588
- if (identifier && !validateIdentifier(identifier, newChannel)) {
3589
- setIdentifier("");
3590
- }
3591
- }, "handleChannelChange");
3592
- const getChannelDescription = /* @__PURE__ */ __name(() => {
3593
- return localChannel === "phone" ? "Enter your phone number to receive a verification code via SMS" : "Enter your email address to receive a verification code";
3594
- }, "getChannelDescription");
3595
- const hasAnyLinks = Boolean(termsUrl || privacyUrl);
3596
- return /* @__PURE__ */ jsxs(Card$1, { className: "w-full max-w-md mx-auto shadow-lg border border-border bg-card/50 backdrop-blur-sm", children: [
3597
- /* @__PURE__ */ jsxs(CardHeader, { className: "text-center pb-6", children: [
3598
- /* @__PURE__ */ jsx("div", { className: "mx-auto w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-4", children: /* @__PURE__ */ jsx(User, { className: "w-6 h-6 text-primary" }) }),
3599
- /* @__PURE__ */ jsx(CardTitle, { className: "text-xl font-semibold", children: "Sign In" }),
3600
- /* @__PURE__ */ jsx(CardDescription, { className: "text-muted-foreground", children: getChannelDescription() })
3601
- ] }),
3602
- /* @__PURE__ */ jsxs(CardContent$1, { className: "space-y-6", children: [
3603
- enablePhoneAuth ? /* @__PURE__ */ jsxs(
3604
- Tabs,
3605
- {
3606
- value: localChannel,
3607
- onValueChange: (value) => handleChannelChange(value),
3608
- children: [
3609
- /* @__PURE__ */ jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [
3610
- /* @__PURE__ */ jsxs(TabsTrigger, { value: "email", className: "flex items-center gap-2", children: [
3611
- /* @__PURE__ */ jsx(Mail, { className: "w-4 h-4" }),
3612
- "Email"
3613
- ] }),
3614
- /* @__PURE__ */ jsxs(TabsTrigger, { value: "phone", className: "flex items-center gap-2", children: [
3615
- /* @__PURE__ */ jsx(Phone, { className: "w-4 h-4" }),
3616
- "Phone"
3617
- ] })
3618
- ] }),
3619
- /* @__PURE__ */ jsxs("form", { onSubmit: handleIdentifierSubmit, className: "space-y-6 mt-6", children: [
3620
- /* @__PURE__ */ jsxs(TabsContent, { value: "email", className: "space-y-3 mt-0", children: [
3621
- /* @__PURE__ */ jsxs(
3622
- Label,
3623
- {
3624
- htmlFor: "identifier",
3625
- className: "text-sm font-medium text-foreground flex items-center gap-2",
3626
- children: [
3627
- /* @__PURE__ */ jsx(Mail, { className: "w-4 h-4" }),
3628
- "Email Address"
3629
- ]
3630
- }
3631
- ),
3632
- /* @__PURE__ */ jsx(
3633
- Input,
3634
- {
3635
- id: "identifier",
3636
- type: "email",
3637
- placeholder: "Enter your email address",
3638
- value: identifier,
3639
- onChange: (e) => handleIdentifierChange(e.target.value),
3640
- disabled: isLoading,
3641
- required: true,
3642
- className: "h-11 text-base"
3643
- }
3644
- )
3645
- ] }),
3646
- /* @__PURE__ */ jsxs(TabsContent, { value: "phone", className: "space-y-3 mt-0", children: [
3647
- /* @__PURE__ */ jsxs(
3648
- Label,
3649
- {
3650
- htmlFor: "phone-identifier",
3651
- className: "text-sm font-medium text-foreground flex items-center gap-2",
3652
- children: [
3653
- /* @__PURE__ */ jsx(Phone, { className: "w-4 h-4" }),
3654
- "Phone Number"
3655
- ]
3656
- }
3657
- ),
3658
- /* @__PURE__ */ jsx(
3659
- PhoneInput,
3660
- {
3661
- value: identifier,
3662
- onChange: (value) => handleIdentifierChange(value || ""),
3663
- disabled: isLoading,
3664
- placeholder: "Enter your phone number",
3665
- defaultCountry: "US",
3666
- className: "h-11 text-base"
3667
- }
3668
- )
3669
- ] }),
3670
- hasAnyLinks && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3671
- /* @__PURE__ */ jsx(
3672
- Checkbox,
3673
- {
3674
- id: "terms",
3675
- checked: acceptedTerms,
3676
- onCheckedChange: setAcceptedTerms,
3677
- disabled: isLoading,
3678
- className: "mt-1"
3679
- }
3680
- ),
3681
- /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground leading-5", children: /* @__PURE__ */ jsxs(Label, { htmlFor: "terms", className: "cursor-pointer", children: [
3682
- "I agree to the",
3683
- " ",
3684
- termsUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
3685
- /* @__PURE__ */ jsx(
3686
- "a",
3687
- {
3688
- href: termsUrl,
3689
- target: "_blank",
3690
- rel: "noopener noreferrer",
3691
- className: "text-primary hover:underline font-medium",
3692
- children: "Terms of Service"
3693
- }
3694
- ),
3695
- privacyUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
3696
- " ",
3697
- "and",
3698
- " "
3699
- ] })
3700
- ] }),
3701
- privacyUrl && /* @__PURE__ */ jsx(
3702
- "a",
3703
- {
3704
- href: privacyUrl,
3705
- target: "_blank",
3706
- rel: "noopener noreferrer",
3707
- className: "text-primary hover:underline font-medium",
3708
- children: "Privacy Policy"
3709
- }
3710
- )
3711
- ] }) })
3712
- ] }),
3713
- error && /* @__PURE__ */ jsx("div", { className: "text-sm text-destructive bg-destructive/10 p-3 rounded-md border border-destructive/20", children: error }),
3714
- /* @__PURE__ */ jsxs(
3715
- Button,
3716
- {
3717
- type: "submit",
3718
- size: "lg",
3719
- className: "w-full",
3720
- disabled: !identifier || hasAnyLinks && !acceptedTerms,
3721
- loading: isLoading,
3722
- children: [
3723
- /* @__PURE__ */ jsx(Send, { className: "w-4 h-4" }),
3724
- "Send verification code"
3725
- ]
3726
- }
3727
- )
3728
- ] })
3729
- ]
3730
- }
3731
- ) : /* @__PURE__ */ jsxs("form", { onSubmit: handleIdentifierSubmit, className: "space-y-6 mt-6", children: [
3732
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3733
- /* @__PURE__ */ jsxs(
3734
- Label,
3735
- {
3736
- htmlFor: "email-only",
3737
- className: "text-sm font-medium text-foreground flex items-center gap-2",
3738
- children: [
3739
- /* @__PURE__ */ jsx(Mail, { className: "w-4 h-4" }),
3740
- "Email Address"
3741
- ]
3742
- }
3743
- ),
3744
- /* @__PURE__ */ jsx(
3745
- Input,
3746
- {
3747
- id: "email-only",
3748
- type: "email",
3749
- placeholder: "Enter your email address",
3750
- value: identifier,
3751
- onChange: (e) => handleIdentifierChange(e.target.value),
3752
- disabled: isLoading,
3753
- required: true,
3754
- className: "h-11 text-base"
3755
- }
3756
- )
3757
- ] }),
3758
- hasAnyLinks && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3759
- /* @__PURE__ */ jsx(
3760
- Checkbox,
3761
- {
3762
- id: "terms-email",
3763
- checked: acceptedTerms,
3764
- onCheckedChange: setAcceptedTerms,
3765
- disabled: isLoading,
3766
- className: "mt-1"
3767
- }
3768
- ),
3769
- /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground leading-5", children: /* @__PURE__ */ jsxs(Label, { htmlFor: "terms-email", className: "cursor-pointer", children: [
3770
- "I agree to the",
3771
- " ",
3772
- termsUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
3773
- /* @__PURE__ */ jsx(
3774
- "a",
3775
- {
3776
- href: termsUrl,
3777
- target: "_blank",
3778
- rel: "noopener noreferrer",
3779
- className: "text-primary hover:underline font-medium",
3780
- children: "Terms of Service"
3781
- }
3782
- ),
3783
- privacyUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
3784
- " ",
3785
- "and",
3786
- " "
3787
- ] })
3788
- ] }),
3789
- privacyUrl && /* @__PURE__ */ jsx(
3790
- "a",
3791
- {
3792
- href: privacyUrl,
3793
- target: "_blank",
3794
- rel: "noopener noreferrer",
3795
- className: "text-primary hover:underline font-medium",
3796
- children: "Privacy Policy"
3797
- }
3798
- )
3799
- ] }) })
3800
- ] }),
3801
- error && /* @__PURE__ */ jsx("div", { className: "text-sm text-destructive bg-destructive/10 p-3 rounded-md border border-destructive/20", children: error }),
3802
- /* @__PURE__ */ jsxs(
3803
- Button,
3804
- {
3805
- type: "submit",
3806
- size: "lg",
3807
- className: "w-full",
3808
- disabled: !identifier || hasAnyLinks && !acceptedTerms,
3809
- loading: isLoading,
3810
- children: [
3811
- /* @__PURE__ */ jsx(Send, { className: "w-4 h-4" }),
3812
- "Send verification code"
3813
- ]
3814
- }
3815
- )
3816
- ] }),
3817
- /* @__PURE__ */ jsx(OAuthProviders, {}),
3818
- /* @__PURE__ */ jsx(AuthHelp, {})
3819
- ] })
3820
- ] });
3821
- }, "IdentifierForm");
3822
- process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
3823
- var logger = createConsola({
3824
- level: 4
3825
- // dev: debug, production: errors only
3826
- }).withTag("layouts");
3827
- logger.withTag("auth");
3828
- logger.withTag("chat");
3829
- logger.withTag("support");
3830
- logger.withTag("payments");
3831
- var profileLogger = logger.withTag("profile");
3832
- logger.withTag("dashboard");
3833
- var OTPForm = /* @__PURE__ */ __name(() => {
3834
- const {
3835
- identifier,
3836
- channel,
3837
- otp,
3838
- isLoading,
3839
- error,
3840
- supportUrl,
3841
- setOtp,
3842
- handleOTPSubmit,
3843
- handleResendOTP,
3844
- handleBackToIdentifier,
3845
- isAutoSubmittingFromUrl
3846
- } = useAuthFormContext();
3847
- const isAutoSubmittingRef = useRef(false);
3848
- const handleOTPComplete = useCallback((completedValue) => {
3849
- if (isAutoSubmittingRef.current || isLoading || isAutoSubmittingFromUrl.current) return;
3850
- if (completedValue.length === 6) {
3851
- isAutoSubmittingRef.current = true;
3852
- const fakeEvent = {
3853
- preventDefault: /* @__PURE__ */ __name(() => {
3854
- }, "preventDefault")
3855
- };
3856
- setTimeout(async () => {
3857
- try {
3858
- await handleOTPSubmit(fakeEvent);
3859
- } finally {
3860
- isAutoSubmittingRef.current = false;
3861
- }
3862
- }, 100);
3863
- }
3864
- }, [handleOTPSubmit, isLoading, isAutoSubmittingFromUrl]);
3865
- const getChannelIcon = /* @__PURE__ */ __name(() => {
3866
- return channel === "phone" ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx(MessageCircle, { className: "w-5 h-5 text-primary" }) }) : /* @__PURE__ */ jsx(Mail, { className: "w-5 h-5 text-primary" });
3867
- }, "getChannelIcon");
3868
- const getChannelTitle = /* @__PURE__ */ __name(() => {
3869
- return channel === "phone" ? "Verify Your Phone" : "Verify Your Email";
3870
- }, "getChannelTitle");
3871
- const getChannelDescription = /* @__PURE__ */ __name(() => {
3872
- const channelName = channel === "phone" ? "phone number" : "email address";
3873
- const method = channel === "phone" ? "WhatsApp/SMS" : "email";
3874
- return `We've sent a 6-digit verification code to your ${channelName} via ${method}`;
3875
- }, "getChannelDescription");
3876
- return /* @__PURE__ */ jsxs(Card$1, { className: "w-full max-w-md mx-auto shadow-lg border border-border bg-card/50 backdrop-blur-sm", children: [
3877
- /* @__PURE__ */ jsxs(CardHeader, { className: "text-center pb-6", children: [
3878
- /* @__PURE__ */ jsx("div", { className: "mx-auto w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-4", children: getChannelIcon() }),
3879
- /* @__PURE__ */ jsx(CardTitle, { className: "text-xl font-semibold", children: getChannelTitle() }),
3880
- /* @__PURE__ */ jsxs(CardDescription, { className: "text-muted-foreground", children: [
3881
- getChannelDescription(),
3882
- /* @__PURE__ */ jsx("br", {}),
3883
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: identifier })
3884
- ] })
3885
- ] }),
3886
- /* @__PURE__ */ jsxs(CardContent$1, { className: "space-y-6", children: [
3887
- /* @__PURE__ */ jsxs("form", { onSubmit: handleOTPSubmit, className: "space-y-6", children: [
3888
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3889
- /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-foreground text-center block", children: "Enter verification code" }),
3890
- /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
3891
- OTPInput,
3892
- {
3893
- length: 6,
3894
- validationMode: "numeric",
3895
- pasteBehavior: "clean",
3896
- value: otp,
3897
- onChange: setOtp,
3898
- onComplete: handleOTPComplete,
3899
- disabled: isLoading,
3900
- autoFocus: true,
3901
- autoSubmit: false,
3902
- size: "lg"
3903
- }
3904
- ) }),
3905
- /* @__PURE__ */ jsx("div", { className: "text-xs text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/30 p-2 rounded-md border border-amber-200 dark:border-amber-800 text-center", children: "\u{1F527} Dev Mode: Any OTP code works" })
3906
- ] }),
3907
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
3908
- /* @__PURE__ */ jsxs(
3909
- Button,
3910
- {
3911
- type: "submit",
3912
- size: "lg",
3913
- className: "w-full",
3914
- disabled: otp.length < 6,
3915
- loading: isLoading,
3916
- children: [
3917
- /* @__PURE__ */ jsx(ShieldCheck, { className: "w-5 h-5" }),
3918
- "Verify Code"
3919
- ]
3920
- }
3921
- ),
3922
- /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
3923
- /* @__PURE__ */ jsxs(
3924
- Button,
3925
- {
3926
- type: "button",
3927
- variant: "outline",
3928
- onClick: handleBackToIdentifier,
3929
- disabled: isLoading,
3930
- className: "flex-1",
3931
- children: [
3932
- /* @__PURE__ */ jsx(ArrowLeft, { className: "w-4 h-4" }),
3933
- "Back"
3934
- ]
3935
- }
3936
- ),
3937
- /* @__PURE__ */ jsxs(
3938
- Button,
3939
- {
3940
- type: "button",
3941
- variant: "outline",
3942
- onClick: handleResendOTP,
3943
- disabled: isLoading,
3944
- className: "flex-1",
3945
- children: [
3946
- /* @__PURE__ */ jsx(RotateCw, { className: "w-4 h-4" }),
3947
- "Resend"
3948
- ]
3949
- }
3950
- )
3951
- ] })
3952
- ] })
3953
- ] }),
3954
- error && /* @__PURE__ */ jsx("div", { className: "text-sm text-destructive bg-destructive/10 p-3 rounded-md border border-destructive/20", children: error }),
3955
- supportUrl && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(AuthHelp, {}) })
3956
- ] })
3957
- ] });
3958
- }, "OTPForm");
3959
- var TwoFactorForm = /* @__PURE__ */ __name(() => {
3960
- const {
3961
- twoFactorCode,
3962
- useBackupCode,
3963
- error,
3964
- is2FALoading,
3965
- twoFactorWarning,
3966
- setTwoFactorCode,
3967
- handle2FASubmit,
3968
- handleUseBackupCode,
3969
- handleUseTOTP
3970
- } = useAuthFormContext();
3971
- return /* @__PURE__ */ jsxs(Card$1, { className: "w-full", children: [
3972
- /* @__PURE__ */ jsxs(CardHeader, { className: "space-y-1 text-center", children: [
3973
- /* @__PURE__ */ jsx("div", { className: "mx-auto w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-2", children: /* @__PURE__ */ jsx(ShieldCheck, { className: "w-6 h-6 text-primary" }) }),
3974
- /* @__PURE__ */ jsx(CardTitle, { className: "text-2xl", children: "Two-Factor Authentication" }),
3975
- /* @__PURE__ */ jsx(CardDescription, { children: useBackupCode ? "Enter one of your backup recovery codes" : "Enter the 6-digit code from your authenticator app" })
3976
- ] }),
3977
- /* @__PURE__ */ jsxs("form", { onSubmit: handle2FASubmit, children: [
3978
- /* @__PURE__ */ jsxs(CardContent$1, { className: "space-y-4", children: [
3979
- error && /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }),
3980
- twoFactorWarning && /* @__PURE__ */ jsx(Alert, { children: /* @__PURE__ */ jsx(AlertDescription, { children: twoFactorWarning }) }),
3981
- !useBackupCode && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
3982
- OTPInput,
3983
- {
3984
- length: 6,
3985
- validationMode: "numeric",
3986
- pasteBehavior: "clean",
3987
- value: twoFactorCode,
3988
- onChange: setTwoFactorCode,
3989
- disabled: is2FALoading,
3990
- autoFocus: true,
3991
- size: "lg"
3992
- }
3993
- ) }),
3994
- useBackupCode && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
3995
- /* @__PURE__ */ jsx(
3996
- Input,
3997
- {
3998
- type: "text",
3999
- placeholder: "Enter backup code",
4000
- value: twoFactorCode,
4001
- onChange: (e) => setTwoFactorCode(e.target.value.toUpperCase()),
4002
- disabled: is2FALoading,
4003
- className: "text-center font-mono text-lg tracking-widest",
4004
- maxLength: 12,
4005
- autoComplete: "off"
4006
- }
4007
- ),
4008
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground text-center", children: "Backup codes are 8 characters, letters and numbers" })
4009
- ] })
4010
- ] }),
4011
- /* @__PURE__ */ jsxs(CardFooter, { className: "flex flex-col space-y-3", children: [
4012
- /* @__PURE__ */ jsx(
4013
- Button,
4014
- {
4015
- type: "submit",
4016
- className: "w-full",
4017
- disabled: is2FALoading || !useBackupCode && twoFactorCode.length !== 6,
4018
- children: is2FALoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
4019
- /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
4020
- "Verifying..."
4021
- ] }) : "Verify"
4022
- }
4023
- ),
4024
- /* @__PURE__ */ jsxs(
4025
- Button,
4026
- {
4027
- type: "button",
4028
- variant: "ghost",
4029
- className: "w-full text-sm",
4030
- onClick: useBackupCode ? handleUseTOTP : handleUseBackupCode,
4031
- disabled: is2FALoading,
4032
- children: [
4033
- /* @__PURE__ */ jsx(KeyRound, { className: "mr-2 h-4 w-4" }),
4034
- useBackupCode ? "Use authenticator app instead" : "Can't access your authenticator? Use a backup code"
4035
- ]
4036
- }
4037
- )
4038
- ] })
4039
- ] })
4040
- ] });
4041
- }, "TwoFactorForm");
4042
- var TwoFactorSetup = /* @__PURE__ */ __name(({
4043
- onComplete,
4044
- onSkip,
4045
- onError,
4046
- deviceName
4047
- }) => {
4048
- const [confirmCode, setConfirmCode] = useState("");
4049
- const [showSecret, setShowSecret] = useState(false);
4050
- const [copiedSecret, setCopiedSecret] = useState(false);
4051
- const [copiedBackupCodes, setCopiedBackupCodes] = useState(false);
4052
- const {
4053
- isLoading,
4054
- error,
4055
- setupData,
4056
- backupCodes,
4057
- backupCodesWarning,
4058
- setupStep,
4059
- startSetup,
4060
- confirmSetup,
4061
- resetSetup
4062
- } = useTwoFactorSetup({
4063
- onComplete,
4064
- onError
4065
- });
4066
- React.useEffect(() => {
4067
- if (setupStep === "idle") {
4068
- startSetup(deviceName);
4069
- }
4070
- }, [setupStep, startSetup, deviceName]);
4071
- const handleConfirm = /* @__PURE__ */ __name(async (e) => {
4072
- e.preventDefault();
4073
- await confirmSetup(confirmCode);
4074
- }, "handleConfirm");
4075
- const copySecret = /* @__PURE__ */ __name(async () => {
4076
- if (setupData?.secret) {
4077
- await navigator.clipboard.writeText(setupData.secret);
4078
- setCopiedSecret(true);
4079
- setTimeout(() => setCopiedSecret(false), 2e3);
4080
- }
4081
- }, "copySecret");
4082
- const copyBackupCodes = /* @__PURE__ */ __name(async () => {
4083
- if (backupCodes) {
4084
- await navigator.clipboard.writeText(backupCodes.join("\n"));
4085
- setCopiedBackupCodes(true);
4086
- setTimeout(() => setCopiedBackupCodes(false), 2e3);
4087
- }
4088
- }, "copyBackupCodes");
4089
- if (isLoading && !setupData) {
4090
- return /* @__PURE__ */ jsx(Card$1, { className: "w-full max-w-md mx-auto", children: /* @__PURE__ */ jsx(CardContent$1, { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(Loader2, { className: "w-8 h-8 animate-spin text-primary" }) }) });
4091
- }
4092
- if (setupStep === "complete" && backupCodes) {
4093
- return /* @__PURE__ */ jsxs(Card$1, { className: "w-full max-w-md mx-auto", children: [
4094
- /* @__PURE__ */ jsxs(CardHeader, { className: "space-y-1 text-center", children: [
4095
- /* @__PURE__ */ jsx("div", { className: "mx-auto w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-full flex items-center justify-center mb-2", children: /* @__PURE__ */ jsx(CheckCircle, { className: "w-6 h-6 text-green-600 dark:text-green-400" }) }),
4096
- /* @__PURE__ */ jsx(CardTitle, { className: "text-2xl", children: "2FA Enabled!" }),
4097
- /* @__PURE__ */ jsx(CardDescription, { children: "Save these backup codes in a secure place" })
4098
- ] }),
4099
- /* @__PURE__ */ jsxs(CardContent$1, { className: "space-y-4", children: [
4100
- backupCodesWarning && /* @__PURE__ */ jsx(Alert, { children: /* @__PURE__ */ jsx(AlertDescription, { children: backupCodesWarning }) }),
4101
- /* @__PURE__ */ jsx("div", { className: "bg-muted rounded-lg p-4", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2 font-mono text-sm", children: backupCodes.map((code, index) => /* @__PURE__ */ jsx("div", { className: "text-center py-1", children: code }, index)) }) }),
4102
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground text-center", children: "Each code can only be used once. Store them securely." })
4103
- ] }),
4104
- /* @__PURE__ */ jsxs(CardFooter, { className: "flex flex-col space-y-3", children: [
4105
- /* @__PURE__ */ jsxs(
4106
- Button,
4107
- {
4108
- type: "button",
4109
- variant: "outline",
4110
- className: "w-full",
4111
- onClick: copyBackupCodes,
4112
- children: [
4113
- /* @__PURE__ */ jsx(Copy, { className: "mr-2 h-4 w-4" }),
4114
- copiedBackupCodes ? "Copied!" : "Copy all codes"
4115
- ]
4116
- }
4117
- ),
4118
- /* @__PURE__ */ jsx(
4119
- Button,
4120
- {
4121
- type: "button",
4122
- className: "w-full",
4123
- onClick: () => onComplete?.(backupCodes),
4124
- children: "I've saved my backup codes"
4125
- }
4126
- )
4127
- ] })
4128
- ] });
4129
- }
4130
- return /* @__PURE__ */ jsxs(Card$1, { className: "w-full max-w-md mx-auto", children: [
4131
- /* @__PURE__ */ jsxs(CardHeader, { className: "space-y-1 text-center", children: [
4132
- /* @__PURE__ */ jsx("div", { className: "mx-auto w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-2", children: /* @__PURE__ */ jsx(ShieldCheck, { className: "w-6 h-6 text-primary" }) }),
4133
- /* @__PURE__ */ jsx(CardTitle, { className: "text-2xl", children: "Set Up 2FA" }),
4134
- /* @__PURE__ */ jsx(CardDescription, { children: "Scan this QR code with your authenticator app" })
4135
- ] }),
4136
- /* @__PURE__ */ jsxs("form", { onSubmit: handleConfirm, children: [
4137
- /* @__PURE__ */ jsxs(CardContent$1, { className: "space-y-6", children: [
4138
- error && /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }),
4139
- setupData && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx("div", { className: "bg-white p-4 rounded-lg", children: /* @__PURE__ */ jsx(
4140
- QRCodeSVG,
4141
- {
4142
- value: setupData.provisioningUri,
4143
- size: 200,
4144
- level: "M",
4145
- marginSize: 0
4146
- }
4147
- ) }) }),
4148
- setupData && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
4149
- /* @__PURE__ */ jsxs(
4150
- Button,
4151
- {
4152
- type: "button",
4153
- variant: "ghost",
4154
- size: "sm",
4155
- className: "w-full text-xs",
4156
- onClick: () => setShowSecret(!showSecret),
4157
- children: [
4158
- showSecret ? /* @__PURE__ */ jsx(EyeOff, { className: "mr-2 h-3 w-3" }) : /* @__PURE__ */ jsx(Eye, { className: "mr-2 h-3 w-3" }),
4159
- showSecret ? "Hide" : "Show",
4160
- " manual entry code"
4161
- ]
4162
- }
4163
- ),
4164
- showSecret && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 bg-muted rounded-lg p-3", children: [
4165
- /* @__PURE__ */ jsx("code", { className: "flex-1 text-xs font-mono break-all", children: setupData.secret }),
4166
- /* @__PURE__ */ jsx(
4167
- Button,
4168
- {
4169
- type: "button",
4170
- variant: "ghost",
4171
- size: "sm",
4172
- onClick: copySecret,
4173
- children: /* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" })
4174
- }
4175
- )
4176
- ] })
4177
- ] }),
4178
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
4179
- /* @__PURE__ */ jsx("p", { className: "text-sm text-center text-muted-foreground", children: "Enter the 6-digit code from your app to confirm" }),
4180
- /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
4181
- OTPInput,
4182
- {
4183
- length: 6,
4184
- validationMode: "numeric",
4185
- pasteBehavior: "clean",
4186
- value: confirmCode,
4187
- onChange: setConfirmCode,
4188
- disabled: isLoading,
4189
- autoFocus: true,
4190
- size: "lg"
4191
- }
4192
- ) })
4193
- ] })
4194
- ] }),
4195
- /* @__PURE__ */ jsxs(CardFooter, { className: "flex flex-col space-y-3", children: [
4196
- /* @__PURE__ */ jsx(
4197
- Button,
4198
- {
4199
- type: "submit",
4200
- className: "w-full",
4201
- disabled: isLoading || confirmCode.length !== 6,
4202
- children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
4203
- /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
4204
- "Verifying..."
4205
- ] }) : "Confirm & Enable 2FA"
4206
- }
4207
- ),
4208
- onSkip && /* @__PURE__ */ jsx(
4209
- Button,
4210
- {
4211
- type: "button",
4212
- variant: "ghost",
4213
- className: "w-full",
4214
- onClick: onSkip,
4215
- disabled: isLoading,
4216
- children: "Skip for now"
4217
- }
4218
- )
4219
- ] })
4220
- ] })
4221
- ] });
4222
- }, "TwoFactorSetup");
4223
- var AuthSuccess = /* @__PURE__ */ __name(({ className, redirectDelay = 1500 }) => {
4224
- const { logoUrl, redirectUrl } = useAuthFormContext();
4225
- const router = useCfgRouter();
4226
- const [isVisible, setIsVisible] = useState(false);
4227
- useEffect(() => {
4228
- const animTimer = setTimeout(() => setIsVisible(true), 50);
4229
- const redirectTimer = setTimeout(() => {
4230
- const finalUrl = redirectUrl || "/dashboard";
4231
- router.hardPush(finalUrl);
4232
- }, redirectDelay);
4233
- return () => {
4234
- clearTimeout(animTimer);
4235
- clearTimeout(redirectTimer);
4236
- };
4237
- }, [redirectUrl, redirectDelay, router]);
4238
- if (!logoUrl) {
4239
- return /* @__PURE__ */ jsx("div", { className: `fixed inset-0 flex items-center justify-center bg-background z-50 ${className || ""}`, children: /* @__PURE__ */ jsx(
4240
- "div",
4241
- {
4242
- className: `transition-all duration-700 ease-out ${isVisible ? "opacity-100 scale-100" : "opacity-0 scale-95"}`,
4243
- children: /* @__PURE__ */ jsx("div", { className: "w-24 h-24 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center", children: /* @__PURE__ */ jsx(
4244
- "svg",
4245
- {
4246
- className: "w-12 h-12 text-green-600 dark:text-green-400",
4247
- fill: "none",
4248
- stroke: "currentColor",
4249
- viewBox: "0 0 24 24",
4250
- children: /* @__PURE__ */ jsx(
4251
- "path",
4252
- {
4253
- strokeLinecap: "round",
4254
- strokeLinejoin: "round",
4255
- strokeWidth: 2,
4256
- d: "M5 13l4 4L19 7"
4257
- }
4258
- )
4259
- }
4260
- ) })
4261
- }
4262
- ) });
4263
- }
4264
- return /* @__PURE__ */ jsx("div", { className: `fixed inset-0 flex items-center justify-center bg-background z-50 ${className || ""}`, children: /* @__PURE__ */ jsx(
4265
- "div",
4266
- {
4267
- className: `transition-all duration-700 ease-out ${isVisible ? "opacity-100 scale-100" : "opacity-0 scale-90"}`,
4268
- children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4269
- /* @__PURE__ */ jsx(
4270
- "div",
4271
- {
4272
- className: `absolute inset-0 blur-3xl transition-opacity duration-1000 ${isVisible ? "opacity-20" : "opacity-0"}`,
4273
- style: {
4274
- background: "radial-gradient(circle, currentColor 0%, transparent 70%)"
4275
- }
4276
- }
4277
- ),
4278
- /* @__PURE__ */ jsx(
4279
- "img",
4280
- {
4281
- src: logoUrl,
4282
- alt: "Success",
4283
- className: "relative w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 object-contain"
4284
- }
4285
- )
4286
- ] })
4287
- }
4288
- ) });
4289
- }, "AuthSuccess");
4290
- var AuthLayout = /* @__PURE__ */ __name((props) => {
4291
- const { enableGithubAuth, redirectUrl = "/dashboard", onOAuthSuccess, onError, className } = props;
4292
- return /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsxs(AuthFormProvider, { ...props, children: [
4293
- /* @__PURE__ */ jsx(AuthSuccessOverlay, {}),
4294
- /* @__PURE__ */ jsxs(
4295
- "div",
4296
- {
4297
- className: `min-h-screen flex flex-col items-center justify-center bg-background py-6 px-4 sm:py-12 sm:px-6 lg:px-8 ${className || ""}`,
4298
- children: [
4299
- enableGithubAuth && /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
4300
- OAuthCallback,
4301
- {
4302
- redirectUrl,
4303
- onSuccess: onOAuthSuccess ? (user, isNewUser) => onOAuthSuccess(user, isNewUser, "github") : void 0,
4304
- onError
4305
- }
4306
- ) }),
4307
- /* @__PURE__ */ jsxs("div", { className: "w-full sm:max-w-md space-y-8", children: [
4308
- props.children,
4309
- /* @__PURE__ */ jsx(AuthContent, {})
4310
- ] })
4311
- ]
4312
- }
4313
- )
4314
- ] }) });
4315
- }, "AuthLayout");
4316
- var AuthContent = /* @__PURE__ */ __name(() => {
4317
- const { step, setStep } = useAuthFormContext();
4318
- switch (step) {
4319
- case "identifier":
4320
- return /* @__PURE__ */ jsx(IdentifierForm, {});
4321
- case "otp":
4322
- return /* @__PURE__ */ jsx(OTPForm, {});
4323
- case "2fa":
4324
- return /* @__PURE__ */ jsx(TwoFactorForm, {});
4325
- case "2fa-setup":
4326
- return /* @__PURE__ */ jsx(
4327
- TwoFactorSetup,
4328
- {
4329
- onComplete: () => setStep("success"),
4330
- onSkip: () => setStep("success")
4331
- }
4332
- );
4333
- case "success":
4334
- return null;
4335
- default:
4336
- return /* @__PURE__ */ jsx(IdentifierForm, {});
4337
- }
4338
- }, "AuthContent");
4339
- var AuthSuccessOverlay = /* @__PURE__ */ __name(() => {
4340
- const { step } = useAuthFormContext();
4341
- if (step !== "success") {
4342
- return null;
4343
- }
4344
- return /* @__PURE__ */ jsx(AuthSuccess, {});
4345
- }, "AuthSuccessOverlay");
4346
- function AdminLayout(props) {
4347
- return /* @__PURE__ */ jsx(PrivateLayout, { ...props });
4348
- }
4349
- __name(AdminLayout, "AdminLayout");
4350
- var SettingsGroup = /* @__PURE__ */ __name(({ children, className }) => /* @__PURE__ */ jsx("div", { className: cn("bg-card rounded-xl overflow-hidden divide-y divide-border", className), children }), "SettingsGroup");
4351
- var SettingsGroupHeader = /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("div", { className: "px-4 py-2 text-xs font-medium text-muted-foreground uppercase tracking-wider", children }), "SettingsGroupHeader");
4352
- var SettingsRow = /* @__PURE__ */ __name(({
4353
- icon,
4354
- label,
4355
- value,
4356
- onClick,
4357
- destructive,
4358
- disabled,
4359
- showChevron = !!onClick,
4360
- className
4361
- }) => {
4362
- const Wrapper = onClick ? "button" : "div";
4363
- return /* @__PURE__ */ jsxs(
4364
- Wrapper,
4365
- {
4366
- onClick: disabled ? void 0 : onClick,
4367
- disabled,
4368
- className: cn(
4369
- "w-full flex items-center gap-3 px-4 py-3 text-left transition-colors",
4370
- onClick && !disabled && "hover:bg-muted/50 active:bg-muted cursor-pointer",
4371
- disabled && "opacity-50 cursor-not-allowed",
4372
- destructive && "text-destructive",
4373
- className
4374
- ),
4375
- children: [
4376
- icon && /* @__PURE__ */ jsx("span", { className: cn("shrink-0", destructive ? "text-destructive" : "text-muted-foreground"), children: icon }),
4377
- /* @__PURE__ */ jsx("span", { className: "flex-1 font-medium", children: label }),
4378
- value && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm truncate max-w-[180px]", children: value }),
4379
- showChevron && /* @__PURE__ */ jsx(ChevronRight, { className: "w-4 h-4 text-muted-foreground/50 shrink-0" })
4380
- ]
4381
- }
4382
- );
4383
- }, "SettingsRow");
4384
- var fieldLabels = {
4385
- first_name: "First Name",
4386
- last_name: "Last Name",
4387
- company: "Company",
4388
- position: "Position",
4389
- phone: "Phone"
4390
- };
4391
- var EditFieldDialog = /* @__PURE__ */ __name(({ open, onOpenChange, field, currentValue, onSave, isSaving }) => {
4392
- const [value, setValue] = useState(currentValue);
4393
- useEffect(() => {
4394
- setValue(currentValue);
4395
- }, [currentValue, open]);
4396
- const handleSave = /* @__PURE__ */ __name(async () => {
4397
- if (field) {
4398
- await onSave(field, value);
4399
- }
4400
- }, "handleSave");
4401
- if (!field) return null;
4402
- return /* @__PURE__ */ jsx(Dialog$1, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent$1, { className: "sm:max-w-md", children: [
4403
- /* @__PURE__ */ jsx(DialogHeader$1, { children: /* @__PURE__ */ jsx(DialogTitle$1, { children: fieldLabels[field] }) }),
4404
- /* @__PURE__ */ jsx("div", { className: "py-4", children: /* @__PURE__ */ jsx(
4405
- Input,
4406
- {
4407
- value,
4408
- onChange: (e) => setValue(e.target.value),
4409
- placeholder: `Enter ${fieldLabels[field].toLowerCase()}`,
4410
- autoFocus: true,
4411
- onKeyDown: (e) => e.key === "Enter" && handleSave()
4412
- }
4413
- ) }),
4414
- /* @__PURE__ */ jsxs(DialogFooter$1, { children: [
4415
- /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), disabled: isSaving, children: "Cancel" }),
4416
- /* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: isSaving, children: isSaving ? "Saving..." : "Save" })
4417
- ] })
4418
- ] }) });
4419
- }, "EditFieldDialog");
4420
- var ProfileContent = /* @__PURE__ */ __name(({
4421
- onUnauthenticated,
4422
- title = "Profile",
4423
- enable2FA = false,
4424
- enableDeleteAccount = true
4425
- }) => {
4426
- const { user, isLoading, logout, uploadAvatar, updateProfile } = useAuth();
4427
- const [editingField, setEditingField] = useState(null);
4428
- const [isSaving, setIsSaving] = useState(false);
4429
- const [isUploading, setIsUploading] = useState(false);
4430
- const [show2FASetup, setShow2FASetup] = useState(false);
4431
- const { has2FAEnabled, fetchStatus: fetch2FAStatus } = useTwoFactorStatus();
4432
- const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
4433
- const form = useForm({
4434
- resolver: zodResolver(PatchedUserProfileUpdateRequestSchema),
4435
- defaultValues: {
4436
- first_name: "",
4437
- last_name: "",
4438
- company: "",
4439
- position: "",
4440
- phone: ""
4441
- }
4442
- });
4443
- useEffect(() => {
4444
- if (user) {
4445
- form.reset({
4446
- first_name: user.first_name || "",
4447
- last_name: user.last_name || "",
4448
- company: user.company || "",
4449
- position: user.position || "",
4450
- phone: user.phone || ""
4451
- });
4452
- }
4453
- }, [user, form]);
4454
- useEffect(() => {
4455
- if (enable2FA) {
4456
- fetch2FAStatus();
4457
- }
4458
- }, [enable2FA, fetch2FAStatus]);
4459
- useEffect(() => {
4460
- if (onUnauthenticated && !user && !isLoading) {
4461
- onUnauthenticated();
4462
- }
4463
- }, [onUnauthenticated, user, isLoading]);
4464
- const getInitials = /* @__PURE__ */ __name((name) => {
4465
- if (!name) return "U";
4466
- return name.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2);
4467
- }, "getInitials");
4468
- const handleAvatarChange = /* @__PURE__ */ __name(async (event) => {
4469
- const file = event.target.files?.[0];
4470
- if (!file) return;
4471
- if (!file.type.startsWith("image/")) {
4472
- toast.error("Please select an image file");
4473
- return;
4474
- }
4475
- if (file.size > 5 * 1024 * 1024) {
4476
- toast.error("File size must be less than 5MB");
4477
- return;
4478
- }
4479
- setIsUploading(true);
4480
- try {
4481
- await uploadAvatar(file);
4482
- toast.success("Avatar updated");
4483
- } catch (error) {
4484
- toast.error("Failed to upload avatar");
4485
- profileLogger.error("Avatar upload error:", error);
4486
- } finally {
4487
- setIsUploading(false);
4488
- }
4489
- }, "handleAvatarChange");
4490
- const handleFieldSave = /* @__PURE__ */ __name(async (field, value) => {
4491
- setIsSaving(true);
4492
- try {
4493
- await updateProfile({ [field]: value });
4494
- toast.success("Profile updated");
4495
- setEditingField(null);
4496
- } catch (error) {
4497
- profileLogger.error("Profile update error:", error);
4498
- toast.error(error?.response?.data?.[field]?.[0] || "Failed to update");
4499
- } finally {
4500
- setIsSaving(false);
4501
- }
4502
- }, "handleFieldSave");
4503
- const handleLogout = /* @__PURE__ */ __name(() => {
4504
- logout();
4505
- }, "handleLogout");
4506
- if (isLoading) {
4507
- return /* @__PURE__ */ jsx(
4508
- Preloader,
4509
- {
4510
- variant: "fullscreen",
4511
- text: "Loading...",
4512
- size: "lg",
4513
- backdrop: true,
4514
- backdropOpacity: 80
4515
- }
4516
- );
4517
- }
4518
- if (!user) {
4519
- return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
4520
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold mb-4", children: "Not Authenticated" }),
4521
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Please log in to view your profile." })
4522
- ] }) });
4523
- }
4524
- if (show2FASetup) {
4525
- return /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4 py-8 max-w-lg", children: /* @__PURE__ */ jsx(
4526
- TwoFactorSetup,
4527
- {
4528
- onComplete: () => {
4529
- setShow2FASetup(false);
4530
- fetch2FAStatus();
4531
- },
4532
- onSkip: () => setShow2FASetup(false)
4533
- }
4534
- ) });
4535
- }
4536
- return /* @__PURE__ */ jsxs("div", { className: "container mx-auto px-4 py-8 max-w-lg", children: [
4537
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-center mb-8", children: title }),
4538
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center mb-8", children: [
4539
- /* @__PURE__ */ jsxs("div", { className: "relative group mb-3", children: [
4540
- /* @__PURE__ */ jsx(Avatar, { className: "w-24 h-24 text-3xl", children: user.avatar ? /* @__PURE__ */ jsx("img", { src: user.avatar, alt: "Avatar", className: "w-full h-full object-cover" }) : /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-primary text-primary-foreground", children: getInitials(user.display_username || user.email || "") }) }),
4541
- /* @__PURE__ */ jsxs("label", { className: "absolute inset-0 rounded-full bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center cursor-pointer", children: [
4542
- isUploading ? /* @__PURE__ */ jsx("div", { className: "w-6 h-6 border-2 border-white border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ jsx(Camera, { className: "w-6 h-6 text-white" }),
4543
- /* @__PURE__ */ jsx(
4544
- "input",
4545
- {
4546
- type: "file",
4547
- accept: "image/*",
4548
- onChange: handleAvatarChange,
4549
- className: "hidden",
4550
- disabled: isUploading
4551
- }
4552
- )
4553
- ] })
4554
- ] }),
4555
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold", children: user.display_username || user.email }),
4556
- user.date_joined && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
4557
- "Member since ",
4558
- moment.utc(user.date_joined).local().format("MMMM YYYY")
4559
- ] })
4560
- ] }),
4561
- /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
4562
- /* @__PURE__ */ jsxs("div", { children: [
4563
- /* @__PURE__ */ jsx(SettingsGroupHeader, { children: "Account" }),
4564
- /* @__PURE__ */ jsx(SettingsGroup, { children: /* @__PURE__ */ jsx(
4565
- SettingsRow,
4566
- {
4567
- icon: /* @__PURE__ */ jsx(Mail, { className: "w-5 h-5" }),
4568
- label: "Email",
4569
- value: user.email,
4570
- showChevron: false
4571
- }
4572
- ) })
4573
- ] }),
4574
- /* @__PURE__ */ jsxs("div", { children: [
4575
- /* @__PURE__ */ jsx(SettingsGroupHeader, { children: "Personal Information" }),
4576
- /* @__PURE__ */ jsxs(SettingsGroup, { children: [
4577
- /* @__PURE__ */ jsx(
4578
- SettingsRow,
4579
- {
4580
- icon: /* @__PURE__ */ jsx(User, { className: "w-5 h-5" }),
4581
- label: "First Name",
4582
- value: user.first_name || "Not set",
4583
- onClick: () => setEditingField("first_name")
4584
- }
4585
- ),
4586
- /* @__PURE__ */ jsx(
4587
- SettingsRow,
4588
- {
4589
- icon: /* @__PURE__ */ jsx(User, { className: "w-5 h-5" }),
4590
- label: "Last Name",
4591
- value: user.last_name || "Not set",
4592
- onClick: () => setEditingField("last_name")
4593
- }
4594
- ),
4595
- /* @__PURE__ */ jsx(
4596
- SettingsRow,
4597
- {
4598
- icon: /* @__PURE__ */ jsx(Phone, { className: "w-5 h-5" }),
4599
- label: "Phone",
4600
- value: user.phone || "Not set",
4601
- onClick: () => setEditingField("phone")
4602
- }
4603
- )
4604
- ] })
4605
- ] }),
4606
- /* @__PURE__ */ jsxs("div", { children: [
4607
- /* @__PURE__ */ jsx(SettingsGroupHeader, { children: "Work" }),
4608
- /* @__PURE__ */ jsxs(SettingsGroup, { children: [
4609
- /* @__PURE__ */ jsx(
4610
- SettingsRow,
4611
- {
4612
- icon: /* @__PURE__ */ jsx(Building2, { className: "w-5 h-5" }),
4613
- label: "Company",
4614
- value: user.company || "Not set",
4615
- onClick: () => setEditingField("company")
4616
- }
4617
- ),
4618
- /* @__PURE__ */ jsx(
4619
- SettingsRow,
4620
- {
4621
- icon: /* @__PURE__ */ jsx(Briefcase, { className: "w-5 h-5" }),
4622
- label: "Position",
4623
- value: user.position || "Not set",
4624
- onClick: () => setEditingField("position")
4625
- }
4626
- )
4627
- ] })
4628
- ] }),
4629
- enable2FA && /* @__PURE__ */ jsxs("div", { children: [
4630
- /* @__PURE__ */ jsx(SettingsGroupHeader, { children: "Security" }),
4631
- /* @__PURE__ */ jsx(SettingsGroup, { children: /* @__PURE__ */ jsx(
4632
- SettingsRow,
4633
- {
4634
- icon: has2FAEnabled ? /* @__PURE__ */ jsx(ShieldCheck, { className: "w-5 h-5 text-green-500" }) : /* @__PURE__ */ jsx(Shield, { className: "w-5 h-5" }),
4635
- label: "Two-Factor Authentication",
4636
- value: has2FAEnabled ? "On" : "Off",
4637
- onClick: () => !has2FAEnabled && setShow2FASetup(true),
4638
- showChevron: !has2FAEnabled
4639
- }
4640
- ) })
4641
- ] }),
4642
- /* @__PURE__ */ jsxs("div", { children: [
4643
- /* @__PURE__ */ jsx(SettingsGroupHeader, { children: "Actions" }),
4644
- /* @__PURE__ */ jsxs(SettingsGroup, { children: [
4645
- /* @__PURE__ */ jsx(
4646
- SettingsRow,
4647
- {
4648
- icon: /* @__PURE__ */ jsx(LogOut, { className: "w-5 h-5" }),
4649
- label: "Sign Out",
4650
- onClick: handleLogout
4651
- }
4652
- ),
4653
- enableDeleteAccount && /* @__PURE__ */ jsx(
4654
- SettingsRow,
4655
- {
4656
- icon: /* @__PURE__ */ jsx(Trash2, { className: "w-5 h-5" }),
4657
- label: "Delete Account",
4658
- onClick: () => setShowDeleteConfirm(true),
4659
- destructive: true
4660
- }
4661
- )
4662
- ] })
4663
- ] })
4664
- ] }),
4665
- /* @__PURE__ */ jsx(
4666
- EditFieldDialog,
4667
- {
4668
- open: editingField !== null,
4669
- onOpenChange: (open) => !open && setEditingField(null),
4670
- field: editingField,
4671
- currentValue: editingField ? user[editingField] || "" : "",
4672
- onSave: handleFieldSave,
4673
- isSaving
4674
- }
4675
- ),
4676
- /* @__PURE__ */ jsx(
4677
- DeleteAccountDialog,
4678
- {
4679
- open: showDeleteConfirm,
4680
- onOpenChange: setShowDeleteConfirm
4681
- }
4682
- )
4683
- ] });
4684
- }, "ProfileContent");
4685
- var CONFIRMATION_TEXT = "DELETE";
4686
- var DeleteAccountDialog = /* @__PURE__ */ __name(({ open, onOpenChange }) => {
4687
- const [confirmationInput, setConfirmationInput] = useState("");
4688
- const { logout } = useAuth();
4689
- const { isLoading, error, deleteAccount, clearError } = useDeleteAccount();
4690
- useEffect(() => {
4691
- if (open) {
4692
- setConfirmationInput("");
4693
- clearError();
4694
- }
4695
- }, [open, clearError]);
4696
- const handleDelete = /* @__PURE__ */ __name(async () => {
4697
- const result = await deleteAccount();
4698
- if (result.success) {
4699
- onOpenChange(false);
4700
- await logout({ skipConfirm: true });
4701
- }
4702
- }, "handleDelete");
4703
- const isValid = confirmationInput.toUpperCase() === CONFIRMATION_TEXT;
4704
- return /* @__PURE__ */ jsx(Dialog$1, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent$1, { children: [
4705
- /* @__PURE__ */ jsxs(DialogHeader$1, { children: [
4706
- /* @__PURE__ */ jsx(DialogTitle$1, { className: "text-destructive", children: "Delete Account" }),
4707
- /* @__PURE__ */ jsx(DialogDescription$1, { children: "This action cannot be undone. Your account will be permanently deleted." })
4708
- ] }),
4709
- /* @__PURE__ */ jsxs("div", { className: "py-4 space-y-4", children: [
4710
- error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
4711
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
4712
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
4713
- "Type ",
4714
- /* @__PURE__ */ jsx("span", { className: "font-mono font-bold", children: CONFIRMATION_TEXT }),
4715
- " to confirm:"
4716
- ] }),
4717
- /* @__PURE__ */ jsx(
4718
- Input,
4719
- {
4720
- value: confirmationInput,
4721
- onChange: (e) => setConfirmationInput(e.target.value),
4722
- placeholder: CONFIRMATION_TEXT,
4723
- disabled: isLoading,
4724
- autoComplete: "off"
4725
- }
4726
- )
4727
- ] })
4728
- ] }),
4729
- /* @__PURE__ */ jsxs(DialogFooter$1, { children: [
4730
- /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), disabled: isLoading, children: "Cancel" }),
4731
- /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: handleDelete, disabled: isLoading || !isValid, children: isLoading ? "Deleting..." : "Delete Account" })
4732
- ] })
4733
- ] }) });
4734
- }, "DeleteAccountDialog");
4735
- var ProfileLayout = /* @__PURE__ */ __name((props) => {
4736
- return /* @__PURE__ */ jsx(ProfileContent, { ...props });
4737
- }, "ProfileLayout");
4738
-
4739
- export { AdminLayout, AppLayout, AuthFormProvider, AuthHelp, AuthLayout, BaseApp, DjangoCFGLogo, FooterBottom, FooterMenuSections, FooterProjectInfo, FooterSocialLinksComponent, IdentifierForm, OAuthCallback, OAuthProviders, OTPForm, PrivateLayout, ProfileLayout, PublicFooter, PublicLayout, UserMenu, useAuthFormContext };
4740
- //# sourceMappingURL=layouts.mjs.map
4741
- //# sourceMappingURL=layouts.mjs.map