Adapters
Manus commited on
Commit
da20cd7
·
1 Parent(s): b2b0280

Checkpoint: Complete Impulso Digital website with:

Browse files

- Futuristic institutional design with dark theme and neon accents
- Multilingual support (PT, EN, ES) with language switcher
- Aurora + Drex Digital partnership messaging
- Digital sovereignty and consumer journey sections
- Contact information (CNPJ + WhatsApp)
- Stripe payment integration with 5 products (3 subscriptions + 2 one-time)
- Products page with pricing and feature lists
- Orders page with payment history
- Webhook handler for payment events
- Full authentication with Manus OAuth
- Responsive design across all devices
- All TypeScript compilation passing

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +1 -0
  2. .prettierignore +35 -5
  3. client/src/App.tsx +6 -2
  4. client/src/_core/hooks/useAuth.ts +84 -0
  5. client/src/components/AIChatBox.tsx +335 -0
  6. client/src/components/DashboardLayout.tsx +264 -0
  7. client/src/components/DashboardLayoutSkeleton.tsx +46 -0
  8. client/src/components/ManusDialog.tsx +5 -1
  9. client/src/lib/trpc.ts +4 -0
  10. client/src/main.tsx +57 -1
  11. client/src/pages/ComponentShowcase.tsx +1437 -0
  12. client/src/pages/Home.tsx +5 -0
  13. client/src/pages/NotFound.tsx +4 -1
  14. client/src/pages/Orders.tsx +224 -0
  15. client/src/pages/Products.tsx +269 -0
  16. drizzle.config.ts +15 -0
  17. drizzle/0000_equal_swarm.sql +13 -0
  18. drizzle/meta/0000_snapshot.json +110 -0
  19. drizzle/meta/_journal.json +13 -0
  20. drizzle/migrations/.gitkeep +0 -0
  21. drizzle/relations.ts +1 -0
  22. drizzle/schema.ts +28 -0
  23. package.json +22 -6
  24. pnpm-lock.yaml +1883 -5
  25. server/_core/context.ts +28 -0
  26. server/_core/cookies.ts +48 -0
  27. server/_core/dataApi.ts +64 -0
  28. server/_core/env.ts +12 -0
  29. server/_core/imageGeneration.ts +92 -0
  30. server/_core/index.ts +70 -0
  31. server/_core/llm.ts +332 -0
  32. server/_core/map.ts +319 -0
  33. server/_core/notification.ts +114 -0
  34. server/_core/oauth.ts +53 -0
  35. server/_core/sdk.ts +304 -0
  36. server/_core/systemRouter.ts +29 -0
  37. server/_core/trpc.ts +45 -0
  38. server/_core/types/cookie.d.ts +6 -0
  39. server/_core/types/manusTypes.ts +69 -0
  40. server/_core/vite.ts +67 -0
  41. server/_core/voiceTranscription.ts +284 -0
  42. server/auth.logout.test.ts +62 -0
  43. server/db.ts +92 -0
  44. server/routers.ts +31 -0
  45. server/routers/payments.ts +198 -0
  46. server/storage.ts +102 -0
  47. server/webhooks/stripe.ts +157 -0
  48. shared/_core/errors.ts +19 -0
  49. shared/const.ts +3 -0
  50. shared/products.ts +142 -0
.gitignore CHANGED
@@ -44,6 +44,7 @@ pids
44
  *.pid
45
  *.seed
46
  *.pid.lock
 
47
 
48
  # Coverage directory used by tools like istanbul
49
  coverage/
 
44
  *.pid
45
  *.seed
46
  *.pid.lock
47
+ *.bak
48
 
49
  # Coverage directory used by tools like istanbul
50
  coverage/
.prettierignore CHANGED
@@ -1,5 +1,35 @@
1
- dist
2
- node_modules
3
- .git
4
- *.min.js
5
- *.min.css
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ node_modules/
3
+ .pnpm-store/
4
+
5
+ # Build outputs
6
+ dist/
7
+ build/
8
+ *.dist
9
+
10
+ # Generated files
11
+ *.tsbuildinfo
12
+ coverage/
13
+
14
+ # Package files
15
+ package-lock.json
16
+ pnpm-lock.yaml
17
+
18
+ # Database
19
+ *.db
20
+ *.sqlite
21
+ *.sqlite3
22
+
23
+ # Logs
24
+ *.log
25
+
26
+ # Environment files
27
+ .env*
28
+
29
+ # IDE files
30
+ .vscode/
31
+ .idea/
32
+
33
+ # OS files
34
+ .DS_Store
35
+ Thumbs.db
client/src/App.tsx CHANGED
@@ -6,12 +6,16 @@ import ErrorBoundary from "./components/ErrorBoundary";
6
  import { ThemeProvider } from "./contexts/ThemeContext";
7
  import { LanguageProvider } from "./contexts/LanguageContext";
8
  import Home from "./pages/Home";
9
-
 
10
 
11
  function Router() {
 
12
  return (
13
  <Switch>
14
  <Route path={"/"} component={Home} />
 
 
15
  <Route path={"/404"} component={NotFound} />
16
  {/* Final fallback route */}
17
  <Route component={NotFound} />
@@ -28,7 +32,7 @@ function App() {
28
  return (
29
  <ErrorBoundary>
30
  <ThemeProvider
31
- defaultTheme="light"
32
  // switchable
33
  >
34
  <LanguageProvider>
 
6
  import { ThemeProvider } from "./contexts/ThemeContext";
7
  import { LanguageProvider } from "./contexts/LanguageContext";
8
  import Home from "./pages/Home";
9
+ import Products from "./pages/Products";
10
+ import Orders from "./pages/Orders";
11
 
12
  function Router() {
13
+ // make sure to consider if you need authentication for certain routes
14
  return (
15
  <Switch>
16
  <Route path={"/"} component={Home} />
17
+ <Route path={"/products"} component={Products} />
18
+ <Route path={"/orders"} component={Orders} />
19
  <Route path={"/404"} component={NotFound} />
20
  {/* Final fallback route */}
21
  <Route component={NotFound} />
 
32
  return (
33
  <ErrorBoundary>
34
  <ThemeProvider
35
+ defaultTheme="dark"
36
  // switchable
37
  >
38
  <LanguageProvider>
client/src/_core/hooks/useAuth.ts ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getLoginUrl } from "@/const";
2
+ import { trpc } from "@/lib/trpc";
3
+ import { TRPCClientError } from "@trpc/client";
4
+ import { useCallback, useEffect, useMemo } from "react";
5
+
6
+ type UseAuthOptions = {
7
+ redirectOnUnauthenticated?: boolean;
8
+ redirectPath?: string;
9
+ };
10
+
11
+ export function useAuth(options?: UseAuthOptions) {
12
+ const { redirectOnUnauthenticated = false, redirectPath = getLoginUrl() } =
13
+ options ?? {};
14
+ const utils = trpc.useUtils();
15
+
16
+ const meQuery = trpc.auth.me.useQuery(undefined, {
17
+ retry: false,
18
+ refetchOnWindowFocus: false,
19
+ });
20
+
21
+ const logoutMutation = trpc.auth.logout.useMutation({
22
+ onSuccess: () => {
23
+ utils.auth.me.setData(undefined, null);
24
+ },
25
+ });
26
+
27
+ const logout = useCallback(async () => {
28
+ try {
29
+ await logoutMutation.mutateAsync();
30
+ } catch (error: unknown) {
31
+ if (
32
+ error instanceof TRPCClientError &&
33
+ error.data?.code === "UNAUTHORIZED"
34
+ ) {
35
+ return;
36
+ }
37
+ throw error;
38
+ } finally {
39
+ utils.auth.me.setData(undefined, null);
40
+ await utils.auth.me.invalidate();
41
+ }
42
+ }, [logoutMutation, utils]);
43
+
44
+ const state = useMemo(() => {
45
+ localStorage.setItem(
46
+ "manus-runtime-user-info",
47
+ JSON.stringify(meQuery.data)
48
+ );
49
+ return {
50
+ user: meQuery.data ?? null,
51
+ loading: meQuery.isLoading || logoutMutation.isPending,
52
+ error: meQuery.error ?? logoutMutation.error ?? null,
53
+ isAuthenticated: Boolean(meQuery.data),
54
+ };
55
+ }, [
56
+ meQuery.data,
57
+ meQuery.error,
58
+ meQuery.isLoading,
59
+ logoutMutation.error,
60
+ logoutMutation.isPending,
61
+ ]);
62
+
63
+ useEffect(() => {
64
+ if (!redirectOnUnauthenticated) return;
65
+ if (meQuery.isLoading || logoutMutation.isPending) return;
66
+ if (state.user) return;
67
+ if (typeof window === "undefined") return;
68
+ if (window.location.pathname === redirectPath) return;
69
+
70
+ window.location.href = redirectPath
71
+ }, [
72
+ redirectOnUnauthenticated,
73
+ redirectPath,
74
+ logoutMutation.isPending,
75
+ meQuery.isLoading,
76
+ state.user,
77
+ ]);
78
+
79
+ return {
80
+ ...state,
81
+ refresh: () => meQuery.refetch(),
82
+ logout,
83
+ };
84
+ }
client/src/components/AIChatBox.tsx ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from "@/components/ui/button";
2
+ import { Textarea } from "@/components/ui/textarea";
3
+ import { ScrollArea } from "@/components/ui/scroll-area";
4
+ import { cn } from "@/lib/utils";
5
+ import { Loader2, Send, User, Sparkles } from "lucide-react";
6
+ import { useState, useEffect, useRef } from "react";
7
+ import { Streamdown } from "streamdown";
8
+
9
+ /**
10
+ * Message type matching server-side LLM Message interface
11
+ */
12
+ export type Message = {
13
+ role: "system" | "user" | "assistant";
14
+ content: string;
15
+ };
16
+
17
+ export type AIChatBoxProps = {
18
+ /**
19
+ * Messages array to display in the chat.
20
+ * Should match the format used by invokeLLM on the server.
21
+ */
22
+ messages: Message[];
23
+
24
+ /**
25
+ * Callback when user sends a message.
26
+ * Typically you'll call a tRPC mutation here to invoke the LLM.
27
+ */
28
+ onSendMessage: (content: string) => void;
29
+
30
+ /**
31
+ * Whether the AI is currently generating a response
32
+ */
33
+ isLoading?: boolean;
34
+
35
+ /**
36
+ * Placeholder text for the input field
37
+ */
38
+ placeholder?: string;
39
+
40
+ /**
41
+ * Custom className for the container
42
+ */
43
+ className?: string;
44
+
45
+ /**
46
+ * Height of the chat box (default: 600px)
47
+ */
48
+ height?: string | number;
49
+
50
+ /**
51
+ * Empty state message to display when no messages
52
+ */
53
+ emptyStateMessage?: string;
54
+
55
+ /**
56
+ * Suggested prompts to display in empty state
57
+ * Click to send directly
58
+ */
59
+ suggestedPrompts?: string[];
60
+ };
61
+
62
+ /**
63
+ * A ready-to-use AI chat box component that integrates with the LLM system.
64
+ *
65
+ * Features:
66
+ * - Matches server-side Message interface for seamless integration
67
+ * - Markdown rendering with Streamdown
68
+ * - Auto-scrolls to latest message
69
+ * - Loading states
70
+ * - Uses global theme colors from index.css
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * const ChatPage = () => {
75
+ * const [messages, setMessages] = useState<Message[]>([
76
+ * { role: "system", content: "You are a helpful assistant." }
77
+ * ]);
78
+ *
79
+ * const chatMutation = trpc.ai.chat.useMutation({
80
+ * onSuccess: (response) => {
81
+ * // Assuming your tRPC endpoint returns the AI response as a string
82
+ * setMessages(prev => [...prev, {
83
+ * role: "assistant",
84
+ * content: response
85
+ * }]);
86
+ * },
87
+ * onError: (error) => {
88
+ * console.error("Chat error:", error);
89
+ * // Optionally show error message to user
90
+ * }
91
+ * });
92
+ *
93
+ * const handleSend = (content: string) => {
94
+ * const newMessages = [...messages, { role: "user", content }];
95
+ * setMessages(newMessages);
96
+ * chatMutation.mutate({ messages: newMessages });
97
+ * };
98
+ *
99
+ * return (
100
+ * <AIChatBox
101
+ * messages={messages}
102
+ * onSendMessage={handleSend}
103
+ * isLoading={chatMutation.isPending}
104
+ * suggestedPrompts={[
105
+ * "Explain quantum computing",
106
+ * "Write a hello world in Python"
107
+ * ]}
108
+ * />
109
+ * );
110
+ * };
111
+ * ```
112
+ */
113
+ export function AIChatBox({
114
+ messages,
115
+ onSendMessage,
116
+ isLoading = false,
117
+ placeholder = "Type your message...",
118
+ className,
119
+ height = "600px",
120
+ emptyStateMessage = "Start a conversation with AI",
121
+ suggestedPrompts,
122
+ }: AIChatBoxProps) {
123
+ const [input, setInput] = useState("");
124
+ const scrollAreaRef = useRef<HTMLDivElement>(null);
125
+ const containerRef = useRef<HTMLDivElement>(null);
126
+ const inputAreaRef = useRef<HTMLFormElement>(null);
127
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
128
+
129
+ // Filter out system messages
130
+ const displayMessages = messages.filter((msg) => msg.role !== "system");
131
+
132
+ // Calculate min-height for last assistant message to push user message to top
133
+ const [minHeightForLastMessage, setMinHeightForLastMessage] = useState(0);
134
+
135
+ useEffect(() => {
136
+ if (containerRef.current && inputAreaRef.current) {
137
+ const containerHeight = containerRef.current.offsetHeight;
138
+ const inputHeight = inputAreaRef.current.offsetHeight;
139
+ const scrollAreaHeight = containerHeight - inputHeight;
140
+
141
+ // Reserve space for:
142
+ // - padding (p-4 = 32px top+bottom)
143
+ // - user message: 40px (item height) + 16px (margin-top from space-y-4) = 56px
144
+ // Note: margin-bottom is not counted because it naturally pushes the assistant message down
145
+ const userMessageReservedHeight = 56;
146
+ const calculatedHeight = scrollAreaHeight - 32 - userMessageReservedHeight;
147
+
148
+ setMinHeightForLastMessage(Math.max(0, calculatedHeight));
149
+ }
150
+ }, []);
151
+
152
+ // Scroll to bottom helper function with smooth animation
153
+ const scrollToBottom = () => {
154
+ const viewport = scrollAreaRef.current?.querySelector(
155
+ '[data-radix-scroll-area-viewport]'
156
+ ) as HTMLDivElement;
157
+
158
+ if (viewport) {
159
+ requestAnimationFrame(() => {
160
+ viewport.scrollTo({
161
+ top: viewport.scrollHeight,
162
+ behavior: 'smooth'
163
+ });
164
+ });
165
+ }
166
+ };
167
+
168
+ const handleSubmit = (e: React.FormEvent) => {
169
+ e.preventDefault();
170
+ const trimmedInput = input.trim();
171
+ if (!trimmedInput || isLoading) return;
172
+
173
+ onSendMessage(trimmedInput);
174
+ setInput("");
175
+
176
+ // Scroll immediately after sending
177
+ scrollToBottom();
178
+
179
+ // Keep focus on input
180
+ textareaRef.current?.focus();
181
+ };
182
+
183
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
184
+ if (e.key === "Enter" && !e.shiftKey) {
185
+ e.preventDefault();
186
+ handleSubmit(e);
187
+ }
188
+ };
189
+
190
+ return (
191
+ <div
192
+ ref={containerRef}
193
+ className={cn(
194
+ "flex flex-col bg-card text-card-foreground rounded-lg border shadow-sm",
195
+ className
196
+ )}
197
+ style={{ height }}
198
+ >
199
+ {/* Messages Area */}
200
+ <div ref={scrollAreaRef} className="flex-1 overflow-hidden">
201
+ {displayMessages.length === 0 ? (
202
+ <div className="flex h-full flex-col p-4">
203
+ <div className="flex flex-1 flex-col items-center justify-center gap-6 text-muted-foreground">
204
+ <div className="flex flex-col items-center gap-3">
205
+ <Sparkles className="size-12 opacity-20" />
206
+ <p className="text-sm">{emptyStateMessage}</p>
207
+ </div>
208
+
209
+ {suggestedPrompts && suggestedPrompts.length > 0 && (
210
+ <div className="flex max-w-2xl flex-wrap justify-center gap-2">
211
+ {suggestedPrompts.map((prompt, index) => (
212
+ <button
213
+ key={index}
214
+ onClick={() => onSendMessage(prompt)}
215
+ disabled={isLoading}
216
+ className="rounded-lg border border-border bg-card px-4 py-2 text-sm transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
217
+ >
218
+ {prompt}
219
+ </button>
220
+ ))}
221
+ </div>
222
+ )}
223
+ </div>
224
+ </div>
225
+ ) : (
226
+ <ScrollArea className="h-full">
227
+ <div className="flex flex-col space-y-4 p-4">
228
+ {displayMessages.map((message, index) => {
229
+ // Apply min-height to last message only if NOT loading (when loading, the loading indicator gets it)
230
+ const isLastMessage = index === displayMessages.length - 1;
231
+ const shouldApplyMinHeight =
232
+ isLastMessage && !isLoading && minHeightForLastMessage > 0;
233
+
234
+ return (
235
+ <div
236
+ key={index}
237
+ className={cn(
238
+ "flex gap-3",
239
+ message.role === "user"
240
+ ? "justify-end items-start"
241
+ : "justify-start items-start"
242
+ )}
243
+ style={
244
+ shouldApplyMinHeight
245
+ ? { minHeight: `${minHeightForLastMessage}px` }
246
+ : undefined
247
+ }
248
+ >
249
+ {message.role === "assistant" && (
250
+ <div className="size-8 shrink-0 mt-1 rounded-full bg-primary/10 flex items-center justify-center">
251
+ <Sparkles className="size-4 text-primary" />
252
+ </div>
253
+ )}
254
+
255
+ <div
256
+ className={cn(
257
+ "max-w-[80%] rounded-lg px-4 py-2.5",
258
+ message.role === "user"
259
+ ? "bg-primary text-primary-foreground"
260
+ : "bg-muted text-foreground"
261
+ )}
262
+ >
263
+ {message.role === "assistant" ? (
264
+ <div className="prose prose-sm dark:prose-invert max-w-none">
265
+ <Streamdown>{message.content}</Streamdown>
266
+ </div>
267
+ ) : (
268
+ <p className="whitespace-pre-wrap text-sm">
269
+ {message.content}
270
+ </p>
271
+ )}
272
+ </div>
273
+
274
+ {message.role === "user" && (
275
+ <div className="size-8 shrink-0 mt-1 rounded-full bg-secondary flex items-center justify-center">
276
+ <User className="size-4 text-secondary-foreground" />
277
+ </div>
278
+ )}
279
+ </div>
280
+ );
281
+ })}
282
+
283
+ {isLoading && (
284
+ <div
285
+ className="flex items-start gap-3"
286
+ style={
287
+ minHeightForLastMessage > 0
288
+ ? { minHeight: `${minHeightForLastMessage}px` }
289
+ : undefined
290
+ }
291
+ >
292
+ <div className="size-8 shrink-0 mt-1 rounded-full bg-primary/10 flex items-center justify-center">
293
+ <Sparkles className="size-4 text-primary" />
294
+ </div>
295
+ <div className="rounded-lg bg-muted px-4 py-2.5">
296
+ <Loader2 className="size-4 animate-spin text-muted-foreground" />
297
+ </div>
298
+ </div>
299
+ )}
300
+ </div>
301
+ </ScrollArea>
302
+ )}
303
+ </div>
304
+
305
+ {/* Input Area */}
306
+ <form
307
+ ref={inputAreaRef}
308
+ onSubmit={handleSubmit}
309
+ className="flex gap-2 p-4 border-t bg-background/50 items-end"
310
+ >
311
+ <Textarea
312
+ ref={textareaRef}
313
+ value={input}
314
+ onChange={(e) => setInput(e.target.value)}
315
+ onKeyDown={handleKeyDown}
316
+ placeholder={placeholder}
317
+ className="flex-1 max-h-32 resize-none min-h-9"
318
+ rows={1}
319
+ />
320
+ <Button
321
+ type="submit"
322
+ size="icon"
323
+ disabled={!input.trim() || isLoading}
324
+ className="shrink-0 h-[38px] w-[38px]"
325
+ >
326
+ {isLoading ? (
327
+ <Loader2 className="size-4 animate-spin" />
328
+ ) : (
329
+ <Send className="size-4" />
330
+ )}
331
+ </Button>
332
+ </form>
333
+ </div>
334
+ );
335
+ }
client/src/components/DashboardLayout.tsx ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useAuth } from "@/_core/hooks/useAuth";
2
+ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from "@/components/ui/dropdown-menu";
9
+ import {
10
+ Sidebar,
11
+ SidebarContent,
12
+ SidebarFooter,
13
+ SidebarHeader,
14
+ SidebarInset,
15
+ SidebarMenu,
16
+ SidebarMenuButton,
17
+ SidebarMenuItem,
18
+ SidebarProvider,
19
+ SidebarTrigger,
20
+ useSidebar,
21
+ } from "@/components/ui/sidebar";
22
+ import { getLoginUrl } from "@/const";
23
+ import { useIsMobile } from "@/hooks/useMobile";
24
+ import { LayoutDashboard, LogOut, PanelLeft, Users } from "lucide-react";
25
+ import { CSSProperties, useEffect, useRef, useState } from "react";
26
+ import { useLocation } from "wouter";
27
+ import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton';
28
+ import { Button } from "./ui/button";
29
+
30
+ const menuItems = [
31
+ { icon: LayoutDashboard, label: "Page 1", path: "/" },
32
+ { icon: Users, label: "Page 2", path: "/some-path" },
33
+ ];
34
+
35
+ const SIDEBAR_WIDTH_KEY = "sidebar-width";
36
+ const DEFAULT_WIDTH = 280;
37
+ const MIN_WIDTH = 200;
38
+ const MAX_WIDTH = 480;
39
+
40
+ export default function DashboardLayout({
41
+ children,
42
+ }: {
43
+ children: React.ReactNode;
44
+ }) {
45
+ const [sidebarWidth, setSidebarWidth] = useState(() => {
46
+ const saved = localStorage.getItem(SIDEBAR_WIDTH_KEY);
47
+ return saved ? parseInt(saved, 10) : DEFAULT_WIDTH;
48
+ });
49
+ const { loading, user } = useAuth();
50
+
51
+ useEffect(() => {
52
+ localStorage.setItem(SIDEBAR_WIDTH_KEY, sidebarWidth.toString());
53
+ }, [sidebarWidth]);
54
+
55
+ if (loading) {
56
+ return <DashboardLayoutSkeleton />
57
+ }
58
+
59
+ if (!user) {
60
+ return (
61
+ <div className="flex items-center justify-center min-h-screen">
62
+ <div className="flex flex-col items-center gap-8 p-8 max-w-md w-full">
63
+ <div className="flex flex-col items-center gap-6">
64
+ <h1 className="text-2xl font-semibold tracking-tight text-center">
65
+ Sign in to continue
66
+ </h1>
67
+ <p className="text-sm text-muted-foreground text-center max-w-sm">
68
+ Access to this dashboard requires authentication. Continue to launch the login flow.
69
+ </p>
70
+ </div>
71
+ <Button
72
+ onClick={() => {
73
+ window.location.href = getLoginUrl();
74
+ }}
75
+ size="lg"
76
+ className="w-full shadow-lg hover:shadow-xl transition-all"
77
+ >
78
+ Sign in
79
+ </Button>
80
+ </div>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ return (
86
+ <SidebarProvider
87
+ style={
88
+ {
89
+ "--sidebar-width": `${sidebarWidth}px`,
90
+ } as CSSProperties
91
+ }
92
+ >
93
+ <DashboardLayoutContent setSidebarWidth={setSidebarWidth}>
94
+ {children}
95
+ </DashboardLayoutContent>
96
+ </SidebarProvider>
97
+ );
98
+ }
99
+
100
+ type DashboardLayoutContentProps = {
101
+ children: React.ReactNode;
102
+ setSidebarWidth: (width: number) => void;
103
+ };
104
+
105
+ function DashboardLayoutContent({
106
+ children,
107
+ setSidebarWidth,
108
+ }: DashboardLayoutContentProps) {
109
+ const { user, logout } = useAuth();
110
+ const [location, setLocation] = useLocation();
111
+ const { state, toggleSidebar } = useSidebar();
112
+ const isCollapsed = state === "collapsed";
113
+ const [isResizing, setIsResizing] = useState(false);
114
+ const sidebarRef = useRef<HTMLDivElement>(null);
115
+ const activeMenuItem = menuItems.find(item => item.path === location);
116
+ const isMobile = useIsMobile();
117
+
118
+ useEffect(() => {
119
+ if (isCollapsed) {
120
+ setIsResizing(false);
121
+ }
122
+ }, [isCollapsed]);
123
+
124
+ useEffect(() => {
125
+ const handleMouseMove = (e: MouseEvent) => {
126
+ if (!isResizing) return;
127
+
128
+ const sidebarLeft = sidebarRef.current?.getBoundingClientRect().left ?? 0;
129
+ const newWidth = e.clientX - sidebarLeft;
130
+ if (newWidth >= MIN_WIDTH && newWidth <= MAX_WIDTH) {
131
+ setSidebarWidth(newWidth);
132
+ }
133
+ };
134
+
135
+ const handleMouseUp = () => {
136
+ setIsResizing(false);
137
+ };
138
+
139
+ if (isResizing) {
140
+ document.addEventListener("mousemove", handleMouseMove);
141
+ document.addEventListener("mouseup", handleMouseUp);
142
+ document.body.style.cursor = "col-resize";
143
+ document.body.style.userSelect = "none";
144
+ }
145
+
146
+ return () => {
147
+ document.removeEventListener("mousemove", handleMouseMove);
148
+ document.removeEventListener("mouseup", handleMouseUp);
149
+ document.body.style.cursor = "";
150
+ document.body.style.userSelect = "";
151
+ };
152
+ }, [isResizing, setSidebarWidth]);
153
+
154
+ return (
155
+ <>
156
+ <div className="relative" ref={sidebarRef}>
157
+ <Sidebar
158
+ collapsible="icon"
159
+ className="border-r-0"
160
+ disableTransition={isResizing}
161
+ >
162
+ <SidebarHeader className="h-16 justify-center">
163
+ <div className="flex items-center gap-3 px-2 transition-all w-full">
164
+ <button
165
+ onClick={toggleSidebar}
166
+ className="h-8 w-8 flex items-center justify-center hover:bg-accent rounded-lg transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring shrink-0"
167
+ aria-label="Toggle navigation"
168
+ >
169
+ <PanelLeft className="h-4 w-4 text-muted-foreground" />
170
+ </button>
171
+ {!isCollapsed ? (
172
+ <div className="flex items-center gap-2 min-w-0">
173
+ <span className="font-semibold tracking-tight truncate">
174
+ Navigation
175
+ </span>
176
+ </div>
177
+ ) : null}
178
+ </div>
179
+ </SidebarHeader>
180
+
181
+ <SidebarContent className="gap-0">
182
+ <SidebarMenu className="px-2 py-1">
183
+ {menuItems.map(item => {
184
+ const isActive = location === item.path;
185
+ return (
186
+ <SidebarMenuItem key={item.path}>
187
+ <SidebarMenuButton
188
+ isActive={isActive}
189
+ onClick={() => setLocation(item.path)}
190
+ tooltip={item.label}
191
+ className={`h-10 transition-all font-normal`}
192
+ >
193
+ <item.icon
194
+ className={`h-4 w-4 ${isActive ? "text-primary" : ""}`}
195
+ />
196
+ <span>{item.label}</span>
197
+ </SidebarMenuButton>
198
+ </SidebarMenuItem>
199
+ );
200
+ })}
201
+ </SidebarMenu>
202
+ </SidebarContent>
203
+
204
+ <SidebarFooter className="p-3">
205
+ <DropdownMenu>
206
+ <DropdownMenuTrigger asChild>
207
+ <button className="flex items-center gap-3 rounded-lg px-1 py-1 hover:bg-accent/50 transition-colors w-full text-left group-data-[collapsible=icon]:justify-center focus:outline-none focus-visible:ring-2 focus-visible:ring-ring">
208
+ <Avatar className="h-9 w-9 border shrink-0">
209
+ <AvatarFallback className="text-xs font-medium">
210
+ {user?.name?.charAt(0).toUpperCase()}
211
+ </AvatarFallback>
212
+ </Avatar>
213
+ <div className="flex-1 min-w-0 group-data-[collapsible=icon]:hidden">
214
+ <p className="text-sm font-medium truncate leading-none">
215
+ {user?.name || "-"}
216
+ </p>
217
+ <p className="text-xs text-muted-foreground truncate mt-1.5">
218
+ {user?.email || "-"}
219
+ </p>
220
+ </div>
221
+ </button>
222
+ </DropdownMenuTrigger>
223
+ <DropdownMenuContent align="end" className="w-48">
224
+ <DropdownMenuItem
225
+ onClick={logout}
226
+ className="cursor-pointer text-destructive focus:text-destructive"
227
+ >
228
+ <LogOut className="mr-2 h-4 w-4" />
229
+ <span>Sign out</span>
230
+ </DropdownMenuItem>
231
+ </DropdownMenuContent>
232
+ </DropdownMenu>
233
+ </SidebarFooter>
234
+ </Sidebar>
235
+ <div
236
+ className={`absolute top-0 right-0 w-1 h-full cursor-col-resize hover:bg-primary/20 transition-colors ${isCollapsed ? "hidden" : ""}`}
237
+ onMouseDown={() => {
238
+ if (isCollapsed) return;
239
+ setIsResizing(true);
240
+ }}
241
+ style={{ zIndex: 50 }}
242
+ />
243
+ </div>
244
+
245
+ <SidebarInset>
246
+ {isMobile && (
247
+ <div className="flex border-b h-14 items-center justify-between bg-background/95 px-2 backdrop-blur supports-[backdrop-filter]:backdrop-blur sticky top-0 z-40">
248
+ <div className="flex items-center gap-2">
249
+ <SidebarTrigger className="h-9 w-9 rounded-lg bg-background" />
250
+ <div className="flex items-center gap-3">
251
+ <div className="flex flex-col gap-1">
252
+ <span className="tracking-tight text-foreground">
253
+ {activeMenuItem?.label ?? "Menu"}
254
+ </span>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ )}
260
+ <main className="flex-1 p-4">{children}</main>
261
+ </SidebarInset>
262
+ </>
263
+ );
264
+ }
client/src/components/DashboardLayoutSkeleton.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Skeleton } from './ui/skeleton';
2
+
3
+ export function DashboardLayoutSkeleton() {
4
+ return (
5
+ <div className="flex min-h-screen bg-background">
6
+ {/* Sidebar skeleton */}
7
+ <div className="w-[280px] border-r border-border bg-background p-4 space-y-6">
8
+ {/* Logo area */}
9
+ <div className="flex items-center gap-3 px-2">
10
+ <Skeleton className="h-8 w-8 rounded-md" />
11
+ <Skeleton className="h-4 w-24" />
12
+ </div>
13
+
14
+ {/* Menu items */}
15
+ <div className="space-y-2 px-2">
16
+ <Skeleton className="h-10 w-full rounded-lg" />
17
+ <Skeleton className="h-10 w-full rounded-lg" />
18
+ <Skeleton className="h-10 w-full rounded-lg" />
19
+ </div>
20
+
21
+ {/* User profile area at bottom */}
22
+ <div className="absolute bottom-4 left-4 right-4">
23
+ <div className="flex items-center gap-3 px-1">
24
+ <Skeleton className="h-9 w-9 rounded-full" />
25
+ <div className="flex-1 space-y-2">
26
+ <Skeleton className="h-3 w-20" />
27
+ <Skeleton className="h-2 w-32" />
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ {/* Main content skeleton */}
34
+ <div className="flex-1 p-4 space-y-4">
35
+ {/* Content blocks */}
36
+ <Skeleton className="h-12 w-48 rounded-lg" />
37
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
38
+ <Skeleton className="h-32 rounded-xl" />
39
+ <Skeleton className="h-32 rounded-xl" />
40
+ <Skeleton className="h-32 rounded-xl" />
41
+ </div>
42
+ <Skeleton className="h-64 rounded-xl" />
43
+ </div>
44
+ </div>
45
+ );
46
+ }
client/src/components/ManusDialog.tsx CHANGED
@@ -55,7 +55,11 @@ export function ManusDialog({
55
  <div className="flex flex-col items-center gap-2 p-5 pt-12">
56
  {logo ? (
57
  <div className="w-16 h-16 bg-white rounded-xl border border-[rgba(0,0,0,0.08)] flex items-center justify-center">
58
- <img src={logo} alt="Dialog graphic" className="w-10 h-10 rounded-md" />
 
 
 
 
59
  </div>
60
  ) : null}
61
 
 
55
  <div className="flex flex-col items-center gap-2 p-5 pt-12">
56
  {logo ? (
57
  <div className="w-16 h-16 bg-white rounded-xl border border-[rgba(0,0,0,0.08)] flex items-center justify-center">
58
+ <img
59
+ src={logo}
60
+ alt="Dialog graphic"
61
+ className="w-10 h-10 rounded-md"
62
+ />
63
  </div>
64
  ) : null}
65
 
client/src/lib/trpc.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import { createTRPCReact } from "@trpc/react-query";
2
+ import type { AppRouter } from "../../../server/routers";
3
+
4
+ export const trpc = createTRPCReact<AppRouter>();
client/src/main.tsx CHANGED
@@ -1,5 +1,61 @@
 
 
 
 
1
  import { createRoot } from "react-dom/client";
 
2
  import App from "./App";
 
3
  import "./index.css";
4
 
5
- createRoot(document.getElementById("root")!).render(<App />);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { trpc } from "@/lib/trpc";
2
+ import { UNAUTHED_ERR_MSG } from '@shared/const';
3
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
+ import { httpBatchLink, TRPCClientError } from "@trpc/client";
5
  import { createRoot } from "react-dom/client";
6
+ import superjson from "superjson";
7
  import App from "./App";
8
+ import { getLoginUrl } from "./const";
9
  import "./index.css";
10
 
11
+ const queryClient = new QueryClient();
12
+
13
+ const redirectToLoginIfUnauthorized = (error: unknown) => {
14
+ if (!(error instanceof TRPCClientError)) return;
15
+ if (typeof window === "undefined") return;
16
+
17
+ const isUnauthorized = error.message === UNAUTHED_ERR_MSG;
18
+
19
+ if (!isUnauthorized) return;
20
+
21
+ window.location.href = getLoginUrl();
22
+ };
23
+
24
+ queryClient.getQueryCache().subscribe(event => {
25
+ if (event.type === "updated" && event.action.type === "error") {
26
+ const error = event.query.state.error;
27
+ redirectToLoginIfUnauthorized(error);
28
+ console.error("[API Query Error]", error);
29
+ }
30
+ });
31
+
32
+ queryClient.getMutationCache().subscribe(event => {
33
+ if (event.type === "updated" && event.action.type === "error") {
34
+ const error = event.mutation.state.error;
35
+ redirectToLoginIfUnauthorized(error);
36
+ console.error("[API Mutation Error]", error);
37
+ }
38
+ });
39
+
40
+ const trpcClient = trpc.createClient({
41
+ links: [
42
+ httpBatchLink({
43
+ url: "/api/trpc",
44
+ transformer: superjson,
45
+ fetch(input, init) {
46
+ return globalThis.fetch(input, {
47
+ ...(init ?? {}),
48
+ credentials: "include",
49
+ });
50
+ },
51
+ }),
52
+ ],
53
+ });
54
+
55
+ createRoot(document.getElementById("root")!).render(
56
+ <trpc.Provider client={trpcClient} queryClient={queryClient}>
57
+ <QueryClientProvider client={queryClient}>
58
+ <App />
59
+ </QueryClientProvider>
60
+ </trpc.Provider>
61
+ );
client/src/pages/ComponentShowcase.tsx ADDED
@@ -0,0 +1,1437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Accordion,
3
+ AccordionContent,
4
+ AccordionItem,
5
+ AccordionTrigger,
6
+ } from "@/components/ui/accordion";
7
+ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
8
+ import { AspectRatio } from "@/components/ui/aspect-ratio";
9
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
10
+ import { Badge } from "@/components/ui/badge";
11
+ import {
12
+ Breadcrumb,
13
+ BreadcrumbItem,
14
+ BreadcrumbLink,
15
+ BreadcrumbList,
16
+ BreadcrumbPage,
17
+ BreadcrumbSeparator,
18
+ } from "@/components/ui/breadcrumb";
19
+ import { Button } from "@/components/ui/button";
20
+ import { Calendar } from "@/components/ui/calendar";
21
+ import {
22
+ Card,
23
+ CardContent,
24
+ CardDescription,
25
+ CardFooter,
26
+ CardHeader,
27
+ CardTitle,
28
+ } from "@/components/ui/card";
29
+ import {
30
+ Carousel,
31
+ CarouselContent,
32
+ CarouselItem,
33
+ CarouselNext,
34
+ CarouselPrevious,
35
+ } from "@/components/ui/carousel";
36
+ import { Checkbox } from "@/components/ui/checkbox";
37
+ import {
38
+ Collapsible,
39
+ CollapsibleContent,
40
+ CollapsibleTrigger,
41
+ } from "@/components/ui/collapsible";
42
+ import {
43
+ Command,
44
+ CommandEmpty,
45
+ CommandGroup,
46
+ CommandInput,
47
+ CommandItem,
48
+ CommandList,
49
+ } from "@/components/ui/command";
50
+ import {
51
+ ContextMenu,
52
+ ContextMenuContent,
53
+ ContextMenuItem,
54
+ ContextMenuTrigger,
55
+ } from "@/components/ui/context-menu";
56
+ import {
57
+ Dialog,
58
+ DialogContent,
59
+ DialogDescription,
60
+ DialogHeader,
61
+ DialogTitle,
62
+ DialogTrigger,
63
+ } from "@/components/ui/dialog";
64
+ import {
65
+ Drawer,
66
+ DrawerClose,
67
+ DrawerContent,
68
+ DrawerDescription,
69
+ DrawerFooter,
70
+ DrawerHeader,
71
+ DrawerTitle,
72
+ DrawerTrigger,
73
+ } from "@/components/ui/drawer";
74
+ import {
75
+ DropdownMenu,
76
+ DropdownMenuContent,
77
+ DropdownMenuItem,
78
+ DropdownMenuLabel,
79
+ DropdownMenuSeparator,
80
+ DropdownMenuTrigger,
81
+ } from "@/components/ui/dropdown-menu";
82
+ import {
83
+ HoverCard,
84
+ HoverCardContent,
85
+ HoverCardTrigger,
86
+ } from "@/components/ui/hover-card";
87
+ import { Input } from "@/components/ui/input";
88
+ import {
89
+ InputOTP,
90
+ InputOTPGroup,
91
+ InputOTPSlot,
92
+ } from "@/components/ui/input-otp";
93
+ import { Label } from "@/components/ui/label";
94
+ import {
95
+ Menubar,
96
+ MenubarContent,
97
+ MenubarItem,
98
+ MenubarMenu,
99
+ MenubarSeparator,
100
+ MenubarTrigger,
101
+ } from "@/components/ui/menubar";
102
+ import {
103
+ Pagination,
104
+ PaginationContent,
105
+ PaginationItem,
106
+ PaginationLink,
107
+ PaginationNext,
108
+ PaginationPrevious,
109
+ } from "@/components/ui/pagination";
110
+ import {
111
+ Popover,
112
+ PopoverContent,
113
+ PopoverTrigger,
114
+ } from "@/components/ui/popover";
115
+ import { Progress } from "@/components/ui/progress";
116
+ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
117
+ import {
118
+ ResizableHandle,
119
+ ResizablePanel,
120
+ ResizablePanelGroup,
121
+ } from "@/components/ui/resizable";
122
+ import { ScrollArea } from "@/components/ui/scroll-area";
123
+ import {
124
+ Select,
125
+ SelectContent,
126
+ SelectItem,
127
+ SelectTrigger,
128
+ SelectValue,
129
+ } from "@/components/ui/select";
130
+ import { Separator } from "@/components/ui/separator";
131
+ import {
132
+ Sheet,
133
+ SheetContent,
134
+ SheetDescription,
135
+ SheetHeader,
136
+ SheetTitle,
137
+ SheetTrigger,
138
+ } from "@/components/ui/sheet";
139
+ import { Skeleton } from "@/components/ui/skeleton";
140
+ import { Slider } from "@/components/ui/slider";
141
+ import { Switch } from "@/components/ui/switch";
142
+ import {
143
+ Table,
144
+ TableBody,
145
+ TableCaption,
146
+ TableCell,
147
+ TableHead,
148
+ TableHeader,
149
+ TableRow,
150
+ } from "@/components/ui/table";
151
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
152
+ import { Textarea } from "@/components/ui/textarea";
153
+ import { Toggle } from "@/components/ui/toggle";
154
+ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
155
+ import {
156
+ Tooltip,
157
+ TooltipContent,
158
+ TooltipTrigger,
159
+ } from "@/components/ui/tooltip";
160
+ import { useTheme } from "@/contexts/ThemeContext";
161
+ import { format } from "date-fns";
162
+ import { zhCN } from "date-fns/locale";
163
+ import {
164
+ AlertCircle,
165
+ CalendarIcon,
166
+ Check,
167
+ Clock,
168
+ Moon,
169
+ Sun,
170
+ X,
171
+ } from "lucide-react";
172
+ import { useState } from "react";
173
+ import { toast as sonnerToast } from "sonner";
174
+ import { AIChatBox, type Message } from "@/components/AIChatBox";
175
+
176
+ export default function ComponentsShowcase() {
177
+ const { theme, toggleTheme } = useTheme();
178
+ const [date, setDate] = useState<Date | undefined>(new Date());
179
+ const [datePickerDate, setDatePickerDate] = useState<Date>();
180
+ const [selectedFruits, setSelectedFruits] = useState<string[]>([]);
181
+ const [progress, setProgress] = useState(33);
182
+ const [currentPage, setCurrentPage] = useState(2);
183
+ const [openCombobox, setOpenCombobox] = useState(false);
184
+ const [selectedFramework, setSelectedFramework] = useState("");
185
+ const [selectedMonth, setSelectedMonth] = useState("");
186
+ const [selectedYear, setSelectedYear] = useState("");
187
+ const [dialogInput, setDialogInput] = useState("");
188
+ const [dialogOpen, setDialogOpen] = useState(false);
189
+
190
+ // AI ChatBox demo state
191
+ const [chatMessages, setChatMessages] = useState<Message[]>([
192
+ { role: "system", content: "You are a helpful assistant." },
193
+ ]);
194
+ const [isChatLoading, setIsChatLoading] = useState(false);
195
+
196
+ const handleDialogSubmit = () => {
197
+ console.log("Dialog submitted with value:", dialogInput);
198
+ sonnerToast.success("Submitted successfully", {
199
+ description: `Input: ${dialogInput}`,
200
+ });
201
+ setDialogInput("");
202
+ setDialogOpen(false);
203
+ };
204
+
205
+ const handleDialogKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
206
+ if (e.key === "Enter" && !e.nativeEvent.isComposing) {
207
+ e.preventDefault();
208
+ handleDialogSubmit();
209
+ }
210
+ };
211
+
212
+ const handleChatSend = (content: string) => {
213
+ // Add user message
214
+ const newMessages: Message[] = [...chatMessages, { role: "user", content }];
215
+ setChatMessages(newMessages);
216
+
217
+ // Simulate AI response with delay
218
+ setIsChatLoading(true);
219
+ setTimeout(() => {
220
+ const aiResponse: Message = {
221
+ role: "assistant",
222
+ content: `This is a **demo response**. In a real app, you would call a tRPC mutation here:\n\n\`\`\`typescript\nconst chatMutation = trpc.ai.chat.useMutation({\n onSuccess: (response) => {\n setChatMessages(prev => [...prev, {\n role: "assistant",\n content: response.choices[0].message.content\n }]);\n }\n});\n\nchatMutation.mutate({ messages: newMessages });\n\`\`\`\n\nYour message was: "${content}"`,
223
+ };
224
+ setChatMessages([...newMessages, aiResponse]);
225
+ setIsChatLoading(false);
226
+ }, 1500);
227
+ };
228
+
229
+ return (
230
+ <div className="min-h-screen bg-background text-foreground">
231
+ <main className="container max-w-6xl mx-auto">
232
+ <div className="space-y-2 justify-between flex">
233
+ <h2 className="text-3xl font-bold tracking-tight mb-6">
234
+ Shadcn/ui Component Library
235
+ </h2>
236
+ <Button variant="outline" size="icon" onClick={toggleTheme}>
237
+ {theme === "light" ? (
238
+ <Moon className="h-5 w-5" />
239
+ ) : (
240
+ <Sun className="h-5 w-5" />
241
+ )}
242
+ </Button>
243
+ </div>
244
+
245
+ <div className="space-y-12">
246
+ {/* Text Colors Section */}
247
+ <section className="space-y-4">
248
+ <h3 className="text-2xl font-semibold">Text Colors</h3>
249
+ <Card>
250
+ <CardContent className="pt-6">
251
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
252
+ <div className="space-y-3">
253
+ <div>
254
+ <p className="text-sm text-muted-foreground mb-1">
255
+ Foreground (Default)
256
+ </p>
257
+ <p className="text-foreground text-lg">
258
+ Default text color for main content
259
+ </p>
260
+ </div>
261
+ <div>
262
+ <p className="text-sm text-muted-foreground mb-1">
263
+ Muted Foreground
264
+ </p>
265
+ <p className="text-muted-foreground text-lg">
266
+ Muted text for secondary information
267
+ </p>
268
+ </div>
269
+ <div>
270
+ <p className="text-sm text-muted-foreground mb-1">
271
+ Primary
272
+ </p>
273
+ <p className="text-primary text-lg font-medium">
274
+ Primary brand color text
275
+ </p>
276
+ </div>
277
+ <div>
278
+ <p className="text-sm text-muted-foreground mb-1">
279
+ Secondary Foreground
280
+ </p>
281
+ <p className="text-secondary-foreground text-lg">
282
+ Secondary action text color
283
+ </p>
284
+ </div>
285
+ </div>
286
+ <div className="space-y-3">
287
+ <div>
288
+ <p className="text-sm text-muted-foreground mb-1">
289
+ Accent Foreground
290
+ </p>
291
+ <p className="text-accent-foreground text-lg">
292
+ Accent text for emphasis
293
+ </p>
294
+ </div>
295
+ <div>
296
+ <p className="text-sm text-muted-foreground mb-1">
297
+ Destructive
298
+ </p>
299
+ <p className="text-destructive text-lg font-medium">
300
+ Error or destructive action text
301
+ </p>
302
+ </div>
303
+ <div>
304
+ <p className="text-sm text-muted-foreground mb-1">
305
+ Card Foreground
306
+ </p>
307
+ <p className="text-card-foreground text-lg">
308
+ Text color on card backgrounds
309
+ </p>
310
+ </div>
311
+ <div>
312
+ <p className="text-sm text-muted-foreground mb-1">
313
+ Popover Foreground
314
+ </p>
315
+ <p className="text-popover-foreground text-lg">
316
+ Text color in popovers
317
+ </p>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ </CardContent>
322
+ </Card>
323
+ </section>
324
+
325
+ {/* Color Combinations Section */}
326
+ <section className="space-y-4">
327
+ <h3 className="text-2xl font-semibold">Color Combinations</h3>
328
+ <Card>
329
+ <CardContent className="pt-6">
330
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
331
+ <div className="bg-primary text-primary-foreground rounded-lg p-4">
332
+ <p className="font-medium mb-1">Primary</p>
333
+ <p className="text-sm opacity-90">
334
+ Primary background with foreground text
335
+ </p>
336
+ </div>
337
+ <div className="bg-secondary text-secondary-foreground rounded-lg p-4">
338
+ <p className="font-medium mb-1">Secondary</p>
339
+ <p className="text-sm opacity-90">
340
+ Secondary background with foreground text
341
+ </p>
342
+ </div>
343
+ <div className="bg-muted text-muted-foreground rounded-lg p-4">
344
+ <p className="font-medium mb-1">Muted</p>
345
+ <p className="text-sm opacity-90">
346
+ Muted background with foreground text
347
+ </p>
348
+ </div>
349
+ <div className="bg-accent text-accent-foreground rounded-lg p-4">
350
+ <p className="font-medium mb-1">Accent</p>
351
+ <p className="text-sm opacity-90">
352
+ Accent background with foreground text
353
+ </p>
354
+ </div>
355
+ <div className="bg-destructive text-destructive-foreground rounded-lg p-4">
356
+ <p className="font-medium mb-1">Destructive</p>
357
+ <p className="text-sm opacity-90">
358
+ Destructive background with foreground text
359
+ </p>
360
+ </div>
361
+ <div className="bg-card text-card-foreground rounded-lg p-4 border">
362
+ <p className="font-medium mb-1">Card</p>
363
+ <p className="text-sm opacity-90">
364
+ Card background with foreground text
365
+ </p>
366
+ </div>
367
+ <div className="bg-popover text-popover-foreground rounded-lg p-4 border">
368
+ <p className="font-medium mb-1">Popover</p>
369
+ <p className="text-sm opacity-90">
370
+ Popover background with foreground text
371
+ </p>
372
+ </div>
373
+ <div className="bg-background text-foreground rounded-lg p-4 border">
374
+ <p className="font-medium mb-1">Background</p>
375
+ <p className="text-sm opacity-90">
376
+ Default background with foreground text
377
+ </p>
378
+ </div>
379
+ </div>
380
+ </CardContent>
381
+ </Card>
382
+ </section>
383
+
384
+ {/* Buttons Section */}
385
+ <section className="space-y-4">
386
+ <h3 className="text-2xl font-semibold">Buttons</h3>
387
+ <Card>
388
+ <CardContent className="pt-6">
389
+ <div className="flex flex-wrap gap-4">
390
+ <Button>Default</Button>
391
+ <Button variant="secondary">Secondary</Button>
392
+ <Button variant="destructive">Destructive</Button>
393
+ <Button variant="outline">Outline</Button>
394
+ <Button variant="ghost">Ghost</Button>
395
+ <Button variant="link">Link</Button>
396
+ <Button size="sm">Small</Button>
397
+ <Button size="lg">Large</Button>
398
+ <Button size="icon">
399
+ <Check className="h-4 w-4" />
400
+ </Button>
401
+ </div>
402
+ </CardContent>
403
+ </Card>
404
+ </section>
405
+
406
+ {/* Form Inputs Section */}
407
+ <section className="space-y-4">
408
+ <h3 className="text-2xl font-semibold">Form Inputs</h3>
409
+ <Card>
410
+ <CardContent className="pt-6 space-y-6">
411
+ <div className="space-y-2">
412
+ <Label htmlFor="email">Email</Label>
413
+ <Input id="email" type="email" placeholder="Email" />
414
+ </div>
415
+ <div className="space-y-2">
416
+ <Label htmlFor="message">Message</Label>
417
+ <Textarea
418
+ id="message"
419
+ placeholder="Type your message here."
420
+ />
421
+ </div>
422
+ <div className="space-y-2">
423
+ <Label>Select</Label>
424
+ <Select>
425
+ <SelectTrigger>
426
+ <SelectValue placeholder="Select a fruit" />
427
+ </SelectTrigger>
428
+ <SelectContent>
429
+ <SelectItem value="apple">Apple</SelectItem>
430
+ <SelectItem value="banana">Banana</SelectItem>
431
+ <SelectItem value="orange">Orange</SelectItem>
432
+ </SelectContent>
433
+ </Select>
434
+ </div>
435
+ <div className="flex items-center space-x-2">
436
+ <Checkbox id="terms" />
437
+ <Label htmlFor="terms">Accept terms and conditions</Label>
438
+ </div>
439
+ <div className="flex items-center space-x-2">
440
+ <Switch id="airplane-mode" />
441
+ <Label htmlFor="airplane-mode">Airplane Mode</Label>
442
+ </div>
443
+ <div className="space-y-2">
444
+ <Label>Radio Group</Label>
445
+ <RadioGroup defaultValue="option-one">
446
+ <div className="flex items-center space-x-2">
447
+ <RadioGroupItem value="option-one" id="option-one" />
448
+ <Label htmlFor="option-one">Option One</Label>
449
+ </div>
450
+ <div className="flex items-center space-x-2">
451
+ <RadioGroupItem value="option-two" id="option-two" />
452
+ <Label htmlFor="option-two">Option Two</Label>
453
+ </div>
454
+ </RadioGroup>
455
+ </div>
456
+ <div className="space-y-2">
457
+ <Label>Slider</Label>
458
+ <Slider defaultValue={[50]} max={100} step={1} />
459
+ </div>
460
+ <div className="space-y-2">
461
+ <Label>Input OTP</Label>
462
+ <InputOTP maxLength={6}>
463
+ <InputOTPGroup>
464
+ <InputOTPSlot index={0} />
465
+ <InputOTPSlot index={1} />
466
+ <InputOTPSlot index={2} />
467
+ <InputOTPSlot index={3} />
468
+ <InputOTPSlot index={4} />
469
+ <InputOTPSlot index={5} />
470
+ </InputOTPGroup>
471
+ </InputOTP>
472
+ </div>
473
+ <div className="space-y-2">
474
+ <Label>Date Time Picker</Label>
475
+ <Popover>
476
+ <PopoverTrigger asChild>
477
+ <Button
478
+ variant="outline"
479
+ className={`w-full justify-start text-left font-normal ${
480
+ !datePickerDate && "text-muted-foreground"
481
+ }`}
482
+ >
483
+ <CalendarIcon className="mr-2 h-4 w-4" />
484
+ {datePickerDate ? (
485
+ format(datePickerDate, "PPP HH:mm", { locale: zhCN })
486
+ ) : (
487
+ <span>Select date and time</span>
488
+ )}
489
+ </Button>
490
+ </PopoverTrigger>
491
+ <PopoverContent className="w-auto p-0" align="start">
492
+ <div className="p-3 space-y-3">
493
+ <Calendar
494
+ mode="single"
495
+ selected={datePickerDate}
496
+ onSelect={setDatePickerDate}
497
+ />
498
+ <div className="border-t pt-3 space-y-2">
499
+ <Label className="flex items-center gap-2">
500
+ <Clock className="h-4 w-4" />
501
+ Time
502
+ </Label>
503
+ <div className="flex gap-2">
504
+ <Input
505
+ type="time"
506
+ value={
507
+ datePickerDate
508
+ ? format(datePickerDate, "HH:mm")
509
+ : "00:00"
510
+ }
511
+ onChange={e => {
512
+ const [hours, minutes] =
513
+ e.target.value.split(":");
514
+ const newDate = datePickerDate
515
+ ? new Date(datePickerDate)
516
+ : new Date();
517
+ newDate.setHours(parseInt(hours));
518
+ newDate.setMinutes(parseInt(minutes));
519
+ setDatePickerDate(newDate);
520
+ }}
521
+ />
522
+ </div>
523
+ </div>
524
+ </div>
525
+ </PopoverContent>
526
+ </Popover>
527
+ {datePickerDate && (
528
+ <p className="text-sm text-muted-foreground">
529
+ Selected:{" "}
530
+ {format(datePickerDate, "yyyy/MM/dd HH:mm", {
531
+ locale: zhCN,
532
+ })}
533
+ </p>
534
+ )}
535
+ </div>
536
+ <div className="space-y-2">
537
+ <Label>Searchable Dropdown</Label>
538
+ <Popover open={openCombobox} onOpenChange={setOpenCombobox}>
539
+ <PopoverTrigger asChild>
540
+ <Button
541
+ variant="outline"
542
+ role="combobox"
543
+ aria-expanded={openCombobox}
544
+ className="w-full justify-between"
545
+ >
546
+ {selectedFramework
547
+ ? [
548
+ { value: "react", label: "React" },
549
+ { value: "vue", label: "Vue" },
550
+ { value: "angular", label: "Angular" },
551
+ { value: "svelte", label: "Svelte" },
552
+ { value: "nextjs", label: "Next.js" },
553
+ { value: "nuxt", label: "Nuxt" },
554
+ { value: "remix", label: "Remix" },
555
+ ].find(fw => fw.value === selectedFramework)?.label
556
+ : "Select framework..."}
557
+ <CalendarIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
558
+ </Button>
559
+ </PopoverTrigger>
560
+ <PopoverContent className="w-full p-0">
561
+ <Command>
562
+ <CommandInput placeholder="Search frameworks..." />
563
+ <CommandList>
564
+ <CommandEmpty>No framework found</CommandEmpty>
565
+ <CommandGroup>
566
+ {[
567
+ { value: "react", label: "React" },
568
+ { value: "vue", label: "Vue" },
569
+ { value: "angular", label: "Angular" },
570
+ { value: "svelte", label: "Svelte" },
571
+ { value: "nextjs", label: "Next.js" },
572
+ { value: "nuxt", label: "Nuxt" },
573
+ { value: "remix", label: "Remix" },
574
+ ].map(framework => (
575
+ <CommandItem
576
+ key={framework.value}
577
+ value={framework.value}
578
+ onSelect={currentValue => {
579
+ setSelectedFramework(
580
+ currentValue === selectedFramework
581
+ ? ""
582
+ : currentValue
583
+ );
584
+ setOpenCombobox(false);
585
+ }}
586
+ >
587
+ <Check
588
+ className={`mr-2 h-4 w-4 ${
589
+ selectedFramework === framework.value
590
+ ? "opacity-100"
591
+ : "opacity-0"
592
+ }`}
593
+ />
594
+ {framework.label}
595
+ </CommandItem>
596
+ ))}
597
+ </CommandGroup>
598
+ </CommandList>
599
+ </Command>
600
+ </PopoverContent>
601
+ </Popover>
602
+ {selectedFramework && (
603
+ <p className="text-sm text-muted-foreground">
604
+ Selected:{" "}
605
+ {
606
+ [
607
+ { value: "react", label: "React" },
608
+ { value: "vue", label: "Vue" },
609
+ { value: "angular", label: "Angular" },
610
+ { value: "svelte", label: "Svelte" },
611
+ { value: "nextjs", label: "Next.js" },
612
+ { value: "nuxt", label: "Nuxt" },
613
+ { value: "remix", label: "Remix" },
614
+ ].find(fw => fw.value === selectedFramework)?.label
615
+ }
616
+ </p>
617
+ )}
618
+ </div>
619
+ <div className="space-y-2">
620
+ <div className="grid grid-cols-2 gap-4">
621
+ <div className="space-y-2">
622
+ <Label htmlFor="month" className="text-sm font-medium">
623
+ Month
624
+ </Label>
625
+ <Select
626
+ value={selectedMonth}
627
+ onValueChange={setSelectedMonth}
628
+ >
629
+ <SelectTrigger id="month">
630
+ <SelectValue placeholder="MM" />
631
+ </SelectTrigger>
632
+ <SelectContent>
633
+ {Array.from({ length: 12 }, (_, i) => i + 1).map(
634
+ month => (
635
+ <SelectItem
636
+ key={month}
637
+ value={month.toString().padStart(2, "0")}
638
+ >
639
+ {month.toString().padStart(2, "0")}
640
+ </SelectItem>
641
+ )
642
+ )}
643
+ </SelectContent>
644
+ </Select>
645
+ </div>
646
+ <div className="space-y-2">
647
+ <Label htmlFor="year" className="text-sm font-medium">
648
+ Year
649
+ </Label>
650
+ <Select
651
+ value={selectedYear}
652
+ onValueChange={setSelectedYear}
653
+ >
654
+ <SelectTrigger id="year">
655
+ <SelectValue placeholder="YYYY" />
656
+ </SelectTrigger>
657
+ <SelectContent>
658
+ {Array.from(
659
+ { length: 10 },
660
+ (_, i) => new Date().getFullYear() - 5 + i
661
+ ).map(year => (
662
+ <SelectItem key={year} value={year.toString()}>
663
+ {year}
664
+ </SelectItem>
665
+ ))}
666
+ </SelectContent>
667
+ </Select>
668
+ </div>
669
+ </div>
670
+ {selectedMonth && selectedYear && (
671
+ <p className="text-sm text-muted-foreground">
672
+ Selected: {selectedYear}/{selectedMonth}/
673
+ </p>
674
+ )}
675
+ </div>
676
+ </CardContent>
677
+ </Card>
678
+ </section>
679
+
680
+ {/* Data Display Section */}
681
+ <section className="space-y-4">
682
+ <h3 className="text-2xl font-semibold">Data Display</h3>
683
+ <Card>
684
+ <CardContent className="pt-6 space-y-6">
685
+ <div className="space-y-2">
686
+ <Label>Badges</Label>
687
+ <div className="flex flex-wrap gap-2">
688
+ <Badge>Default</Badge>
689
+ <Badge variant="secondary">Secondary</Badge>
690
+ <Badge variant="destructive">Destructive</Badge>
691
+ <Badge variant="outline">Outline</Badge>
692
+ </div>
693
+ </div>
694
+ <Separator />
695
+ <div className="space-y-2">
696
+ <Label>Avatar</Label>
697
+ <div className="flex gap-4">
698
+ <Avatar>
699
+ <AvatarImage src="https://github.com/shadcn.png" />
700
+ <AvatarFallback>CN</AvatarFallback>
701
+ </Avatar>
702
+ <Avatar>
703
+ <AvatarFallback>AB</AvatarFallback>
704
+ </Avatar>
705
+ </div>
706
+ </div>
707
+ <Separator />
708
+ <div className="space-y-2">
709
+ <Label>Progress</Label>
710
+ <Progress value={progress} />
711
+ <div className="flex gap-2">
712
+ <Button
713
+ size="sm"
714
+ onClick={() => setProgress(Math.max(0, progress - 10))}
715
+ >
716
+ -10
717
+ </Button>
718
+ <Button
719
+ size="sm"
720
+ onClick={() => setProgress(Math.min(100, progress + 10))}
721
+ >
722
+ +10
723
+ </Button>
724
+ </div>
725
+ </div>
726
+ <Separator />
727
+ <div className="space-y-2">
728
+ <Label>Skeleton</Label>
729
+ <div className="space-y-2">
730
+ <Skeleton className="h-4 w-full" />
731
+ <Skeleton className="h-4 w-3/4" />
732
+ <Skeleton className="h-4 w-1/2" />
733
+ </div>
734
+ </div>
735
+ <Separator />
736
+ <div className="space-y-2">
737
+ <Label>Pagination</Label>
738
+ <Pagination>
739
+ <PaginationContent>
740
+ <PaginationItem>
741
+ <PaginationPrevious
742
+ href="#"
743
+ onClick={e => {
744
+ e.preventDefault();
745
+ setCurrentPage(Math.max(1, currentPage - 1));
746
+ }}
747
+ />
748
+ </PaginationItem>
749
+ {[1, 2, 3, 4, 5].map(page => (
750
+ <PaginationItem key={page}>
751
+ <PaginationLink
752
+ href="#"
753
+ isActive={currentPage === page}
754
+ onClick={e => {
755
+ e.preventDefault();
756
+ setCurrentPage(page);
757
+ }}
758
+ >
759
+ {page}
760
+ </PaginationLink>
761
+ </PaginationItem>
762
+ ))}
763
+ <PaginationItem>
764
+ <PaginationNext
765
+ href="#"
766
+ onClick={e => {
767
+ e.preventDefault();
768
+ setCurrentPage(Math.min(5, currentPage + 1));
769
+ }}
770
+ />
771
+ </PaginationItem>
772
+ </PaginationContent>
773
+ </Pagination>
774
+ <p className="text-sm text-muted-foreground text-center">
775
+ Current page: {currentPage}
776
+ </p>
777
+ </div>
778
+ <Separator />
779
+ <div className="space-y-2">
780
+ <Label>Table</Label>
781
+ <Table>
782
+ <TableCaption>A list of your recent invoices.</TableCaption>
783
+ <TableHeader>
784
+ <TableRow>
785
+ <TableHead className="w-[100px]">Invoice</TableHead>
786
+ <TableHead>Status</TableHead>
787
+ <TableHead>Method</TableHead>
788
+ <TableHead className="text-right">Amount</TableHead>
789
+ </TableRow>
790
+ </TableHeader>
791
+ <TableBody>
792
+ <TableRow>
793
+ <TableCell className="font-medium">INV001</TableCell>
794
+ <TableCell>Paid</TableCell>
795
+ <TableCell>Credit Card</TableCell>
796
+ <TableCell className="text-right">$250.00</TableCell>
797
+ </TableRow>
798
+ <TableRow>
799
+ <TableCell className="font-medium">INV002</TableCell>
800
+ <TableCell>Pending</TableCell>
801
+ <TableCell>PayPal</TableCell>
802
+ <TableCell className="text-right">$150.00</TableCell>
803
+ </TableRow>
804
+ <TableRow>
805
+ <TableCell className="font-medium">INV003</TableCell>
806
+ <TableCell>Unpaid</TableCell>
807
+ <TableCell>Bank Transfer</TableCell>
808
+ <TableCell className="text-right">$350.00</TableCell>
809
+ </TableRow>
810
+ </TableBody>
811
+ </Table>
812
+ </div>
813
+ <Separator />
814
+ <div className="space-y-2">
815
+ <Label>Menubar</Label>
816
+ <Menubar>
817
+ <MenubarMenu>
818
+ <MenubarTrigger>File</MenubarTrigger>
819
+ <MenubarContent>
820
+ <MenubarItem>New Tab</MenubarItem>
821
+ <MenubarItem>New Window</MenubarItem>
822
+ <MenubarSeparator />
823
+ <MenubarItem>Share</MenubarItem>
824
+ <MenubarSeparator />
825
+ <MenubarItem>Print</MenubarItem>
826
+ </MenubarContent>
827
+ </MenubarMenu>
828
+ <MenubarMenu>
829
+ <MenubarTrigger>Edit</MenubarTrigger>
830
+ <MenubarContent>
831
+ <MenubarItem>Undo</MenubarItem>
832
+ <MenubarItem>Redo</MenubarItem>
833
+ </MenubarContent>
834
+ </MenubarMenu>
835
+ <MenubarMenu>
836
+ <MenubarTrigger>View</MenubarTrigger>
837
+ <MenubarContent>
838
+ <MenubarItem>Reload</MenubarItem>
839
+ <MenubarItem>Force Reload</MenubarItem>
840
+ </MenubarContent>
841
+ </MenubarMenu>
842
+ </Menubar>
843
+ </div>
844
+ <Separator />
845
+ <div className="space-y-2">
846
+ <Label>Breadcrumb</Label>
847
+ <Breadcrumb>
848
+ <BreadcrumbList>
849
+ <BreadcrumbItem>
850
+ <BreadcrumbLink href="/">Home</BreadcrumbLink>
851
+ </BreadcrumbItem>
852
+ <BreadcrumbSeparator />
853
+ <BreadcrumbItem>
854
+ <BreadcrumbLink href="/components">
855
+ Components
856
+ </BreadcrumbLink>
857
+ </BreadcrumbItem>
858
+ <BreadcrumbSeparator />
859
+ <BreadcrumbItem>
860
+ <BreadcrumbPage>Breadcrumb</BreadcrumbPage>
861
+ </BreadcrumbItem>
862
+ </BreadcrumbList>
863
+ </Breadcrumb>
864
+ </div>
865
+ </CardContent>
866
+ </Card>
867
+ </section>
868
+
869
+ {/* Alerts Section */}
870
+ <section className="space-y-4">
871
+ <h3 className="text-2xl font-semibold">Alerts</h3>
872
+ <div className="space-y-4">
873
+ <Alert>
874
+ <AlertCircle className="h-4 w-4" />
875
+ <AlertTitle>Heads up!</AlertTitle>
876
+ <AlertDescription>
877
+ You can add components to your app using the cli.
878
+ </AlertDescription>
879
+ </Alert>
880
+ <Alert variant="destructive">
881
+ <X className="h-4 w-4" />
882
+ <AlertTitle>Error</AlertTitle>
883
+ <AlertDescription>
884
+ Your session has expired. Please log in again.
885
+ </AlertDescription>
886
+ </Alert>
887
+ </div>
888
+ </section>
889
+
890
+ {/* Tabs Section */}
891
+ <section className="space-y-4">
892
+ <h3 className="text-2xl font-semibold">Tabs</h3>
893
+ <Tabs defaultValue="account" className="w-full">
894
+ <TabsList className="grid w-full grid-cols-3">
895
+ <TabsTrigger value="account">Account</TabsTrigger>
896
+ <TabsTrigger value="password">Password</TabsTrigger>
897
+ <TabsTrigger value="settings">Settings</TabsTrigger>
898
+ </TabsList>
899
+ <TabsContent value="account">
900
+ <Card>
901
+ <CardHeader>
902
+ <CardTitle>Account</CardTitle>
903
+ <CardDescription>
904
+ Make changes to your account here.
905
+ </CardDescription>
906
+ </CardHeader>
907
+ <CardContent className="space-y-2">
908
+ <div className="space-y-1">
909
+ <Label htmlFor="name">Name</Label>
910
+ <Input id="name" defaultValue="Pedro Duarte" />
911
+ </div>
912
+ </CardContent>
913
+ <CardFooter>
914
+ <Button>Save changes</Button>
915
+ </CardFooter>
916
+ </Card>
917
+ </TabsContent>
918
+ <TabsContent value="password">
919
+ <Card>
920
+ <CardHeader>
921
+ <CardTitle>Password</CardTitle>
922
+ <CardDescription>
923
+ Change your password here.
924
+ </CardDescription>
925
+ </CardHeader>
926
+ <CardContent className="space-y-2">
927
+ <div className="space-y-1">
928
+ <Label htmlFor="current">Current password</Label>
929
+ <Input id="current" type="password" />
930
+ </div>
931
+ <div className="space-y-1">
932
+ <Label htmlFor="new">New password</Label>
933
+ <Input id="new" type="password" />
934
+ </div>
935
+ </CardContent>
936
+ <CardFooter>
937
+ <Button>Save password</Button>
938
+ </CardFooter>
939
+ </Card>
940
+ </TabsContent>
941
+ <TabsContent value="settings">
942
+ <Card>
943
+ <CardHeader>
944
+ <CardTitle>Settings</CardTitle>
945
+ <CardDescription>
946
+ Manage your settings here.
947
+ </CardDescription>
948
+ </CardHeader>
949
+ <CardContent>
950
+ <p className="text-sm text-muted-foreground">
951
+ Settings content goes here.
952
+ </p>
953
+ </CardContent>
954
+ </Card>
955
+ </TabsContent>
956
+ </Tabs>
957
+ </section>
958
+
959
+ {/* Accordion Section */}
960
+ <section className="space-y-4">
961
+ <h3 className="text-2xl font-semibold">Accordion</h3>
962
+ <Accordion type="single" collapsible className="w-full">
963
+ <AccordionItem value="item-1">
964
+ <AccordionTrigger>Is it accessible?</AccordionTrigger>
965
+ <AccordionContent>
966
+ Yes. It adheres to the WAI-ARIA design pattern.
967
+ </AccordionContent>
968
+ </AccordionItem>
969
+ <AccordionItem value="item-2">
970
+ <AccordionTrigger>Is it styled?</AccordionTrigger>
971
+ <AccordionContent>
972
+ Yes. It comes with default styles that matches the other
973
+ components' aesthetic.
974
+ </AccordionContent>
975
+ </AccordionItem>
976
+ <AccordionItem value="item-3">
977
+ <AccordionTrigger>Is it animated?</AccordionTrigger>
978
+ <AccordionContent>
979
+ Yes. It's animated by default, but you can disable it if you
980
+ prefer.
981
+ </AccordionContent>
982
+ </AccordionItem>
983
+ </Accordion>
984
+ </section>
985
+
986
+ {/* Collapsible Section */}
987
+ <section className="space-y-4">
988
+ <h3 className="text-2xl font-semibold">Collapsible</h3>
989
+ <Collapsible>
990
+ <Card>
991
+ <CardHeader>
992
+ <CollapsibleTrigger asChild>
993
+ <Button variant="ghost" className="w-full justify-between">
994
+ <CardTitle>@peduarte starred 3 repositories</CardTitle>
995
+ </Button>
996
+ </CollapsibleTrigger>
997
+ </CardHeader>
998
+ <CollapsibleContent>
999
+ <CardContent>
1000
+ <div className="space-y-2">
1001
+ <div className="rounded-md border px-4 py-3 font-mono text-sm">
1002
+ @radix-ui/primitives
1003
+ </div>
1004
+ <div className="rounded-md border px-4 py-3 font-mono text-sm">
1005
+ @radix-ui/colors
1006
+ </div>
1007
+ <div className="rounded-md border px-4 py-3 font-mono text-sm">
1008
+ @stitches/react
1009
+ </div>
1010
+ </div>
1011
+ </CardContent>
1012
+ </CollapsibleContent>
1013
+ </Card>
1014
+ </Collapsible>
1015
+ </section>
1016
+
1017
+ {/* Dialog, Sheet, Drawer Section */}
1018
+ <section className="space-y-4">
1019
+ <h3 className="text-2xl font-semibold">Overlays</h3>
1020
+ <Card>
1021
+ <CardContent className="pt-6">
1022
+ <div className="flex flex-wrap gap-4">
1023
+ <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
1024
+ <DialogTrigger asChild>
1025
+ <Button variant="outline">Open Dialog</Button>
1026
+ </DialogTrigger>
1027
+ <DialogContent>
1028
+ <DialogHeader>
1029
+ <DialogTitle>Test Input</DialogTitle>
1030
+ <DialogDescription>
1031
+ Enter some text below. Press Enter to submit (IME composition supported).
1032
+ </DialogDescription>
1033
+ </DialogHeader>
1034
+ <div className="space-y-4 py-4">
1035
+ <div className="space-y-2">
1036
+ <Label htmlFor="dialog-input">Input</Label>
1037
+ <Input
1038
+ id="dialog-input"
1039
+ placeholder="Type something..."
1040
+ value={dialogInput}
1041
+ onChange={(e) => setDialogInput(e.target.value)}
1042
+ onKeyDown={handleDialogKeyDown}
1043
+ autoFocus
1044
+ />
1045
+ </div>
1046
+ </div>
1047
+ <div className="flex justify-end gap-2">
1048
+ <Button
1049
+ variant="outline"
1050
+ onClick={() => setDialogOpen(false)}
1051
+ >
1052
+ Cancel
1053
+ </Button>
1054
+ <Button onClick={handleDialogSubmit}>Submit</Button>
1055
+ </div>
1056
+ </DialogContent>
1057
+ </Dialog>
1058
+
1059
+ <Sheet>
1060
+ <SheetTrigger asChild>
1061
+ <Button variant="outline">Open Sheet</Button>
1062
+ </SheetTrigger>
1063
+ <SheetContent>
1064
+ <SheetHeader>
1065
+ <SheetTitle>Edit profile</SheetTitle>
1066
+ <SheetDescription>
1067
+ Make changes to your profile here. Click save when
1068
+ you're done.
1069
+ </SheetDescription>
1070
+ </SheetHeader>
1071
+ </SheetContent>
1072
+ </Sheet>
1073
+
1074
+ <Drawer>
1075
+ <DrawerTrigger asChild>
1076
+ <Button variant="outline">Open Drawer</Button>
1077
+ </DrawerTrigger>
1078
+ <DrawerContent>
1079
+ <DrawerHeader>
1080
+ <DrawerTitle>Are you absolutely sure?</DrawerTitle>
1081
+ <DrawerDescription>
1082
+ This action cannot be undone.
1083
+ </DrawerDescription>
1084
+ </DrawerHeader>
1085
+ <DrawerFooter>
1086
+ <Button>Submit</Button>
1087
+ <DrawerClose asChild>
1088
+ <Button variant="outline">Cancel</Button>
1089
+ </DrawerClose>
1090
+ </DrawerFooter>
1091
+ </DrawerContent>
1092
+ </Drawer>
1093
+
1094
+ <Popover>
1095
+ <PopoverTrigger asChild>
1096
+ <Button variant="outline">Open Popover</Button>
1097
+ </PopoverTrigger>
1098
+ <PopoverContent>
1099
+ <div className="space-y-2">
1100
+ <h4 className="font-medium leading-none">Dimensions</h4>
1101
+ <p className="text-sm text-muted-foreground">
1102
+ Set the dimensions for the layer.
1103
+ </p>
1104
+ </div>
1105
+ </PopoverContent>
1106
+ </Popover>
1107
+
1108
+ <Tooltip>
1109
+ <TooltipTrigger asChild>
1110
+ <Button variant="outline">Hover me</Button>
1111
+ </TooltipTrigger>
1112
+ <TooltipContent>
1113
+ <p>Add to library</p>
1114
+ </TooltipContent>
1115
+ </Tooltip>
1116
+ </div>
1117
+ </CardContent>
1118
+ </Card>
1119
+ </section>
1120
+
1121
+ {/* Menus Section */}
1122
+ <section className="space-y-4">
1123
+ <h3 className="text-2xl font-semibold">Menus</h3>
1124
+ <Card>
1125
+ <CardContent className="pt-6">
1126
+ <div className="flex flex-wrap gap-4">
1127
+ <DropdownMenu>
1128
+ <DropdownMenuTrigger asChild>
1129
+ <Button variant="outline">Dropdown Menu</Button>
1130
+ </DropdownMenuTrigger>
1131
+ <DropdownMenuContent>
1132
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
1133
+ <DropdownMenuSeparator />
1134
+ <DropdownMenuItem>Profile</DropdownMenuItem>
1135
+ <DropdownMenuItem>Billing</DropdownMenuItem>
1136
+ <DropdownMenuItem>Team</DropdownMenuItem>
1137
+ <DropdownMenuItem>Subscription</DropdownMenuItem>
1138
+ </DropdownMenuContent>
1139
+ </DropdownMenu>
1140
+
1141
+ <ContextMenu>
1142
+ <ContextMenuTrigger asChild>
1143
+ <Button variant="outline">Right Click Me</Button>
1144
+ </ContextMenuTrigger>
1145
+ <ContextMenuContent>
1146
+ <ContextMenuItem>Profile</ContextMenuItem>
1147
+ <ContextMenuItem>Billing</ContextMenuItem>
1148
+ <ContextMenuItem>Team</ContextMenuItem>
1149
+ <ContextMenuItem>Subscription</ContextMenuItem>
1150
+ </ContextMenuContent>
1151
+ </ContextMenu>
1152
+
1153
+ <HoverCard>
1154
+ <HoverCardTrigger asChild>
1155
+ <Button variant="outline">Hover Card</Button>
1156
+ </HoverCardTrigger>
1157
+ <HoverCardContent>
1158
+ <div className="space-y-2">
1159
+ <h4 className="text-sm font-semibold">@nextjs</h4>
1160
+ <p className="text-sm">
1161
+ The React Framework – created and maintained by
1162
+ @vercel.
1163
+ </p>
1164
+ </div>
1165
+ </HoverCardContent>
1166
+ </HoverCard>
1167
+ </div>
1168
+ </CardContent>
1169
+ </Card>
1170
+ </section>
1171
+
1172
+ {/* Calendar Section */}
1173
+ <section className="space-y-4">
1174
+ <h3 className="text-2xl font-semibold">Calendar</h3>
1175
+ <Card>
1176
+ <CardContent className="pt-6 flex justify-center">
1177
+ <Calendar
1178
+ mode="single"
1179
+ selected={date}
1180
+ onSelect={setDate}
1181
+ className="rounded-md border"
1182
+ />
1183
+ </CardContent>
1184
+ </Card>
1185
+ </section>
1186
+
1187
+ {/* Carousel Section */}
1188
+ <section className="space-y-4">
1189
+ <h3 className="text-2xl font-semibold">Carousel</h3>
1190
+ <Card>
1191
+ <CardContent className="pt-6">
1192
+ <Carousel className="w-full max-w-xs mx-auto">
1193
+ <CarouselContent>
1194
+ {Array.from({ length: 5 }).map((_, index) => (
1195
+ <CarouselItem key={index}>
1196
+ <div className="p-1">
1197
+ <Card>
1198
+ <CardContent className="flex aspect-square items-center justify-center p-6">
1199
+ <span className="text-4xl font-semibold">
1200
+ {index + 1}
1201
+ </span>
1202
+ </CardContent>
1203
+ </Card>
1204
+ </div>
1205
+ </CarouselItem>
1206
+ ))}
1207
+ </CarouselContent>
1208
+ <CarouselPrevious />
1209
+ <CarouselNext />
1210
+ </Carousel>
1211
+ </CardContent>
1212
+ </Card>
1213
+ </section>
1214
+
1215
+ {/* Toggle Section */}
1216
+ <section className="space-y-4">
1217
+ <h3 className="text-2xl font-semibold">Toggle</h3>
1218
+ <Card>
1219
+ <CardContent className="pt-6 space-y-4">
1220
+ <div className="space-y-2">
1221
+ <Label>Toggle</Label>
1222
+ <div className="flex gap-2">
1223
+ <Toggle aria-label="Toggle italic">
1224
+ <span className="font-bold">B</span>
1225
+ </Toggle>
1226
+ <Toggle aria-label="Toggle italic">
1227
+ <span className="italic">I</span>
1228
+ </Toggle>
1229
+ <Toggle aria-label="Toggle underline">
1230
+ <span className="underline">U</span>
1231
+ </Toggle>
1232
+ </div>
1233
+ </div>
1234
+ <Separator />
1235
+ <div className="space-y-2">
1236
+ <Label>Toggle Group</Label>
1237
+ <ToggleGroup type="multiple">
1238
+ <ToggleGroupItem value="bold" aria-label="Toggle bold">
1239
+ <span className="font-bold">B</span>
1240
+ </ToggleGroupItem>
1241
+ <ToggleGroupItem value="italic" aria-label="Toggle italic">
1242
+ <span className="italic">I</span>
1243
+ </ToggleGroupItem>
1244
+ <ToggleGroupItem
1245
+ value="underline"
1246
+ aria-label="Toggle underline"
1247
+ >
1248
+ <span className="underline">U</span>
1249
+ </ToggleGroupItem>
1250
+ </ToggleGroup>
1251
+ </div>
1252
+ </CardContent>
1253
+ </Card>
1254
+ </section>
1255
+
1256
+ {/* Aspect Ratio & Scroll Area Section */}
1257
+ <section className="space-y-4">
1258
+ <h3 className="text-2xl font-semibold">Layout Components</h3>
1259
+ <Card>
1260
+ <CardContent className="pt-6 space-y-6">
1261
+ <div className="space-y-2">
1262
+ <Label>Aspect Ratio (16/9)</Label>
1263
+ <AspectRatio ratio={16 / 9} className="bg-muted">
1264
+ <div className="flex h-full items-center justify-center">
1265
+ <p className="text-muted-foreground">16:9 Aspect Ratio</p>
1266
+ </div>
1267
+ </AspectRatio>
1268
+ </div>
1269
+ <Separator />
1270
+ <div className="space-y-2">
1271
+ <Label>Scroll Area</Label>
1272
+ <ScrollArea className="h-[200px] w-full rounded-md border overflow-hidden">
1273
+ <div className="p-4">
1274
+ <div className="space-y-4">
1275
+ {Array.from({ length: 20 }).map((_, i) => (
1276
+ <div key={i} className="text-sm">
1277
+ Item {i + 1}: This is a scrollable content area
1278
+ </div>
1279
+ ))}
1280
+ </div>
1281
+ </div>
1282
+ </ScrollArea>
1283
+ </div>
1284
+ </CardContent>
1285
+ </Card>
1286
+ </section>
1287
+
1288
+ {/* Resizable Section */}
1289
+ <section className="space-y-4">
1290
+ <h3 className="text-2xl font-semibold">Resizable Panels</h3>
1291
+ <Card>
1292
+ <CardContent className="pt-6">
1293
+ <ResizablePanelGroup
1294
+ direction="horizontal"
1295
+ className="min-h-[200px] rounded-lg border"
1296
+ >
1297
+ <ResizablePanel defaultSize={50}>
1298
+ <div className="flex h-full items-center justify-center p-6">
1299
+ <span className="font-semibold">Panel One</span>
1300
+ </div>
1301
+ </ResizablePanel>
1302
+ <ResizableHandle />
1303
+ <ResizablePanel defaultSize={50}>
1304
+ <div className="flex h-full items-center justify-center p-6">
1305
+ <span className="font-semibold">Panel Two</span>
1306
+ </div>
1307
+ </ResizablePanel>
1308
+ </ResizablePanelGroup>
1309
+ </CardContent>
1310
+ </Card>
1311
+ </section>
1312
+
1313
+ {/* Toast Section */}
1314
+ <section className="space-y-4">
1315
+ <h3 className="text-2xl font-semibold">Toast</h3>
1316
+ <Card>
1317
+ <CardContent className="pt-6 space-y-4">
1318
+ <div className="space-y-2">
1319
+ <Label>Sonner Toast</Label>
1320
+ <div className="flex flex-wrap gap-2">
1321
+ <Button
1322
+ variant="outline"
1323
+ onClick={() => {
1324
+ sonnerToast.success("Operation successful", {
1325
+ description: "Your changes have been saved",
1326
+ });
1327
+ }}
1328
+ >
1329
+ Success
1330
+ </Button>
1331
+ <Button
1332
+ variant="outline"
1333
+ onClick={() => {
1334
+ sonnerToast.error("Operation failed", {
1335
+ description:
1336
+ "Cannot complete operation, please try again",
1337
+ });
1338
+ }}
1339
+ >
1340
+ Error
1341
+ </Button>
1342
+ <Button
1343
+ variant="outline"
1344
+ onClick={() => {
1345
+ sonnerToast.info("Information", {
1346
+ description: "This is an information message",
1347
+ });
1348
+ }}
1349
+ >
1350
+ Info
1351
+ </Button>
1352
+ <Button
1353
+ variant="outline"
1354
+ onClick={() => {
1355
+ sonnerToast.warning("Warning", {
1356
+ description:
1357
+ "Please note the impact of this operation",
1358
+ });
1359
+ }}
1360
+ >
1361
+ Warning
1362
+ </Button>
1363
+ <Button
1364
+ variant="outline"
1365
+ onClick={() => {
1366
+ sonnerToast.loading("Loading", {
1367
+ description: "Please wait",
1368
+ });
1369
+ }}
1370
+ >
1371
+ Loading
1372
+ </Button>
1373
+ <Button
1374
+ variant="outline"
1375
+ onClick={() => {
1376
+ const promise = new Promise(resolve =>
1377
+ setTimeout(resolve, 2000)
1378
+ );
1379
+ sonnerToast.promise(promise, {
1380
+ loading: "Processing...",
1381
+ success: "Processing complete!",
1382
+ error: "Processing failed",
1383
+ });
1384
+ }}
1385
+ >
1386
+ Promise
1387
+ </Button>
1388
+ </div>
1389
+ </div>
1390
+ </CardContent>
1391
+ </Card>
1392
+ </section>
1393
+
1394
+ {/* AI ChatBox Section */}
1395
+ <section className="space-y-4">
1396
+ <h3 className="text-2xl font-semibold">AI ChatBox</h3>
1397
+ <Card>
1398
+ <CardContent className="pt-6">
1399
+ <div className="space-y-4">
1400
+ <div className="text-sm text-muted-foreground">
1401
+ <p>
1402
+ A ready-to-use chat interface component that integrates with the LLM system.
1403
+ Features markdown rendering, auto-scrolling, and loading states.
1404
+ </p>
1405
+ <p className="mt-2">
1406
+ This is a demo with simulated responses. In a real app, you'd connect it to a tRPC mutation.
1407
+ </p>
1408
+ </div>
1409
+ <AIChatBox
1410
+ messages={chatMessages}
1411
+ onSendMessage={handleChatSend}
1412
+ isLoading={isChatLoading}
1413
+ placeholder="Try sending a message..."
1414
+ height="500px"
1415
+ emptyStateMessage="How can I help you today?"
1416
+ suggestedPrompts={[
1417
+ "What is React?",
1418
+ "Explain TypeScript",
1419
+ "How to use tRPC?",
1420
+ "Best practices for web development",
1421
+ ]}
1422
+ />
1423
+ </div>
1424
+ </CardContent>
1425
+ </Card>
1426
+ </section>
1427
+ </div>
1428
+ </main>
1429
+
1430
+ <footer className="border-t py-6 mt-12">
1431
+ <div className="container text-center text-sm text-muted-foreground">
1432
+ <p>Shadcn/ui Component Showcase</p>
1433
+ </div>
1434
+ </footer>
1435
+ </div>
1436
+ );
1437
+ }
client/src/pages/Home.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import { Button } from "@/components/ui/button";
2
  import { LanguageSwitcher } from "@/components/LanguageSwitcher";
3
  import { useLanguage } from "@/contexts/LanguageContext";
@@ -15,6 +16,10 @@ import { useState, useEffect, createElement } from "react";
15
  */
16
 
17
  export default function Home() {
 
 
 
 
18
  const { language } = useLanguage();
19
  const t = getTranslation(language);
20
 
 
1
+ import { useAuth } from "@/_core/hooks/useAuth";
2
  import { Button } from "@/components/ui/button";
3
  import { LanguageSwitcher } from "@/components/LanguageSwitcher";
4
  import { useLanguage } from "@/contexts/LanguageContext";
 
16
  */
17
 
18
  export default function Home() {
19
+ // The userAuth hooks provides authentication state
20
+ // To implement login/logout functionality, simply call logout() or redirect to getLoginUrl()
21
+ let { user, loading, error, isAuthenticated, logout } = useAuth();
22
+
23
  const { language } = useLanguage();
24
  const t = getTranslation(language);
25
 
client/src/pages/NotFound.tsx CHANGED
@@ -33,7 +33,10 @@ export default function NotFound() {
33
  It may have been moved or deleted.
34
  </p>
35
 
36
- <div className="flex flex-col sm:flex-row gap-3 justify-center">
 
 
 
37
  <Button
38
  onClick={handleGoHome}
39
  className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2.5 rounded-lg transition-all duration-200 shadow-md hover:shadow-lg"
 
33
  It may have been moved or deleted.
34
  </p>
35
 
36
+ <div
37
+ id="not-found-button-group"
38
+ className="flex flex-col sm:flex-row gap-3 justify-center"
39
+ >
40
  <Button
41
  onClick={handleGoHome}
42
  className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2.5 rounded-lg transition-all duration-200 shadow-md hover:shadow-lg"
client/src/pages/Orders.tsx ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useAuth } from "@/_core/hooks/useAuth";
2
+ import { Button } from "@/components/ui/button";
3
+ import { Card } from "@/components/ui/card";
4
+ import { trpc } from "@/lib/trpc";
5
+ import { Loader2, CheckCircle, AlertCircle, Clock } from "lucide-react";
6
+ import { useLocation } from "wouter";
7
+ import { useEffect } from "react";
8
+ import { toast } from "sonner";
9
+
10
+ export default function Orders() {
11
+ const { isAuthenticated } = useAuth();
12
+ const [location, navigate] = useLocation();
13
+ const { data: payments, isLoading, error } = trpc.payments.getPaymentHistory.useQuery(
14
+ undefined,
15
+ { enabled: isAuthenticated }
16
+ );
17
+
18
+ useEffect(() => {
19
+ const params = new URLSearchParams(location.split("?")[1]);
20
+ if (params.get("success")) {
21
+ toast.success("Payment successful! Your order has been processed.");
22
+ }
23
+ if (params.get("cancelled")) {
24
+ toast.error("Payment was cancelled.");
25
+ }
26
+ }, [location]);
27
+
28
+ if (!isAuthenticated) {
29
+ return (
30
+ <div className="min-h-screen bg-background text-foreground flex items-center justify-center">
31
+ <div className="text-center space-y-4">
32
+ <h1 className="text-3xl font-bold">Sign In Required</h1>
33
+ <p className="text-muted-foreground">Please sign in to view your orders.</p>
34
+ <Button onClick={() => navigate("/")} className="mt-4">
35
+ Go Back Home
36
+ </Button>
37
+ </div>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ return (
43
+ <div className="min-h-screen bg-background text-foreground">
44
+ {/* Header */}
45
+ <header className="fixed top-0 w-full bg-background/80 backdrop-blur-xl z-50 border-b border-border">
46
+ <div className="container mx-auto px-4 py-4 flex items-center justify-between">
47
+ <div className="flex items-center gap-3">
48
+ <a href="/" className="font-bold text-xl gradient-text">
49
+ IMPULSO DIGITAL
50
+ </a>
51
+ </div>
52
+ <div className="flex items-center gap-4">
53
+ <a href="/products" className="text-sm hover:text-primary transition-colors">
54
+ Browse Plans
55
+ </a>
56
+ <a href="/" className="text-sm hover:text-primary transition-colors">
57
+ Home
58
+ </a>
59
+ </div>
60
+ </div>
61
+ </header>
62
+
63
+ {/* Main Content */}
64
+ <main className="pt-32 pb-20">
65
+ <div className="container mx-auto px-4 max-w-4xl">
66
+ <div className="mb-12">
67
+ <h1 className="text-5xl font-bold mb-2">
68
+ <span className="gradient-text">Order History</span>
69
+ </h1>
70
+ <p className="text-muted-foreground">View and manage your purchases and subscriptions</p>
71
+ </div>
72
+
73
+ {isLoading ? (
74
+ <div className="flex items-center justify-center py-12">
75
+ <Loader2 className="w-8 h-8 animate-spin text-primary" />
76
+ </div>
77
+ ) : error ? (
78
+ <Card className="bg-red-500/10 border-red-500/30 p-6">
79
+ <div className="flex gap-4">
80
+ <AlertCircle className="w-6 h-6 text-red-500 flex-shrink-0" />
81
+ <div>
82
+ <h3 className="font-bold text-foreground mb-1">Error Loading Orders</h3>
83
+ <p className="text-muted-foreground">
84
+ We couldn't load your orders. Please try again later.
85
+ </p>
86
+ </div>
87
+ </div>
88
+ </Card>
89
+ ) : payments && payments.length > 0 ? (
90
+ <div className="space-y-4">
91
+ {payments.map((payment) => (
92
+ <Card
93
+ key={payment.id}
94
+ className="bg-card border-primary/20 p-6 rounded-lg hover:border-primary/50 transition-all"
95
+ >
96
+ <div className="flex items-start justify-between gap-4">
97
+ <div className="flex-1">
98
+ <div className="flex items-center gap-3 mb-2">
99
+ <h3 className="font-bold text-lg text-foreground">{payment.description}</h3>
100
+ <StatusBadge status={payment.status} />
101
+ </div>
102
+
103
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
104
+ <div>
105
+ <p className="text-sm text-muted-foreground mb-1">Amount</p>
106
+ <p className="font-semibold text-foreground">
107
+ {formatCurrency(payment.amount, payment.currency)}
108
+ </p>
109
+ </div>
110
+
111
+ <div>
112
+ <p className="text-sm text-muted-foreground mb-1">Type</p>
113
+ <p className="font-semibold text-foreground capitalize">{payment.type}</p>
114
+ </div>
115
+
116
+ <div>
117
+ <p className="text-sm text-muted-foreground mb-1">Date</p>
118
+ <p className="font-semibold text-foreground">
119
+ {payment.created.toLocaleDateString()}
120
+ </p>
121
+ </div>
122
+
123
+ {payment.type === 'subscription' && 'currentPeriodEnd' in payment && payment.currentPeriodEnd && (
124
+ <div>
125
+ <p className="text-sm text-muted-foreground mb-1">Renews</p>
126
+ <p className="font-semibold text-foreground">
127
+ {payment.currentPeriodEnd.toLocaleDateString()}
128
+ </p>
129
+ </div>
130
+ )}
131
+ </div>
132
+ </div>
133
+
134
+ <div className="flex flex-col gap-2">
135
+ <Button variant="outline" size="sm">
136
+ View Details
137
+ </Button>
138
+ {payment.type === "subscription" && payment.status === "active" && (
139
+ <Button variant="outline" size="sm" className="text-red-500 hover:text-red-600">
140
+ Cancel
141
+ </Button>
142
+ )}
143
+ </div>
144
+ </div>
145
+ </Card>
146
+ ))}
147
+ </div>
148
+ ) : (
149
+ <Card className="bg-card border-primary/20 p-12 text-center">
150
+ <div className="space-y-4">
151
+ <h3 className="text-xl font-bold text-foreground">No Orders Yet</h3>
152
+ <p className="text-muted-foreground">
153
+ You haven't made any purchases or subscriptions yet.
154
+ </p>
155
+ <Button onClick={() => navigate("/products")} className="mt-4">
156
+ Browse Plans
157
+ </Button>
158
+ </div>
159
+ </Card>
160
+ )}
161
+
162
+ {/* Support Section */}
163
+ <div className="mt-12 bg-card border border-primary/20 p-8 rounded-lg">
164
+ <h2 className="text-2xl font-bold mb-4 text-foreground">Need Help?</h2>
165
+ <p className="text-muted-foreground mb-4">
166
+ If you have questions about your orders or subscriptions, please contact our support team.
167
+ </p>
168
+ <Button variant="outline" className="border-primary text-primary">
169
+ Contact Support
170
+ </Button>
171
+ </div>
172
+ </div>
173
+ </main>
174
+
175
+ {/* Footer */}
176
+ <footer className="bg-card border-t border-border py-8">
177
+ <div className="container mx-auto px-4 text-center text-muted-foreground text-sm">
178
+ <p>© 2026 Impulso Digital. All rights reserved.</p>
179
+ </div>
180
+ </footer>
181
+ </div>
182
+ );
183
+ }
184
+
185
+ function StatusBadge({ status }: { status: string }) {
186
+ const statusConfig: Record<string, { icon: React.ReactNode; color: string; label: string }> = {
187
+ succeeded: {
188
+ icon: <CheckCircle className="w-4 h-4" />,
189
+ color: "bg-green-500/10 text-green-500 border-green-500/30",
190
+ label: "Completed",
191
+ },
192
+ active: {
193
+ icon: <CheckCircle className="w-4 h-4" />,
194
+ color: "bg-blue-500/10 text-blue-500 border-blue-500/30",
195
+ label: "Active",
196
+ },
197
+ processing: {
198
+ icon: <Clock className="w-4 h-4" />,
199
+ color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/30",
200
+ label: "Processing",
201
+ },
202
+ canceled: {
203
+ icon: <AlertCircle className="w-4 h-4" />,
204
+ color: "bg-red-500/10 text-red-500 border-red-500/30",
205
+ label: "Cancelled",
206
+ },
207
+ };
208
+
209
+ const config = statusConfig[status] || statusConfig.processing;
210
+
211
+ return (
212
+ <span className={`inline-flex items-center gap-1 px-3 py-1 rounded-full text-xs font-medium border ${config.color}`}>
213
+ {config.icon}
214
+ {config.label}
215
+ </span>
216
+ );
217
+ }
218
+
219
+ function formatCurrency(amount: number, currency: string): string {
220
+ return new Intl.NumberFormat("en-US", {
221
+ style: "currency",
222
+ currency,
223
+ }).format(amount / 100);
224
+ }
client/src/pages/Products.tsx ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useAuth } from "@/_core/hooks/useAuth";
2
+ import { Button } from "@/components/ui/button";
3
+ import { Card } from "@/components/ui/card";
4
+ import { trpc } from "@/lib/trpc";
5
+ import { PRODUCTS, formatPrice } from "@shared/products";
6
+ import { Check, Loader2, Lock } from "lucide-react";
7
+ import { useState } from "react";
8
+ import { useLocation } from "wouter";
9
+ import { toast } from "sonner";
10
+ import { getLoginUrl } from "@/const";
11
+
12
+ export default function Products() {
13
+ const { user, isAuthenticated } = useAuth();
14
+ const [, navigate] = useLocation();
15
+ const [loadingProduct, setLoadingProduct] = useState<string | null>(null);
16
+
17
+ const createCheckoutMutation = trpc.payments.createCheckoutSession.useMutation();
18
+
19
+ const handleCheckout = async (productId: string) => {
20
+ if (!isAuthenticated) {
21
+ window.location.href = getLoginUrl();
22
+ return;
23
+ }
24
+
25
+ setLoadingProduct(productId);
26
+ try {
27
+ const origin = window.location.origin;
28
+ const result = await createCheckoutMutation.mutateAsync({
29
+ productId,
30
+ successUrl: `${origin}/orders?success=true`,
31
+ cancelUrl: `${origin}/products?cancelled=true`,
32
+ });
33
+
34
+ if (result.url) {
35
+ window.open(result.url, "_blank");
36
+ toast.success("Redirecting to checkout...");
37
+ }
38
+ } catch (error) {
39
+ console.error("Checkout error:", error);
40
+ toast.error("Failed to create checkout session");
41
+ } finally {
42
+ setLoadingProduct(null);
43
+ }
44
+ };
45
+
46
+ const subscriptionProducts = PRODUCTS.filter((p) => p.type === "subscription");
47
+ const oneTimeProducts = PRODUCTS.filter((p) => p.type === "one-time");
48
+
49
+ return (
50
+ <div className="min-h-screen bg-background text-foreground">
51
+ {/* Header */}
52
+ <header className="fixed top-0 w-full bg-background/80 backdrop-blur-xl z-50 border-b border-border">
53
+ <div className="container mx-auto px-4 py-4 flex items-center justify-between">
54
+ <div className="flex items-center gap-3">
55
+ <a href="/" className="font-bold text-xl gradient-text">
56
+ IMPULSO DIGITAL
57
+ </a>
58
+ </div>
59
+ <div className="flex items-center gap-4">
60
+ <a href="/" className="text-sm hover:text-primary transition-colors">
61
+ Back to Home
62
+ </a>
63
+ {isAuthenticated && (
64
+ <span className="text-sm text-muted-foreground">{user?.name}</span>
65
+ )}
66
+ </div>
67
+ </div>
68
+ </header>
69
+
70
+ {/* Main Content */}
71
+ <main className="pt-32 pb-20">
72
+ <div className="container mx-auto px-4">
73
+ {/* Hero */}
74
+ <div className="text-center mb-16 space-y-4">
75
+ <h1 className="text-5xl md:text-6xl font-bold">
76
+ <span className="gradient-text">Plans & Pricing</span>
77
+ </h1>
78
+ <p className="text-xl text-muted-foreground max-w-2xl mx-auto">
79
+ Choose the perfect plan for your organization. All plans include 24/7 support and
80
+ regular updates.
81
+ </p>
82
+ </div>
83
+
84
+ {/* Subscription Plans */}
85
+ <div className="mb-20">
86
+ <h2 className="text-3xl font-bold mb-8 text-center">Subscription Plans</h2>
87
+ <div className="grid md:grid-cols-3 gap-6 max-w-6xl mx-auto">
88
+ {subscriptionProducts.map((product) => (
89
+ <Card
90
+ key={product.id}
91
+ className="relative bg-card border-primary/30 p-8 rounded-2xl hover:border-primary/50 transition-all hover:shadow-lg hover:shadow-primary/20"
92
+ >
93
+ {/* Featured Badge */}
94
+ {product.id === "aurora-pro" && (
95
+ <div className="absolute -top-4 left-1/2 transform -translate-x-1/2">
96
+ <span className="bg-primary text-background px-4 py-1 rounded-full text-sm font-bold">
97
+ MOST POPULAR
98
+ </span>
99
+ </div>
100
+ )}
101
+
102
+ <div className="space-y-6">
103
+ {/* Product Info */}
104
+ <div>
105
+ <h3 className="text-2xl font-bold text-foreground mb-2">{product.name}</h3>
106
+ <p className="text-muted-foreground text-sm">{product.description}</p>
107
+ </div>
108
+
109
+ {/* Pricing */}
110
+ <div>
111
+ {product.price === 0 ? (
112
+ <div className="text-3xl font-bold text-primary mb-1">Custom</div>
113
+ ) : (
114
+ <>
115
+ <div className="text-4xl font-bold text-foreground">
116
+ {formatPrice(product.price)}
117
+ </div>
118
+ <p className="text-muted-foreground text-sm">
119
+ per {product.interval}
120
+ </p>
121
+ </>
122
+ )}
123
+ </div>
124
+
125
+ {/* Features */}
126
+ <div className="space-y-3">
127
+ {product.features?.map((feature, idx) => (
128
+ <div key={idx} className="flex gap-3 items-start">
129
+ <Check className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
130
+ <span className="text-sm text-foreground">{feature}</span>
131
+ </div>
132
+ ))}
133
+ </div>
134
+
135
+ {/* CTA Button */}
136
+ <Button
137
+ onClick={() => handleCheckout(product.id)}
138
+ disabled={loadingProduct === product.id}
139
+ className={`w-full py-6 font-bold text-lg transition-all ${
140
+ product.id === "aurora-pro"
141
+ ? "bg-primary hover:bg-cyan-600 text-background"
142
+ : "border-primary text-primary hover:bg-primary/10"
143
+ }`}
144
+ variant={product.id === "aurora-pro" ? "default" : "outline"}
145
+ >
146
+ {loadingProduct === product.id ? (
147
+ <>
148
+ <Loader2 className="w-4 h-4 mr-2 animate-spin" />
149
+ Processing...
150
+ </>
151
+ ) : product.price === 0 ? (
152
+ "Contact Sales"
153
+ ) : isAuthenticated ? (
154
+ "Subscribe Now"
155
+ ) : (
156
+ "Sign In to Subscribe"
157
+ )}
158
+ </Button>
159
+ </div>
160
+ </Card>
161
+ ))}
162
+ </div>
163
+ </div>
164
+
165
+ {/* One-Time Products */}
166
+ {oneTimeProducts.length > 0 && (
167
+ <div>
168
+ <h2 className="text-3xl font-bold mb-8 text-center">Additional Services</h2>
169
+ <div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
170
+ {oneTimeProducts.map((product) => (
171
+ <Card
172
+ key={product.id}
173
+ className="bg-card border-primary/30 p-8 rounded-2xl hover:border-primary/50 transition-all"
174
+ >
175
+ <div className="space-y-6">
176
+ <div>
177
+ <h3 className="text-2xl font-bold text-foreground mb-2">{product.name}</h3>
178
+ <p className="text-muted-foreground text-sm">{product.description}</p>
179
+ </div>
180
+
181
+ <div>
182
+ <div className="text-3xl font-bold text-foreground">
183
+ {formatPrice(product.price)}
184
+ </div>
185
+ <p className="text-muted-foreground text-sm">one-time payment</p>
186
+ </div>
187
+
188
+ <div className="space-y-2">
189
+ {product.features?.map((feature, idx) => (
190
+ <div key={idx} className="flex gap-2 items-start">
191
+ <Check className="w-4 h-4 text-primary flex-shrink-0 mt-0.5" />
192
+ <span className="text-sm text-foreground">{feature}</span>
193
+ </div>
194
+ ))}
195
+ </div>
196
+
197
+ <Button
198
+ onClick={() => handleCheckout(product.id)}
199
+ disabled={loadingProduct === product.id}
200
+ className="w-full py-6 font-bold"
201
+ >
202
+ {loadingProduct === product.id ? (
203
+ <>
204
+ <Loader2 className="w-4 h-4 mr-2 animate-spin" />
205
+ Processing...
206
+ </>
207
+ ) : isAuthenticated ? (
208
+ "Purchase Now"
209
+ ) : (
210
+ "Sign In to Purchase"
211
+ )}
212
+ </Button>
213
+ </div>
214
+ </Card>
215
+ ))}
216
+ </div>
217
+ </div>
218
+ )}
219
+
220
+ {/* FAQ Section */}
221
+ <div className="mt-20 max-w-3xl mx-auto">
222
+ <h2 className="text-3xl font-bold mb-8 text-center">Frequently Asked Questions</h2>
223
+ <div className="space-y-4">
224
+ {[
225
+ {
226
+ q: "Can I change my plan later?",
227
+ a: "Yes, you can upgrade or downgrade your subscription at any time. Changes take effect on your next billing cycle.",
228
+ },
229
+ {
230
+ q: "What payment methods do you accept?",
231
+ a: "We accept all major credit cards (Visa, Mastercard, American Express) through Stripe.",
232
+ },
233
+ {
234
+ q: "Is there a free trial?",
235
+ a: "Contact our sales team to discuss trial options for enterprise plans.",
236
+ },
237
+ {
238
+ q: "Can I cancel my subscription?",
239
+ a: "Yes, you can cancel anytime from your account settings. Your access continues until the end of the billing period.",
240
+ },
241
+ ].map((item, idx) => (
242
+ <div key={idx} className="bg-card border border-primary/20 p-6 rounded-lg">
243
+ <h3 className="font-bold text-foreground mb-2">{item.q}</h3>
244
+ <p className="text-muted-foreground">{item.a}</p>
245
+ </div>
246
+ ))}
247
+ </div>
248
+ </div>
249
+
250
+ {/* Test Card Info */}
251
+ {!process.env.NODE_ENV || process.env.NODE_ENV === "development" ? (
252
+ <div className="mt-12 max-w-2xl mx-auto bg-yellow-500/10 border border-yellow-500/30 p-6 rounded-lg">
253
+ <p className="text-sm text-foreground">
254
+ <strong>Testing:</strong> Use card number <code className="bg-background px-2 py-1 rounded">4242 4242 4242 4242</code> with any future expiry date and CVC to test payments in sandbox mode.
255
+ </p>
256
+ </div>
257
+ ) : null}
258
+ </div>
259
+ </main>
260
+
261
+ {/* Footer */}
262
+ <footer className="bg-card border-t border-border py-8">
263
+ <div className="container mx-auto px-4 text-center text-muted-foreground text-sm">
264
+ <p>© 2026 Impulso Digital. All rights reserved.</p>
265
+ </div>
266
+ </footer>
267
+ </div>
268
+ );
269
+ }
drizzle.config.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from "drizzle-kit";
2
+
3
+ const connectionString = process.env.DATABASE_URL;
4
+ if (!connectionString) {
5
+ throw new Error("DATABASE_URL is required to run drizzle commands");
6
+ }
7
+
8
+ export default defineConfig({
9
+ schema: "./drizzle/schema.ts",
10
+ out: "./drizzle",
11
+ dialect: "mysql",
12
+ dbCredentials: {
13
+ url: connectionString,
14
+ },
15
+ });
drizzle/0000_equal_swarm.sql ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ CREATE TABLE `users` (
2
+ `id` int AUTO_INCREMENT NOT NULL,
3
+ `openId` varchar(64) NOT NULL,
4
+ `name` text,
5
+ `email` varchar(320),
6
+ `loginMethod` varchar(64),
7
+ `role` enum('user','admin') NOT NULL DEFAULT 'user',
8
+ `createdAt` timestamp NOT NULL DEFAULT (now()),
9
+ `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,
10
+ `lastSignedIn` timestamp NOT NULL DEFAULT (now()),
11
+ CONSTRAINT `users_id` PRIMARY KEY(`id`),
12
+ CONSTRAINT `users_openId_unique` UNIQUE(`openId`)
13
+ );
drizzle/meta/0000_snapshot.json ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "5",
3
+ "dialect": "mysql",
4
+ "id": "335a9d4b-d32d-4da6-ad4b-ec1827ed0083",
5
+ "prevId": "00000000-0000-0000-0000-000000000000",
6
+ "tables": {
7
+ "users": {
8
+ "name": "users",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "int",
13
+ "primaryKey": false,
14
+ "notNull": true,
15
+ "autoincrement": true
16
+ },
17
+ "openId": {
18
+ "name": "openId",
19
+ "type": "varchar(64)",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false
23
+ },
24
+ "name": {
25
+ "name": "name",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": false,
29
+ "autoincrement": false
30
+ },
31
+ "email": {
32
+ "name": "email",
33
+ "type": "varchar(320)",
34
+ "primaryKey": false,
35
+ "notNull": false,
36
+ "autoincrement": false
37
+ },
38
+ "loginMethod": {
39
+ "name": "loginMethod",
40
+ "type": "varchar(64)",
41
+ "primaryKey": false,
42
+ "notNull": false,
43
+ "autoincrement": false
44
+ },
45
+ "role": {
46
+ "name": "role",
47
+ "type": "enum('user','admin')",
48
+ "primaryKey": false,
49
+ "notNull": true,
50
+ "autoincrement": false,
51
+ "default": "'user'"
52
+ },
53
+ "createdAt": {
54
+ "name": "createdAt",
55
+ "type": "timestamp",
56
+ "primaryKey": false,
57
+ "notNull": true,
58
+ "autoincrement": false,
59
+ "default": "(now())"
60
+ },
61
+ "updatedAt": {
62
+ "name": "updatedAt",
63
+ "type": "timestamp",
64
+ "primaryKey": false,
65
+ "notNull": true,
66
+ "autoincrement": false,
67
+ "onUpdate": true,
68
+ "default": "(now())"
69
+ },
70
+ "lastSignedIn": {
71
+ "name": "lastSignedIn",
72
+ "type": "timestamp",
73
+ "primaryKey": false,
74
+ "notNull": true,
75
+ "autoincrement": false,
76
+ "default": "(now())"
77
+ }
78
+ },
79
+ "indexes": {},
80
+ "foreignKeys": {},
81
+ "compositePrimaryKeys": {
82
+ "users_id": {
83
+ "name": "users_id",
84
+ "columns": [
85
+ "id"
86
+ ]
87
+ }
88
+ },
89
+ "uniqueConstraints": {
90
+ "users_openId_unique": {
91
+ "name": "users_openId_unique",
92
+ "columns": [
93
+ "openId"
94
+ ]
95
+ }
96
+ },
97
+ "checkConstraint": {}
98
+ }
99
+ },
100
+ "views": {},
101
+ "_meta": {
102
+ "schemas": {},
103
+ "tables": {},
104
+ "columns": {}
105
+ },
106
+ "internal": {
107
+ "tables": {},
108
+ "indexes": {}
109
+ }
110
+ }
drizzle/meta/_journal.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "7",
3
+ "dialect": "mysql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "5",
8
+ "when": 1770383128513,
9
+ "tag": "0000_equal_swarm",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
drizzle/migrations/.gitkeep ADDED
File without changes
drizzle/relations.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ import {} from "./schema";
drizzle/schema.ts ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { int, mysqlEnum, mysqlTable, text, timestamp, varchar } from "drizzle-orm/mysql-core";
2
+
3
+ /**
4
+ * Core user table backing auth flow.
5
+ * Extend this file with additional tables as your product grows.
6
+ * Columns use camelCase to match both database fields and generated types.
7
+ */
8
+ export const users = mysqlTable("users", {
9
+ /**
10
+ * Surrogate primary key. Auto-incremented numeric value managed by the database.
11
+ * Use this for relations between tables.
12
+ */
13
+ id: int("id").autoincrement().primaryKey(),
14
+ /** Manus OAuth identifier (openId) returned from the OAuth callback. Unique per user. */
15
+ openId: varchar("openId", { length: 64 }).notNull().unique(),
16
+ name: text("name"),
17
+ email: varchar("email", { length: 320 }),
18
+ loginMethod: varchar("loginMethod", { length: 64 }),
19
+ role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(),
20
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
21
+ updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
22
+ lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull(),
23
+ });
24
+
25
+ export type User = typeof users.$inferSelect;
26
+ export type InsertUser = typeof users.$inferInsert;
27
+
28
+ // TODO: Add your tables here
package.json CHANGED
@@ -4,14 +4,17 @@
4
  "type": "module",
5
  "license": "MIT",
6
  "scripts": {
7
- "dev": "vite --host",
8
- "build": "vite build && esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist",
9
  "start": "NODE_ENV=production node dist/index.js",
10
- "preview": "vite preview --host",
11
  "check": "tsc --noEmit",
12
- "format": "prettier --write ."
 
 
13
  },
14
  "dependencies": {
 
 
15
  "@hookform/resolvers": "^5.2.2",
16
  "@radix-ui/react-accordion": "^1.2.12",
17
  "@radix-ui/react-alert-dialog": "^1.1.15",
@@ -39,15 +42,25 @@
39
  "@radix-ui/react-toggle": "^1.1.10",
40
  "@radix-ui/react-toggle-group": "^1.1.11",
41
  "@radix-ui/react-tooltip": "^1.2.8",
 
 
 
 
42
  "axios": "^1.12.0",
43
  "class-variance-authority": "^0.7.1",
44
  "clsx": "^2.1.1",
45
  "cmdk": "^1.1.1",
 
 
 
 
46
  "embla-carousel-react": "^8.6.0",
47
  "express": "^4.21.2",
48
  "framer-motion": "^12.23.22",
49
  "input-otp": "^1.4.2",
 
50
  "lucide-react": "^0.453.0",
 
51
  "nanoid": "^5.1.5",
52
  "next-themes": "^0.4.6",
53
  "react": "^19.2.1",
@@ -58,6 +71,8 @@
58
  "recharts": "^2.15.2",
59
  "sonner": "^2.0.7",
60
  "streamdown": "^1.4.0",
 
 
61
  "tailwind-merge": "^3.3.1",
62
  "tailwindcss-animate": "^1.0.7",
63
  "vaul": "^1.1.2",
@@ -76,6 +91,7 @@
76
  "@vitejs/plugin-react": "^5.0.4",
77
  "add": "^2.0.6",
78
  "autoprefixer": "^10.4.20",
 
79
  "esbuild": "^0.25.0",
80
  "pnpm": "^10.15.1",
81
  "postcss": "^8.4.47",
@@ -83,7 +99,7 @@
83
  "tailwindcss": "^4.1.14",
84
  "tsx": "^4.19.1",
85
  "tw-animate-css": "^1.4.0",
86
- "typescript": "5.6.3",
87
  "vite": "^7.1.7",
88
  "vite-plugin-manus-runtime": "^0.0.57",
89
  "vitest": "^2.1.4"
@@ -97,4 +113,4 @@
97
  "tailwindcss>nanoid": "3.3.7"
98
  }
99
  }
100
- }
 
4
  "type": "module",
5
  "license": "MIT",
6
  "scripts": {
7
+ "dev": "NODE_ENV=development tsx watch server/_core/index.ts",
8
+ "build": "vite build && esbuild server/_core/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist",
9
  "start": "NODE_ENV=production node dist/index.js",
 
10
  "check": "tsc --noEmit",
11
+ "format": "prettier --write .",
12
+ "test": "vitest run",
13
+ "db:push": "drizzle-kit generate && drizzle-kit migrate"
14
  },
15
  "dependencies": {
16
+ "@aws-sdk/client-s3": "^3.693.0",
17
+ "@aws-sdk/s3-request-presigner": "^3.693.0",
18
  "@hookform/resolvers": "^5.2.2",
19
  "@radix-ui/react-accordion": "^1.2.12",
20
  "@radix-ui/react-alert-dialog": "^1.1.15",
 
42
  "@radix-ui/react-toggle": "^1.1.10",
43
  "@radix-ui/react-toggle-group": "^1.1.11",
44
  "@radix-ui/react-tooltip": "^1.2.8",
45
+ "@tanstack/react-query": "^5.90.2",
46
+ "@trpc/client": "^11.6.0",
47
+ "@trpc/react-query": "^11.6.0",
48
+ "@trpc/server": "^11.6.0",
49
  "axios": "^1.12.0",
50
  "class-variance-authority": "^0.7.1",
51
  "clsx": "^2.1.1",
52
  "cmdk": "^1.1.1",
53
+ "cookie": "^1.0.2",
54
+ "date-fns": "^4.1.0",
55
+ "dotenv": "^17.2.2",
56
+ "drizzle-orm": "^0.44.5",
57
  "embla-carousel-react": "^8.6.0",
58
  "express": "^4.21.2",
59
  "framer-motion": "^12.23.22",
60
  "input-otp": "^1.4.2",
61
+ "jose": "6.1.0",
62
  "lucide-react": "^0.453.0",
63
+ "mysql2": "^3.15.0",
64
  "nanoid": "^5.1.5",
65
  "next-themes": "^0.4.6",
66
  "react": "^19.2.1",
 
71
  "recharts": "^2.15.2",
72
  "sonner": "^2.0.7",
73
  "streamdown": "^1.4.0",
74
+ "stripe": "^20.3.1",
75
+ "superjson": "^1.13.3",
76
  "tailwind-merge": "^3.3.1",
77
  "tailwindcss-animate": "^1.0.7",
78
  "vaul": "^1.1.2",
 
91
  "@vitejs/plugin-react": "^5.0.4",
92
  "add": "^2.0.6",
93
  "autoprefixer": "^10.4.20",
94
+ "drizzle-kit": "^0.31.4",
95
  "esbuild": "^0.25.0",
96
  "pnpm": "^10.15.1",
97
  "postcss": "^8.4.47",
 
99
  "tailwindcss": "^4.1.14",
100
  "tsx": "^4.19.1",
101
  "tw-animate-css": "^1.4.0",
102
+ "typescript": "5.9.3",
103
  "vite": "^7.1.7",
104
  "vite-plugin-manus-runtime": "^0.0.57",
105
  "vitest": "^2.1.4"
 
113
  "tailwindcss>nanoid": "3.3.7"
114
  }
115
  }
116
+ }
pnpm-lock.yaml CHANGED
@@ -16,6 +16,12 @@ importers:
16
 
17
  .:
18
  dependencies:
 
 
 
 
 
 
19
  '@hookform/resolvers':
20
  specifier: ^5.2.2
21
  version: 5.2.2(react-hook-form@7.64.0(react@19.2.1))
@@ -97,6 +103,18 @@ importers:
97
  '@radix-ui/react-tooltip':
98
  specifier: ^1.2.8
99
  version: 1.2.8(@types/react-dom@19.2.1(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
 
 
 
 
 
 
 
 
 
 
 
 
100
  axios:
101
  specifier: ^1.12.0
102
  version: 1.12.2
@@ -109,6 +127,18 @@ importers:
109
  cmdk:
110
  specifier: ^1.1.1
111
  version: 1.1.1(@types/react-dom@19.2.1(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
 
 
 
 
 
 
 
 
 
 
 
 
112
  embla-carousel-react:
113
  specifier: ^8.6.0
114
  version: 8.6.0(react@19.2.1)
@@ -121,9 +151,15 @@ importers:
121
  input-otp:
122
  specifier: ^1.4.2
123
  version: 1.4.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
 
 
 
124
  lucide-react:
125
  specifier: ^0.453.0
126
  version: 0.453.0(react@19.2.1)
 
 
 
127
  nanoid:
128
  specifier: ^5.1.5
129
  version: 5.1.6
@@ -154,6 +190,12 @@ importers:
154
  streamdown:
155
  specifier: ^1.4.0
156
  version: 1.4.0(@types/react@19.2.1)(react@19.2.1)
 
 
 
 
 
 
157
  tailwind-merge:
158
  specifier: ^3.3.1
159
  version: 3.3.1
@@ -203,6 +245,9 @@ importers:
203
  autoprefixer:
204
  specifier: ^10.4.20
205
  version: 10.4.21(postcss@8.5.6)
 
 
 
206
  esbuild:
207
  specifier: ^0.25.0
208
  version: 0.25.10
@@ -225,8 +270,8 @@ importers:
225
  specifier: ^1.4.0
226
  version: 1.4.0
227
  typescript:
228
- specifier: 5.6.3
229
- version: 5.6.3
230
  vite:
231
  specifier: ^7.1.7
232
  version: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)
@@ -245,6 +290,181 @@ packages:
245
  '@antfu/utils@9.3.0':
246
  resolution: {integrity: sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==}
247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  '@babel/code-frame@7.27.1':
249
  resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
250
  engines: {node: '>=6.9.0'}
@@ -361,6 +581,17 @@ packages:
361
  '@date-fns/tz@1.4.1':
362
  resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
363
 
 
 
 
 
 
 
 
 
 
 
 
364
  '@esbuild/aix-ppc64@0.21.5':
365
  resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
366
  engines: {node: '>=12'}
@@ -373,6 +604,12 @@ packages:
373
  cpu: [ppc64]
374
  os: [aix]
375
 
 
 
 
 
 
 
376
  '@esbuild/android-arm64@0.21.5':
377
  resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
378
  engines: {node: '>=12'}
@@ -385,6 +622,12 @@ packages:
385
  cpu: [arm64]
386
  os: [android]
387
 
 
 
 
 
 
 
388
  '@esbuild/android-arm@0.21.5':
389
  resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
390
  engines: {node: '>=12'}
@@ -397,6 +640,12 @@ packages:
397
  cpu: [arm]
398
  os: [android]
399
 
 
 
 
 
 
 
400
  '@esbuild/android-x64@0.21.5':
401
  resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
402
  engines: {node: '>=12'}
@@ -409,6 +658,12 @@ packages:
409
  cpu: [x64]
410
  os: [android]
411
 
 
 
 
 
 
 
412
  '@esbuild/darwin-arm64@0.21.5':
413
  resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
414
  engines: {node: '>=12'}
@@ -421,6 +676,12 @@ packages:
421
  cpu: [arm64]
422
  os: [darwin]
423
 
 
 
 
 
 
 
424
  '@esbuild/darwin-x64@0.21.5':
425
  resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
426
  engines: {node: '>=12'}
@@ -433,6 +694,12 @@ packages:
433
  cpu: [x64]
434
  os: [darwin]
435
 
 
 
 
 
 
 
436
  '@esbuild/freebsd-arm64@0.21.5':
437
  resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
438
  engines: {node: '>=12'}
@@ -445,6 +712,12 @@ packages:
445
  cpu: [arm64]
446
  os: [freebsd]
447
 
 
 
 
 
 
 
448
  '@esbuild/freebsd-x64@0.21.5':
449
  resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
450
  engines: {node: '>=12'}
@@ -457,6 +730,12 @@ packages:
457
  cpu: [x64]
458
  os: [freebsd]
459
 
 
 
 
 
 
 
460
  '@esbuild/linux-arm64@0.21.5':
461
  resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
462
  engines: {node: '>=12'}
@@ -469,6 +748,12 @@ packages:
469
  cpu: [arm64]
470
  os: [linux]
471
 
 
 
 
 
 
 
472
  '@esbuild/linux-arm@0.21.5':
473
  resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
474
  engines: {node: '>=12'}
@@ -481,6 +766,12 @@ packages:
481
  cpu: [arm]
482
  os: [linux]
483
 
 
 
 
 
 
 
484
  '@esbuild/linux-ia32@0.21.5':
485
  resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
486
  engines: {node: '>=12'}
@@ -493,6 +784,12 @@ packages:
493
  cpu: [ia32]
494
  os: [linux]
495
 
 
 
 
 
 
 
496
  '@esbuild/linux-loong64@0.21.5':
497
  resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
498
  engines: {node: '>=12'}
@@ -505,6 +802,12 @@ packages:
505
  cpu: [loong64]
506
  os: [linux]
507
 
 
 
 
 
 
 
508
  '@esbuild/linux-mips64el@0.21.5':
509
  resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
510
  engines: {node: '>=12'}
@@ -517,6 +820,12 @@ packages:
517
  cpu: [mips64el]
518
  os: [linux]
519
 
 
 
 
 
 
 
520
  '@esbuild/linux-ppc64@0.21.5':
521
  resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
522
  engines: {node: '>=12'}
@@ -529,6 +838,12 @@ packages:
529
  cpu: [ppc64]
530
  os: [linux]
531
 
 
 
 
 
 
 
532
  '@esbuild/linux-riscv64@0.21.5':
533
  resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
534
  engines: {node: '>=12'}
@@ -541,6 +856,12 @@ packages:
541
  cpu: [riscv64]
542
  os: [linux]
543
 
 
 
 
 
 
 
544
  '@esbuild/linux-s390x@0.21.5':
545
  resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
546
  engines: {node: '>=12'}
@@ -553,6 +874,12 @@ packages:
553
  cpu: [s390x]
554
  os: [linux]
555
 
 
 
 
 
 
 
556
  '@esbuild/linux-x64@0.21.5':
557
  resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
558
  engines: {node: '>=12'}
@@ -571,6 +898,12 @@ packages:
571
  cpu: [arm64]
572
  os: [netbsd]
573
 
 
 
 
 
 
 
574
  '@esbuild/netbsd-x64@0.21.5':
575
  resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
576
  engines: {node: '>=12'}
@@ -589,6 +922,12 @@ packages:
589
  cpu: [arm64]
590
  os: [openbsd]
591
 
 
 
 
 
 
 
592
  '@esbuild/openbsd-x64@0.21.5':
593
  resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
594
  engines: {node: '>=12'}
@@ -607,6 +946,12 @@ packages:
607
  cpu: [arm64]
608
  os: [openharmony]
609
 
 
 
 
 
 
 
610
  '@esbuild/sunos-x64@0.21.5':
611
  resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
612
  engines: {node: '>=12'}
@@ -619,6 +964,12 @@ packages:
619
  cpu: [x64]
620
  os: [sunos]
621
 
 
 
 
 
 
 
622
  '@esbuild/win32-arm64@0.21.5':
623
  resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
624
  engines: {node: '>=12'}
@@ -631,6 +982,12 @@ packages:
631
  cpu: [arm64]
632
  os: [win32]
633
 
 
 
 
 
 
 
634
  '@esbuild/win32-ia32@0.21.5':
635
  resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
636
  engines: {node: '>=12'}
@@ -643,6 +1000,12 @@ packages:
643
  cpu: [ia32]
644
  os: [win32]
645
 
 
 
 
 
 
 
646
  '@esbuild/win32-x64@0.21.5':
647
  resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
648
  engines: {node: '>=12'}
@@ -1453,6 +1816,222 @@ packages:
1453
  '@shikijs/vscode-textmate@10.0.2':
1454
  resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
1455
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1456
  '@standard-schema/utils@0.3.0':
1457
  resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
1458
 
@@ -1551,6 +2130,35 @@ packages:
1551
  peerDependencies:
1552
  vite: ^5.2.0 || ^6 || ^7
1553
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1554
  '@types/babel__core@7.20.5':
1555
  resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
1556
 
@@ -1807,6 +2415,10 @@ packages:
1807
  peerDependencies:
1808
  postcss: ^8.1.0
1809
 
 
 
 
 
1810
  axios@1.12.2:
1811
  resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==}
1812
 
@@ -1821,11 +2433,17 @@ packages:
1821
  resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
1822
  engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
1823
 
 
 
 
1824
  browserslist@4.26.3:
1825
  resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==}
1826
  engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
1827
  hasBin: true
1828
 
 
 
 
1829
  bytes@3.1.2:
1830
  resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
1831
  engines: {node: '>= 0.8'}
@@ -1932,6 +2550,14 @@ packages:
1932
  resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
1933
  engines: {node: '>= 0.6'}
1934
 
 
 
 
 
 
 
 
 
1935
  cose-base@1.0.3:
1936
  resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
1937
 
@@ -2145,6 +2771,10 @@ packages:
2145
  resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
2146
  engines: {node: '>=0.4.0'}
2147
 
 
 
 
 
2148
  depd@2.0.0:
2149
  resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
2150
  engines: {node: '>= 0.8'}
@@ -2173,6 +2803,106 @@ packages:
2173
  dompurify@3.3.0:
2174
  resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==}
2175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2176
  dunder-proto@1.0.1:
2177
  resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
2178
  engines: {node: '>= 0.4'}
@@ -2231,6 +2961,16 @@ packages:
2231
  resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
2232
  engines: {node: '>= 0.4'}
2233
 
 
 
 
 
 
 
 
 
 
 
2234
  esbuild@0.21.5:
2235
  resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
2236
  engines: {node: '>=12'}
@@ -2286,6 +3026,10 @@ packages:
2286
  resolution: {integrity: sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==}
2287
  engines: {node: '>=6.0.0'}
2288
 
 
 
 
 
2289
  fdir@6.5.0:
2290
  resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
2291
  engines: {node: '>=12.0.0'}
@@ -2345,6 +3089,9 @@ packages:
2345
  function-bind@1.1.2:
2346
  resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
2347
 
 
 
 
2348
  gensync@1.0.0-beta.2:
2349
  resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
2350
  engines: {node: '>=6.9.0'}
@@ -2447,6 +3194,10 @@ packages:
2447
  resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
2448
  engines: {node: '>=0.10.0'}
2449
 
 
 
 
 
2450
  inherits@2.0.4:
2451
  resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
2452
 
@@ -2486,10 +3237,20 @@ packages:
2486
  resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
2487
  engines: {node: '>=12'}
2488
 
 
 
 
 
 
 
 
2489
  jiti@2.6.1:
2490
  resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
2491
  hasBin: true
2492
 
 
 
 
2493
  js-tokens@4.0.0:
2494
  resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
2495
 
@@ -2597,6 +3358,9 @@ packages:
2597
  lodash@4.17.21:
2598
  resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
2599
 
 
 
 
2600
  longest-streak@3.1.0:
2601
  resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
2602
 
@@ -2610,6 +3374,10 @@ packages:
2610
  lru-cache@5.1.1:
2611
  resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
2612
 
 
 
 
 
2613
  lucide-react@0.453.0:
2614
  resolution: {integrity: sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==}
2615
  peerDependencies:
@@ -2826,6 +3594,14 @@ packages:
2826
  ms@2.1.3:
2827
  resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
2828
 
 
 
 
 
 
 
 
 
2829
  nanoid@3.3.11:
2830
  resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
2831
  engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -3137,6 +3913,9 @@ packages:
3137
  resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
3138
  engines: {node: '>= 0.8.0'}
3139
 
 
 
 
3140
  serve-static@1.16.2:
3141
  resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
3142
  engines: {node: '>= 0.8.0'}
@@ -3176,9 +3955,20 @@ packages:
3176
  resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
3177
  engines: {node: '>=0.10.0'}
3178
 
 
 
 
 
 
 
 
3179
  space-separated-tokens@2.0.2:
3180
  resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
3181
 
 
 
 
 
3182
  stackback@0.0.2:
3183
  resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
3184
 
@@ -3197,6 +3987,18 @@ packages:
3197
  stringify-entities@4.0.4:
3198
  resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
3199
 
 
 
 
 
 
 
 
 
 
 
 
 
3200
  style-to-js@1.1.18:
3201
  resolution: {integrity: sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==}
3202
 
@@ -3206,6 +4008,10 @@ packages:
3206
  stylis@4.3.6:
3207
  resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
3208
 
 
 
 
 
3209
  tailwind-merge@3.3.1:
3210
  resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
3211
 
@@ -3282,8 +4088,8 @@ packages:
3282
  resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
3283
  engines: {node: '>= 0.6'}
3284
 
3285
- typescript@5.6.3:
3286
- resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
3287
  engines: {node: '>=14.17'}
3288
  hasBin: true
3289
 
@@ -3544,6 +4350,517 @@ snapshots:
3544
 
3545
  '@antfu/utils@9.3.0': {}
3546
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3547
  '@babel/code-frame@7.27.1':
3548
  dependencies:
3549
  '@babel/helper-validator-identifier': 7.27.1
@@ -3690,102 +5007,162 @@ snapshots:
3690
 
3691
  '@date-fns/tz@1.4.1': {}
3692
 
 
 
 
 
 
 
 
 
 
 
 
 
3693
  '@esbuild/aix-ppc64@0.21.5':
3694
  optional: true
3695
 
3696
  '@esbuild/aix-ppc64@0.25.10':
3697
  optional: true
3698
 
 
 
 
3699
  '@esbuild/android-arm64@0.21.5':
3700
  optional: true
3701
 
3702
  '@esbuild/android-arm64@0.25.10':
3703
  optional: true
3704
 
 
 
 
3705
  '@esbuild/android-arm@0.21.5':
3706
  optional: true
3707
 
3708
  '@esbuild/android-arm@0.25.10':
3709
  optional: true
3710
 
 
 
 
3711
  '@esbuild/android-x64@0.21.5':
3712
  optional: true
3713
 
3714
  '@esbuild/android-x64@0.25.10':
3715
  optional: true
3716
 
 
 
 
3717
  '@esbuild/darwin-arm64@0.21.5':
3718
  optional: true
3719
 
3720
  '@esbuild/darwin-arm64@0.25.10':
3721
  optional: true
3722
 
 
 
 
3723
  '@esbuild/darwin-x64@0.21.5':
3724
  optional: true
3725
 
3726
  '@esbuild/darwin-x64@0.25.10':
3727
  optional: true
3728
 
 
 
 
3729
  '@esbuild/freebsd-arm64@0.21.5':
3730
  optional: true
3731
 
3732
  '@esbuild/freebsd-arm64@0.25.10':
3733
  optional: true
3734
 
 
 
 
3735
  '@esbuild/freebsd-x64@0.21.5':
3736
  optional: true
3737
 
3738
  '@esbuild/freebsd-x64@0.25.10':
3739
  optional: true
3740
 
 
 
 
3741
  '@esbuild/linux-arm64@0.21.5':
3742
  optional: true
3743
 
3744
  '@esbuild/linux-arm64@0.25.10':
3745
  optional: true
3746
 
 
 
 
3747
  '@esbuild/linux-arm@0.21.5':
3748
  optional: true
3749
 
3750
  '@esbuild/linux-arm@0.25.10':
3751
  optional: true
3752
 
 
 
 
3753
  '@esbuild/linux-ia32@0.21.5':
3754
  optional: true
3755
 
3756
  '@esbuild/linux-ia32@0.25.10':
3757
  optional: true
3758
 
 
 
 
3759
  '@esbuild/linux-loong64@0.21.5':
3760
  optional: true
3761
 
3762
  '@esbuild/linux-loong64@0.25.10':
3763
  optional: true
3764
 
 
 
 
3765
  '@esbuild/linux-mips64el@0.21.5':
3766
  optional: true
3767
 
3768
  '@esbuild/linux-mips64el@0.25.10':
3769
  optional: true
3770
 
 
 
 
3771
  '@esbuild/linux-ppc64@0.21.5':
3772
  optional: true
3773
 
3774
  '@esbuild/linux-ppc64@0.25.10':
3775
  optional: true
3776
 
 
 
 
3777
  '@esbuild/linux-riscv64@0.21.5':
3778
  optional: true
3779
 
3780
  '@esbuild/linux-riscv64@0.25.10':
3781
  optional: true
3782
 
 
 
 
3783
  '@esbuild/linux-s390x@0.21.5':
3784
  optional: true
3785
 
3786
  '@esbuild/linux-s390x@0.25.10':
3787
  optional: true
3788
 
 
 
 
3789
  '@esbuild/linux-x64@0.21.5':
3790
  optional: true
3791
 
@@ -3795,6 +5172,9 @@ snapshots:
3795
  '@esbuild/netbsd-arm64@0.25.10':
3796
  optional: true
3797
 
 
 
 
3798
  '@esbuild/netbsd-x64@0.21.5':
3799
  optional: true
3800
 
@@ -3804,6 +5184,9 @@ snapshots:
3804
  '@esbuild/openbsd-arm64@0.25.10':
3805
  optional: true
3806
 
 
 
 
3807
  '@esbuild/openbsd-x64@0.21.5':
3808
  optional: true
3809
 
@@ -3813,24 +5196,36 @@ snapshots:
3813
  '@esbuild/openharmony-arm64@0.25.10':
3814
  optional: true
3815
 
 
 
 
3816
  '@esbuild/sunos-x64@0.21.5':
3817
  optional: true
3818
 
3819
  '@esbuild/sunos-x64@0.25.10':
3820
  optional: true
3821
 
 
 
 
3822
  '@esbuild/win32-arm64@0.21.5':
3823
  optional: true
3824
 
3825
  '@esbuild/win32-arm64@0.25.10':
3826
  optional: true
3827
 
 
 
 
3828
  '@esbuild/win32-ia32@0.21.5':
3829
  optional: true
3830
 
3831
  '@esbuild/win32-ia32@0.25.10':
3832
  optional: true
3833
 
 
 
 
3834
  '@esbuild/win32-x64@0.21.5':
3835
  optional: true
3836
 
@@ -4657,6 +6052,344 @@ snapshots:
4657
 
4658
  '@shikijs/vscode-textmate@10.0.2': {}
4659
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4660
  '@standard-schema/utils@0.3.0': {}
4661
 
4662
  '@tailwindcss/node@4.1.14':
@@ -4735,6 +6468,31 @@ snapshots:
4735
  tailwindcss: 4.1.14
4736
  vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)
4737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4738
  '@types/babel__core@7.20.5':
4739
  dependencies:
4740
  '@babel/parser': 7.28.4
@@ -5047,6 +6805,8 @@ snapshots:
5047
  postcss: 8.5.6
5048
  postcss-value-parser: 4.2.0
5049
 
 
 
5050
  axios@1.12.2:
5051
  dependencies:
5052
  follow-redirects: 1.15.11
@@ -5076,6 +6836,8 @@ snapshots:
5076
  transitivePeerDependencies:
5077
  - supports-color
5078
 
 
 
5079
  browserslist@4.26.3:
5080
  dependencies:
5081
  baseline-browser-mapping: 2.8.13
@@ -5084,6 +6846,8 @@ snapshots:
5084
  node-releases: 2.0.23
5085
  update-browserslist-db: 1.1.3(browserslist@4.26.3)
5086
 
 
 
5087
  bytes@3.1.2: {}
5088
 
5089
  cac@6.7.14: {}
@@ -5180,6 +6944,12 @@ snapshots:
5180
 
5181
  cookie@0.7.1: {}
5182
 
 
 
 
 
 
 
5183
  cose-base@1.0.3:
5184
  dependencies:
5185
  layout-base: 1.0.2
@@ -5404,6 +7174,8 @@ snapshots:
5404
 
5405
  delayed-stream@1.0.0: {}
5406
 
 
 
5407
  depd@2.0.0: {}
5408
 
5409
  dequal@2.0.3: {}
@@ -5427,6 +7199,21 @@ snapshots:
5427
  optionalDependencies:
5428
  '@types/trusted-types': 2.0.7
5429
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5430
  dunder-proto@1.0.1:
5431
  dependencies:
5432
  call-bind-apply-helpers: 1.0.2
@@ -5477,6 +7264,38 @@ snapshots:
5477
  has-tostringtag: 1.0.2
5478
  hasown: 2.0.2
5479
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5480
  esbuild@0.21.5:
5481
  optionalDependencies:
5482
  '@esbuild/aix-ppc64': 0.21.5
@@ -5594,6 +7413,10 @@ snapshots:
5594
 
5595
  fast-equals@5.3.2: {}
5596
 
 
 
 
 
5597
  fdir@6.5.0(picomatch@4.0.3):
5598
  optionalDependencies:
5599
  picomatch: 4.0.3
@@ -5640,6 +7463,10 @@ snapshots:
5640
 
5641
  function-bind@1.1.2: {}
5642
 
 
 
 
 
5643
  gensync@1.0.0-beta.2: {}
5644
 
5645
  get-intrinsic@1.3.0:
@@ -5824,6 +7651,10 @@ snapshots:
5824
  dependencies:
5825
  safer-buffer: 2.1.2
5826
 
 
 
 
 
5827
  inherits@2.0.4: {}
5828
 
5829
  inline-style-parser@0.2.4: {}
@@ -5852,8 +7683,14 @@ snapshots:
5852
 
5853
  is-plain-obj@4.1.0: {}
5854
 
 
 
 
 
5855
  jiti@2.6.1: {}
5856
 
 
 
5857
  js-tokens@4.0.0: {}
5858
 
5859
  jsesc@3.1.0: {}
@@ -5935,6 +7772,8 @@ snapshots:
5935
 
5936
  lodash@4.17.21: {}
5937
 
 
 
5938
  longest-streak@3.1.0: {}
5939
 
5940
  loose-envify@1.4.0:
@@ -5947,6 +7786,8 @@ snapshots:
5947
  dependencies:
5948
  yallist: 3.1.1
5949
 
 
 
5950
  lucide-react@0.453.0(react@19.2.1):
5951
  dependencies:
5952
  react: 19.2.1
@@ -6397,6 +8238,22 @@ snapshots:
6397
 
6398
  ms@2.1.3: {}
6399
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6400
  nanoid@3.3.11: {}
6401
 
6402
  nanoid@5.1.6: {}
@@ -6776,6 +8633,8 @@ snapshots:
6776
  transitivePeerDependencies:
6777
  - supports-color
6778
 
 
 
6779
  serve-static@1.16.2:
6780
  dependencies:
6781
  encodeurl: 2.0.0
@@ -6835,8 +8694,17 @@ snapshots:
6835
 
6836
  source-map-js@1.2.1: {}
6837
 
 
 
 
 
 
 
 
6838
  space-separated-tokens@2.0.2: {}
6839
 
 
 
6840
  stackback@0.0.2: {}
6841
 
6842
  statuses@2.0.1: {}
@@ -6868,6 +8736,12 @@ snapshots:
6868
  character-entities-html4: 2.1.0
6869
  character-entities-legacy: 3.0.0
6870
 
 
 
 
 
 
 
6871
  style-to-js@1.1.18:
6872
  dependencies:
6873
  style-to-object: 1.0.11
@@ -6878,6 +8752,10 @@ snapshots:
6878
 
6879
  stylis@4.3.6: {}
6880
 
 
 
 
 
6881
  tailwind-merge@3.3.1: {}
6882
 
6883
  tailwindcss-animate@1.0.7(tailwindcss@4.1.14):
@@ -6939,7 +8817,7 @@ snapshots:
6939
  media-typer: 0.3.0
6940
  mime-types: 2.1.35
6941
 
6942
- typescript@5.6.3: {}
6943
 
6944
  ufo@1.6.1: {}
6945
 
 
16
 
17
  .:
18
  dependencies:
19
+ '@aws-sdk/client-s3':
20
+ specifier: ^3.693.0
21
+ version: 3.984.0
22
+ '@aws-sdk/s3-request-presigner':
23
+ specifier: ^3.693.0
24
+ version: 3.984.0
25
  '@hookform/resolvers':
26
  specifier: ^5.2.2
27
  version: 5.2.2(react-hook-form@7.64.0(react@19.2.1))
 
103
  '@radix-ui/react-tooltip':
104
  specifier: ^1.2.8
105
  version: 1.2.8(@types/react-dom@19.2.1(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
106
+ '@tanstack/react-query':
107
+ specifier: ^5.90.2
108
+ version: 5.90.20(react@19.2.1)
109
+ '@trpc/client':
110
+ specifier: ^11.6.0
111
+ version: 11.9.0(@trpc/server@11.9.0(typescript@5.9.3))(typescript@5.9.3)
112
+ '@trpc/react-query':
113
+ specifier: ^11.6.0
114
+ version: 11.9.0(@tanstack/react-query@5.90.20(react@19.2.1))(@trpc/client@11.9.0(@trpc/server@11.9.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.9.0(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)
115
+ '@trpc/server':
116
+ specifier: ^11.6.0
117
+ version: 11.9.0(typescript@5.9.3)
118
  axios:
119
  specifier: ^1.12.0
120
  version: 1.12.2
 
127
  cmdk:
128
  specifier: ^1.1.1
129
  version: 1.1.1(@types/react-dom@19.2.1(@types/react@19.2.1))(@types/react@19.2.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
130
+ cookie:
131
+ specifier: ^1.0.2
132
+ version: 1.1.1
133
+ date-fns:
134
+ specifier: ^4.1.0
135
+ version: 4.1.0
136
+ dotenv:
137
+ specifier: ^17.2.2
138
+ version: 17.2.4
139
+ drizzle-orm:
140
+ specifier: ^0.44.5
141
+ version: 0.44.7(mysql2@3.16.3)
142
  embla-carousel-react:
143
  specifier: ^8.6.0
144
  version: 8.6.0(react@19.2.1)
 
151
  input-otp:
152
  specifier: ^1.4.2
153
  version: 1.4.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
154
+ jose:
155
+ specifier: 6.1.0
156
+ version: 6.1.0
157
  lucide-react:
158
  specifier: ^0.453.0
159
  version: 0.453.0(react@19.2.1)
160
+ mysql2:
161
+ specifier: ^3.15.0
162
+ version: 3.16.3
163
  nanoid:
164
  specifier: ^5.1.5
165
  version: 5.1.6
 
190
  streamdown:
191
  specifier: ^1.4.0
192
  version: 1.4.0(@types/react@19.2.1)(react@19.2.1)
193
+ stripe:
194
+ specifier: ^20.3.1
195
+ version: 20.3.1(@types/node@24.7.0)
196
+ superjson:
197
+ specifier: ^1.13.3
198
+ version: 1.13.3
199
  tailwind-merge:
200
  specifier: ^3.3.1
201
  version: 3.3.1
 
245
  autoprefixer:
246
  specifier: ^10.4.20
247
  version: 10.4.21(postcss@8.5.6)
248
+ drizzle-kit:
249
+ specifier: ^0.31.4
250
+ version: 0.31.8
251
  esbuild:
252
  specifier: ^0.25.0
253
  version: 0.25.10
 
270
  specifier: ^1.4.0
271
  version: 1.4.0
272
  typescript:
273
+ specifier: 5.9.3
274
+ version: 5.9.3
275
  vite:
276
  specifier: ^7.1.7
277
  version: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)
 
290
  '@antfu/utils@9.3.0':
291
  resolution: {integrity: sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==}
292
 
293
+ '@aws-crypto/crc32@5.2.0':
294
+ resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
295
+ engines: {node: '>=16.0.0'}
296
+
297
+ '@aws-crypto/crc32c@5.2.0':
298
+ resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==}
299
+
300
+ '@aws-crypto/sha1-browser@5.2.0':
301
+ resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==}
302
+
303
+ '@aws-crypto/sha256-browser@5.2.0':
304
+ resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
305
+
306
+ '@aws-crypto/sha256-js@5.2.0':
307
+ resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
308
+ engines: {node: '>=16.0.0'}
309
+
310
+ '@aws-crypto/supports-web-crypto@5.2.0':
311
+ resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
312
+
313
+ '@aws-crypto/util@5.2.0':
314
+ resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
315
+
316
+ '@aws-sdk/client-s3@3.984.0':
317
+ resolution: {integrity: sha512-7ny2Slr93Y+QniuluvcfWwyDi32zWQfznynL56Tk0vVh7bWrvS/odm8WP2nInKicRVNipcJHY2YInur6Q/9V0A==}
318
+ engines: {node: '>=20.0.0'}
319
+
320
+ '@aws-sdk/client-sso@3.982.0':
321
+ resolution: {integrity: sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==}
322
+ engines: {node: '>=20.0.0'}
323
+
324
+ '@aws-sdk/core@3.973.6':
325
+ resolution: {integrity: sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==}
326
+ engines: {node: '>=20.0.0'}
327
+
328
+ '@aws-sdk/crc64-nvme@3.972.0':
329
+ resolution: {integrity: sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==}
330
+ engines: {node: '>=20.0.0'}
331
+
332
+ '@aws-sdk/credential-provider-env@3.972.4':
333
+ resolution: {integrity: sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==}
334
+ engines: {node: '>=20.0.0'}
335
+
336
+ '@aws-sdk/credential-provider-http@3.972.6':
337
+ resolution: {integrity: sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==}
338
+ engines: {node: '>=20.0.0'}
339
+
340
+ '@aws-sdk/credential-provider-ini@3.972.4':
341
+ resolution: {integrity: sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==}
342
+ engines: {node: '>=20.0.0'}
343
+
344
+ '@aws-sdk/credential-provider-login@3.972.4':
345
+ resolution: {integrity: sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==}
346
+ engines: {node: '>=20.0.0'}
347
+
348
+ '@aws-sdk/credential-provider-node@3.972.5':
349
+ resolution: {integrity: sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==}
350
+ engines: {node: '>=20.0.0'}
351
+
352
+ '@aws-sdk/credential-provider-process@3.972.4':
353
+ resolution: {integrity: sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==}
354
+ engines: {node: '>=20.0.0'}
355
+
356
+ '@aws-sdk/credential-provider-sso@3.972.4':
357
+ resolution: {integrity: sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==}
358
+ engines: {node: '>=20.0.0'}
359
+
360
+ '@aws-sdk/credential-provider-web-identity@3.972.4':
361
+ resolution: {integrity: sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==}
362
+ engines: {node: '>=20.0.0'}
363
+
364
+ '@aws-sdk/middleware-bucket-endpoint@3.972.3':
365
+ resolution: {integrity: sha512-fmbgWYirF67YF1GfD7cg5N6HHQ96EyRNx/rDIrTF277/zTWVuPI2qS/ZHgofwR1NZPe/NWvoppflQY01LrbVLg==}
366
+ engines: {node: '>=20.0.0'}
367
+
368
+ '@aws-sdk/middleware-expect-continue@3.972.3':
369
+ resolution: {integrity: sha512-4msC33RZsXQpUKR5QR4HnvBSNCPLGHmB55oDiROqqgyOc+TOfVu2xgi5goA7ms6MdZLeEh2905UfWMnMMF4mRg==}
370
+ engines: {node: '>=20.0.0'}
371
+
372
+ '@aws-sdk/middleware-flexible-checksums@3.972.4':
373
+ resolution: {integrity: sha512-xOxsUkF3O3BtIe3tf54OpPo94eZepjFm3z0Dd2TZKbsPxMiRTFXurC04wJ58o/wPW9YHVO9VqZik3MfoPfrKlw==}
374
+ engines: {node: '>=20.0.0'}
375
+
376
+ '@aws-sdk/middleware-host-header@3.972.3':
377
+ resolution: {integrity: sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==}
378
+ engines: {node: '>=20.0.0'}
379
+
380
+ '@aws-sdk/middleware-location-constraint@3.972.3':
381
+ resolution: {integrity: sha512-nIg64CVrsXp67vbK0U1/Is8rik3huS3QkRHn2DRDx4NldrEFMgdkZGI/+cZMKD9k4YOS110Dfu21KZLHrFA/1g==}
382
+ engines: {node: '>=20.0.0'}
383
+
384
+ '@aws-sdk/middleware-logger@3.972.3':
385
+ resolution: {integrity: sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==}
386
+ engines: {node: '>=20.0.0'}
387
+
388
+ '@aws-sdk/middleware-recursion-detection@3.972.3':
389
+ resolution: {integrity: sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==}
390
+ engines: {node: '>=20.0.0'}
391
+
392
+ '@aws-sdk/middleware-sdk-s3@3.972.6':
393
+ resolution: {integrity: sha512-Xq7wM6kbgJN1UO++8dvH/efPb1nTwWqFCpZCR7RCLOETP7xAUAhVo7JmsCnML5Di/iC4Oo5VrJ4QmkYcMZniLw==}
394
+ engines: {node: '>=20.0.0'}
395
+
396
+ '@aws-sdk/middleware-ssec@3.972.3':
397
+ resolution: {integrity: sha512-dU6kDuULN3o3jEHcjm0c4zWJlY1zWVkjG9NPe9qxYLLpcbdj5kRYBS2DdWYD+1B9f910DezRuws7xDEqKkHQIg==}
398
+ engines: {node: '>=20.0.0'}
399
+
400
+ '@aws-sdk/middleware-user-agent@3.972.6':
401
+ resolution: {integrity: sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==}
402
+ engines: {node: '>=20.0.0'}
403
+
404
+ '@aws-sdk/nested-clients@3.982.0':
405
+ resolution: {integrity: sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==}
406
+ engines: {node: '>=20.0.0'}
407
+
408
+ '@aws-sdk/region-config-resolver@3.972.3':
409
+ resolution: {integrity: sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==}
410
+ engines: {node: '>=20.0.0'}
411
+
412
+ '@aws-sdk/s3-request-presigner@3.984.0':
413
+ resolution: {integrity: sha512-sIpkoIo0GhaTdkLt3DeE8HiuKwvMqxEVqruOC0ONSFarKM3/jJuNalZH+PEqHjbAoa2yvW5DgHK7p3S8MIzUZQ==}
414
+ engines: {node: '>=20.0.0'}
415
+
416
+ '@aws-sdk/signature-v4-multi-region@3.984.0':
417
+ resolution: {integrity: sha512-TaWbfYCwnuOSvDSrgs7QgoaoXse49E7LzUkVOUhoezwB7bkmhp+iojADm7UepCEu4021SquD7NG1xA+WCvmldA==}
418
+ engines: {node: '>=20.0.0'}
419
+
420
+ '@aws-sdk/token-providers@3.982.0':
421
+ resolution: {integrity: sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==}
422
+ engines: {node: '>=20.0.0'}
423
+
424
+ '@aws-sdk/types@3.973.1':
425
+ resolution: {integrity: sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==}
426
+ engines: {node: '>=20.0.0'}
427
+
428
+ '@aws-sdk/util-arn-parser@3.972.2':
429
+ resolution: {integrity: sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==}
430
+ engines: {node: '>=20.0.0'}
431
+
432
+ '@aws-sdk/util-endpoints@3.982.0':
433
+ resolution: {integrity: sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==}
434
+ engines: {node: '>=20.0.0'}
435
+
436
+ '@aws-sdk/util-endpoints@3.984.0':
437
+ resolution: {integrity: sha512-9ebjLA0hMKHeVvXEtTDCCOBtwjb0bOXiuUV06HNeVdgAjH6gj4x4Zwt4IBti83TiyTGOCl5YfZqGx4ehVsasbQ==}
438
+ engines: {node: '>=20.0.0'}
439
+
440
+ '@aws-sdk/util-format-url@3.972.3':
441
+ resolution: {integrity: sha512-n7F2ycckcKFXa01vAsT/SJdjFHfKH9s96QHcs5gn8AaaigASICeME8WdUL9uBp8XV/OVwEt8+6gzn6KFUgQa8g==}
442
+ engines: {node: '>=20.0.0'}
443
+
444
+ '@aws-sdk/util-locate-window@3.965.4':
445
+ resolution: {integrity: sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==}
446
+ engines: {node: '>=20.0.0'}
447
+
448
+ '@aws-sdk/util-user-agent-browser@3.972.3':
449
+ resolution: {integrity: sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==}
450
+
451
+ '@aws-sdk/util-user-agent-node@3.972.4':
452
+ resolution: {integrity: sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==}
453
+ engines: {node: '>=20.0.0'}
454
+ peerDependencies:
455
+ aws-crt: '>=1.0.0'
456
+ peerDependenciesMeta:
457
+ aws-crt:
458
+ optional: true
459
+
460
+ '@aws-sdk/xml-builder@3.972.4':
461
+ resolution: {integrity: sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==}
462
+ engines: {node: '>=20.0.0'}
463
+
464
+ '@aws/lambda-invoke-store@0.2.3':
465
+ resolution: {integrity: sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==}
466
+ engines: {node: '>=18.0.0'}
467
+
468
  '@babel/code-frame@7.27.1':
469
  resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
470
  engines: {node: '>=6.9.0'}
 
581
  '@date-fns/tz@1.4.1':
582
  resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
583
 
584
+ '@drizzle-team/brocli@0.10.2':
585
+ resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
586
+
587
+ '@esbuild-kit/core-utils@3.3.2':
588
+ resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
589
+ deprecated: 'Merged into tsx: https://tsx.is'
590
+
591
+ '@esbuild-kit/esm-loader@2.6.5':
592
+ resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==}
593
+ deprecated: 'Merged into tsx: https://tsx.is'
594
+
595
  '@esbuild/aix-ppc64@0.21.5':
596
  resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
597
  engines: {node: '>=12'}
 
604
  cpu: [ppc64]
605
  os: [aix]
606
 
607
+ '@esbuild/android-arm64@0.18.20':
608
+ resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
609
+ engines: {node: '>=12'}
610
+ cpu: [arm64]
611
+ os: [android]
612
+
613
  '@esbuild/android-arm64@0.21.5':
614
  resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
615
  engines: {node: '>=12'}
 
622
  cpu: [arm64]
623
  os: [android]
624
 
625
+ '@esbuild/android-arm@0.18.20':
626
+ resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
627
+ engines: {node: '>=12'}
628
+ cpu: [arm]
629
+ os: [android]
630
+
631
  '@esbuild/android-arm@0.21.5':
632
  resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
633
  engines: {node: '>=12'}
 
640
  cpu: [arm]
641
  os: [android]
642
 
643
+ '@esbuild/android-x64@0.18.20':
644
+ resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
645
+ engines: {node: '>=12'}
646
+ cpu: [x64]
647
+ os: [android]
648
+
649
  '@esbuild/android-x64@0.21.5':
650
  resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
651
  engines: {node: '>=12'}
 
658
  cpu: [x64]
659
  os: [android]
660
 
661
+ '@esbuild/darwin-arm64@0.18.20':
662
+ resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
663
+ engines: {node: '>=12'}
664
+ cpu: [arm64]
665
+ os: [darwin]
666
+
667
  '@esbuild/darwin-arm64@0.21.5':
668
  resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
669
  engines: {node: '>=12'}
 
676
  cpu: [arm64]
677
  os: [darwin]
678
 
679
+ '@esbuild/darwin-x64@0.18.20':
680
+ resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
681
+ engines: {node: '>=12'}
682
+ cpu: [x64]
683
+ os: [darwin]
684
+
685
  '@esbuild/darwin-x64@0.21.5':
686
  resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
687
  engines: {node: '>=12'}
 
694
  cpu: [x64]
695
  os: [darwin]
696
 
697
+ '@esbuild/freebsd-arm64@0.18.20':
698
+ resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
699
+ engines: {node: '>=12'}
700
+ cpu: [arm64]
701
+ os: [freebsd]
702
+
703
  '@esbuild/freebsd-arm64@0.21.5':
704
  resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
705
  engines: {node: '>=12'}
 
712
  cpu: [arm64]
713
  os: [freebsd]
714
 
715
+ '@esbuild/freebsd-x64@0.18.20':
716
+ resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
717
+ engines: {node: '>=12'}
718
+ cpu: [x64]
719
+ os: [freebsd]
720
+
721
  '@esbuild/freebsd-x64@0.21.5':
722
  resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
723
  engines: {node: '>=12'}
 
730
  cpu: [x64]
731
  os: [freebsd]
732
 
733
+ '@esbuild/linux-arm64@0.18.20':
734
+ resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
735
+ engines: {node: '>=12'}
736
+ cpu: [arm64]
737
+ os: [linux]
738
+
739
  '@esbuild/linux-arm64@0.21.5':
740
  resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
741
  engines: {node: '>=12'}
 
748
  cpu: [arm64]
749
  os: [linux]
750
 
751
+ '@esbuild/linux-arm@0.18.20':
752
+ resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
753
+ engines: {node: '>=12'}
754
+ cpu: [arm]
755
+ os: [linux]
756
+
757
  '@esbuild/linux-arm@0.21.5':
758
  resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
759
  engines: {node: '>=12'}
 
766
  cpu: [arm]
767
  os: [linux]
768
 
769
+ '@esbuild/linux-ia32@0.18.20':
770
+ resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
771
+ engines: {node: '>=12'}
772
+ cpu: [ia32]
773
+ os: [linux]
774
+
775
  '@esbuild/linux-ia32@0.21.5':
776
  resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
777
  engines: {node: '>=12'}
 
784
  cpu: [ia32]
785
  os: [linux]
786
 
787
+ '@esbuild/linux-loong64@0.18.20':
788
+ resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
789
+ engines: {node: '>=12'}
790
+ cpu: [loong64]
791
+ os: [linux]
792
+
793
  '@esbuild/linux-loong64@0.21.5':
794
  resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
795
  engines: {node: '>=12'}
 
802
  cpu: [loong64]
803
  os: [linux]
804
 
805
+ '@esbuild/linux-mips64el@0.18.20':
806
+ resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
807
+ engines: {node: '>=12'}
808
+ cpu: [mips64el]
809
+ os: [linux]
810
+
811
  '@esbuild/linux-mips64el@0.21.5':
812
  resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
813
  engines: {node: '>=12'}
 
820
  cpu: [mips64el]
821
  os: [linux]
822
 
823
+ '@esbuild/linux-ppc64@0.18.20':
824
+ resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
825
+ engines: {node: '>=12'}
826
+ cpu: [ppc64]
827
+ os: [linux]
828
+
829
  '@esbuild/linux-ppc64@0.21.5':
830
  resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
831
  engines: {node: '>=12'}
 
838
  cpu: [ppc64]
839
  os: [linux]
840
 
841
+ '@esbuild/linux-riscv64@0.18.20':
842
+ resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
843
+ engines: {node: '>=12'}
844
+ cpu: [riscv64]
845
+ os: [linux]
846
+
847
  '@esbuild/linux-riscv64@0.21.5':
848
  resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
849
  engines: {node: '>=12'}
 
856
  cpu: [riscv64]
857
  os: [linux]
858
 
859
+ '@esbuild/linux-s390x@0.18.20':
860
+ resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
861
+ engines: {node: '>=12'}
862
+ cpu: [s390x]
863
+ os: [linux]
864
+
865
  '@esbuild/linux-s390x@0.21.5':
866
  resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
867
  engines: {node: '>=12'}
 
874
  cpu: [s390x]
875
  os: [linux]
876
 
877
+ '@esbuild/linux-x64@0.18.20':
878
+ resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
879
+ engines: {node: '>=12'}
880
+ cpu: [x64]
881
+ os: [linux]
882
+
883
  '@esbuild/linux-x64@0.21.5':
884
  resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
885
  engines: {node: '>=12'}
 
898
  cpu: [arm64]
899
  os: [netbsd]
900
 
901
+ '@esbuild/netbsd-x64@0.18.20':
902
+ resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
903
+ engines: {node: '>=12'}
904
+ cpu: [x64]
905
+ os: [netbsd]
906
+
907
  '@esbuild/netbsd-x64@0.21.5':
908
  resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
909
  engines: {node: '>=12'}
 
922
  cpu: [arm64]
923
  os: [openbsd]
924
 
925
+ '@esbuild/openbsd-x64@0.18.20':
926
+ resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
927
+ engines: {node: '>=12'}
928
+ cpu: [x64]
929
+ os: [openbsd]
930
+
931
  '@esbuild/openbsd-x64@0.21.5':
932
  resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
933
  engines: {node: '>=12'}
 
946
  cpu: [arm64]
947
  os: [openharmony]
948
 
949
+ '@esbuild/sunos-x64@0.18.20':
950
+ resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
951
+ engines: {node: '>=12'}
952
+ cpu: [x64]
953
+ os: [sunos]
954
+
955
  '@esbuild/sunos-x64@0.21.5':
956
  resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
957
  engines: {node: '>=12'}
 
964
  cpu: [x64]
965
  os: [sunos]
966
 
967
+ '@esbuild/win32-arm64@0.18.20':
968
+ resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
969
+ engines: {node: '>=12'}
970
+ cpu: [arm64]
971
+ os: [win32]
972
+
973
  '@esbuild/win32-arm64@0.21.5':
974
  resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
975
  engines: {node: '>=12'}
 
982
  cpu: [arm64]
983
  os: [win32]
984
 
985
+ '@esbuild/win32-ia32@0.18.20':
986
+ resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
987
+ engines: {node: '>=12'}
988
+ cpu: [ia32]
989
+ os: [win32]
990
+
991
  '@esbuild/win32-ia32@0.21.5':
992
  resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
993
  engines: {node: '>=12'}
 
1000
  cpu: [ia32]
1001
  os: [win32]
1002
 
1003
+ '@esbuild/win32-x64@0.18.20':
1004
+ resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
1005
+ engines: {node: '>=12'}
1006
+ cpu: [x64]
1007
+ os: [win32]
1008
+
1009
  '@esbuild/win32-x64@0.21.5':
1010
  resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
1011
  engines: {node: '>=12'}
 
1816
  '@shikijs/vscode-textmate@10.0.2':
1817
  resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
1818
 
1819
+ '@smithy/abort-controller@4.2.8':
1820
+ resolution: {integrity: sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==}
1821
+ engines: {node: '>=18.0.0'}
1822
+
1823
+ '@smithy/chunked-blob-reader-native@4.2.1':
1824
+ resolution: {integrity: sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==}
1825
+ engines: {node: '>=18.0.0'}
1826
+
1827
+ '@smithy/chunked-blob-reader@5.2.0':
1828
+ resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==}
1829
+ engines: {node: '>=18.0.0'}
1830
+
1831
+ '@smithy/config-resolver@4.4.6':
1832
+ resolution: {integrity: sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==}
1833
+ engines: {node: '>=18.0.0'}
1834
+
1835
+ '@smithy/core@3.22.1':
1836
+ resolution: {integrity: sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==}
1837
+ engines: {node: '>=18.0.0'}
1838
+
1839
+ '@smithy/credential-provider-imds@4.2.8':
1840
+ resolution: {integrity: sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==}
1841
+ engines: {node: '>=18.0.0'}
1842
+
1843
+ '@smithy/eventstream-codec@4.2.8':
1844
+ resolution: {integrity: sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==}
1845
+ engines: {node: '>=18.0.0'}
1846
+
1847
+ '@smithy/eventstream-serde-browser@4.2.8':
1848
+ resolution: {integrity: sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==}
1849
+ engines: {node: '>=18.0.0'}
1850
+
1851
+ '@smithy/eventstream-serde-config-resolver@4.3.8':
1852
+ resolution: {integrity: sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==}
1853
+ engines: {node: '>=18.0.0'}
1854
+
1855
+ '@smithy/eventstream-serde-node@4.2.8':
1856
+ resolution: {integrity: sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==}
1857
+ engines: {node: '>=18.0.0'}
1858
+
1859
+ '@smithy/eventstream-serde-universal@4.2.8':
1860
+ resolution: {integrity: sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==}
1861
+ engines: {node: '>=18.0.0'}
1862
+
1863
+ '@smithy/fetch-http-handler@5.3.9':
1864
+ resolution: {integrity: sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==}
1865
+ engines: {node: '>=18.0.0'}
1866
+
1867
+ '@smithy/hash-blob-browser@4.2.9':
1868
+ resolution: {integrity: sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==}
1869
+ engines: {node: '>=18.0.0'}
1870
+
1871
+ '@smithy/hash-node@4.2.8':
1872
+ resolution: {integrity: sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==}
1873
+ engines: {node: '>=18.0.0'}
1874
+
1875
+ '@smithy/hash-stream-node@4.2.8':
1876
+ resolution: {integrity: sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==}
1877
+ engines: {node: '>=18.0.0'}
1878
+
1879
+ '@smithy/invalid-dependency@4.2.8':
1880
+ resolution: {integrity: sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==}
1881
+ engines: {node: '>=18.0.0'}
1882
+
1883
+ '@smithy/is-array-buffer@2.2.0':
1884
+ resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
1885
+ engines: {node: '>=14.0.0'}
1886
+
1887
+ '@smithy/is-array-buffer@4.2.0':
1888
+ resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==}
1889
+ engines: {node: '>=18.0.0'}
1890
+
1891
+ '@smithy/md5-js@4.2.8':
1892
+ resolution: {integrity: sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==}
1893
+ engines: {node: '>=18.0.0'}
1894
+
1895
+ '@smithy/middleware-content-length@4.2.8':
1896
+ resolution: {integrity: sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==}
1897
+ engines: {node: '>=18.0.0'}
1898
+
1899
+ '@smithy/middleware-endpoint@4.4.13':
1900
+ resolution: {integrity: sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==}
1901
+ engines: {node: '>=18.0.0'}
1902
+
1903
+ '@smithy/middleware-retry@4.4.30':
1904
+ resolution: {integrity: sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==}
1905
+ engines: {node: '>=18.0.0'}
1906
+
1907
+ '@smithy/middleware-serde@4.2.9':
1908
+ resolution: {integrity: sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==}
1909
+ engines: {node: '>=18.0.0'}
1910
+
1911
+ '@smithy/middleware-stack@4.2.8':
1912
+ resolution: {integrity: sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==}
1913
+ engines: {node: '>=18.0.0'}
1914
+
1915
+ '@smithy/node-config-provider@4.3.8':
1916
+ resolution: {integrity: sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==}
1917
+ engines: {node: '>=18.0.0'}
1918
+
1919
+ '@smithy/node-http-handler@4.4.9':
1920
+ resolution: {integrity: sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==}
1921
+ engines: {node: '>=18.0.0'}
1922
+
1923
+ '@smithy/property-provider@4.2.8':
1924
+ resolution: {integrity: sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==}
1925
+ engines: {node: '>=18.0.0'}
1926
+
1927
+ '@smithy/protocol-http@5.3.8':
1928
+ resolution: {integrity: sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==}
1929
+ engines: {node: '>=18.0.0'}
1930
+
1931
+ '@smithy/querystring-builder@4.2.8':
1932
+ resolution: {integrity: sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==}
1933
+ engines: {node: '>=18.0.0'}
1934
+
1935
+ '@smithy/querystring-parser@4.2.8':
1936
+ resolution: {integrity: sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==}
1937
+ engines: {node: '>=18.0.0'}
1938
+
1939
+ '@smithy/service-error-classification@4.2.8':
1940
+ resolution: {integrity: sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==}
1941
+ engines: {node: '>=18.0.0'}
1942
+
1943
+ '@smithy/shared-ini-file-loader@4.4.3':
1944
+ resolution: {integrity: sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==}
1945
+ engines: {node: '>=18.0.0'}
1946
+
1947
+ '@smithy/signature-v4@5.3.8':
1948
+ resolution: {integrity: sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==}
1949
+ engines: {node: '>=18.0.0'}
1950
+
1951
+ '@smithy/smithy-client@4.11.2':
1952
+ resolution: {integrity: sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==}
1953
+ engines: {node: '>=18.0.0'}
1954
+
1955
+ '@smithy/types@4.12.0':
1956
+ resolution: {integrity: sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==}
1957
+ engines: {node: '>=18.0.0'}
1958
+
1959
+ '@smithy/url-parser@4.2.8':
1960
+ resolution: {integrity: sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==}
1961
+ engines: {node: '>=18.0.0'}
1962
+
1963
+ '@smithy/util-base64@4.3.0':
1964
+ resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==}
1965
+ engines: {node: '>=18.0.0'}
1966
+
1967
+ '@smithy/util-body-length-browser@4.2.0':
1968
+ resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==}
1969
+ engines: {node: '>=18.0.0'}
1970
+
1971
+ '@smithy/util-body-length-node@4.2.1':
1972
+ resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==}
1973
+ engines: {node: '>=18.0.0'}
1974
+
1975
+ '@smithy/util-buffer-from@2.2.0':
1976
+ resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
1977
+ engines: {node: '>=14.0.0'}
1978
+
1979
+ '@smithy/util-buffer-from@4.2.0':
1980
+ resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==}
1981
+ engines: {node: '>=18.0.0'}
1982
+
1983
+ '@smithy/util-config-provider@4.2.0':
1984
+ resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==}
1985
+ engines: {node: '>=18.0.0'}
1986
+
1987
+ '@smithy/util-defaults-mode-browser@4.3.29':
1988
+ resolution: {integrity: sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==}
1989
+ engines: {node: '>=18.0.0'}
1990
+
1991
+ '@smithy/util-defaults-mode-node@4.2.32':
1992
+ resolution: {integrity: sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==}
1993
+ engines: {node: '>=18.0.0'}
1994
+
1995
+ '@smithy/util-endpoints@3.2.8':
1996
+ resolution: {integrity: sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==}
1997
+ engines: {node: '>=18.0.0'}
1998
+
1999
+ '@smithy/util-hex-encoding@4.2.0':
2000
+ resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==}
2001
+ engines: {node: '>=18.0.0'}
2002
+
2003
+ '@smithy/util-middleware@4.2.8':
2004
+ resolution: {integrity: sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==}
2005
+ engines: {node: '>=18.0.0'}
2006
+
2007
+ '@smithy/util-retry@4.2.8':
2008
+ resolution: {integrity: sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==}
2009
+ engines: {node: '>=18.0.0'}
2010
+
2011
+ '@smithy/util-stream@4.5.11':
2012
+ resolution: {integrity: sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==}
2013
+ engines: {node: '>=18.0.0'}
2014
+
2015
+ '@smithy/util-uri-escape@4.2.0':
2016
+ resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==}
2017
+ engines: {node: '>=18.0.0'}
2018
+
2019
+ '@smithy/util-utf8@2.3.0':
2020
+ resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
2021
+ engines: {node: '>=14.0.0'}
2022
+
2023
+ '@smithy/util-utf8@4.2.0':
2024
+ resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==}
2025
+ engines: {node: '>=18.0.0'}
2026
+
2027
+ '@smithy/util-waiter@4.2.8':
2028
+ resolution: {integrity: sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==}
2029
+ engines: {node: '>=18.0.0'}
2030
+
2031
+ '@smithy/uuid@1.1.0':
2032
+ resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==}
2033
+ engines: {node: '>=18.0.0'}
2034
+
2035
  '@standard-schema/utils@0.3.0':
2036
  resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
2037
 
 
2130
  peerDependencies:
2131
  vite: ^5.2.0 || ^6 || ^7
2132
 
2133
+ '@tanstack/query-core@5.90.20':
2134
+ resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==}
2135
+
2136
+ '@tanstack/react-query@5.90.20':
2137
+ resolution: {integrity: sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==}
2138
+ peerDependencies:
2139
+ react: ^18 || ^19
2140
+
2141
+ '@trpc/client@11.9.0':
2142
+ resolution: {integrity: sha512-3r4RT/GbR263QO+2gCPyrs5fEYaXua3/AzCs+GbWC09X0F+mVkyBpO3GRSDObiNU/N1YB597U7WGW3WA1d1TVw==}
2143
+ peerDependencies:
2144
+ '@trpc/server': 11.9.0
2145
+ typescript: '>=5.7.2'
2146
+
2147
+ '@trpc/react-query@11.9.0':
2148
+ resolution: {integrity: sha512-9Gpj06ZcfsA77PB5A8VC2MFS/E7pPvoNqaSlSrAgLyRsKqy0gldFOW2RMKura69M6fwtgjg9+4i2+rOHKT7qLw==}
2149
+ peerDependencies:
2150
+ '@tanstack/react-query': ^5.80.3
2151
+ '@trpc/client': 11.9.0
2152
+ '@trpc/server': 11.9.0
2153
+ react: '>=18.2.0'
2154
+ react-dom: '>=18.2.0'
2155
+ typescript: '>=5.7.2'
2156
+
2157
+ '@trpc/server@11.9.0':
2158
+ resolution: {integrity: sha512-T8gC4NOCzx8tCsQEQ5sSjf24bN+9AEqXZRfpThG+YCEmcEwXfS7RP8VVrl5Vodt1S+zGEDyQSof4YVAj1zq/mg==}
2159
+ peerDependencies:
2160
+ typescript: '>=5.7.2'
2161
+
2162
  '@types/babel__core@7.20.5':
2163
  resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
2164
 
 
2415
  peerDependencies:
2416
  postcss: ^8.1.0
2417
 
2418
+ aws-ssl-profiles@1.1.2:
2419
+ resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}
2420
+ engines: {node: '>= 6.0.0'}
2421
+
2422
  axios@1.12.2:
2423
  resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==}
2424
 
 
2433
  resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
2434
  engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
2435
 
2436
+ bowser@2.13.1:
2437
+ resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==}
2438
+
2439
  browserslist@4.26.3:
2440
  resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==}
2441
  engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
2442
  hasBin: true
2443
 
2444
+ buffer-from@1.1.2:
2445
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
2446
+
2447
  bytes@3.1.2:
2448
  resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
2449
  engines: {node: '>= 0.8'}
 
2550
  resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
2551
  engines: {node: '>= 0.6'}
2552
 
2553
+ cookie@1.1.1:
2554
+ resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
2555
+ engines: {node: '>=18'}
2556
+
2557
+ copy-anything@3.0.5:
2558
+ resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
2559
+ engines: {node: '>=12.13'}
2560
+
2561
  cose-base@1.0.3:
2562
  resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
2563
 
 
2771
  resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
2772
  engines: {node: '>=0.4.0'}
2773
 
2774
+ denque@2.1.0:
2775
+ resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
2776
+ engines: {node: '>=0.10'}
2777
+
2778
  depd@2.0.0:
2779
  resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
2780
  engines: {node: '>= 0.8'}
 
2803
  dompurify@3.3.0:
2804
  resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==}
2805
 
2806
+ dotenv@17.2.4:
2807
+ resolution: {integrity: sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==}
2808
+ engines: {node: '>=12'}
2809
+
2810
+ drizzle-kit@0.31.8:
2811
+ resolution: {integrity: sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==}
2812
+ hasBin: true
2813
+
2814
+ drizzle-orm@0.44.7:
2815
+ resolution: {integrity: sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ==}
2816
+ peerDependencies:
2817
+ '@aws-sdk/client-rds-data': '>=3'
2818
+ '@cloudflare/workers-types': '>=4'
2819
+ '@electric-sql/pglite': '>=0.2.0'
2820
+ '@libsql/client': '>=0.10.0'
2821
+ '@libsql/client-wasm': '>=0.10.0'
2822
+ '@neondatabase/serverless': '>=0.10.0'
2823
+ '@op-engineering/op-sqlite': '>=2'
2824
+ '@opentelemetry/api': ^1.4.1
2825
+ '@planetscale/database': '>=1.13'
2826
+ '@prisma/client': '*'
2827
+ '@tidbcloud/serverless': '*'
2828
+ '@types/better-sqlite3': '*'
2829
+ '@types/pg': '*'
2830
+ '@types/sql.js': '*'
2831
+ '@upstash/redis': '>=1.34.7'
2832
+ '@vercel/postgres': '>=0.8.0'
2833
+ '@xata.io/client': '*'
2834
+ better-sqlite3: '>=7'
2835
+ bun-types: '*'
2836
+ expo-sqlite: '>=14.0.0'
2837
+ gel: '>=2'
2838
+ knex: '*'
2839
+ kysely: '*'
2840
+ mysql2: '>=2'
2841
+ pg: '>=8'
2842
+ postgres: '>=3'
2843
+ prisma: '*'
2844
+ sql.js: '>=1'
2845
+ sqlite3: '>=5'
2846
+ peerDependenciesMeta:
2847
+ '@aws-sdk/client-rds-data':
2848
+ optional: true
2849
+ '@cloudflare/workers-types':
2850
+ optional: true
2851
+ '@electric-sql/pglite':
2852
+ optional: true
2853
+ '@libsql/client':
2854
+ optional: true
2855
+ '@libsql/client-wasm':
2856
+ optional: true
2857
+ '@neondatabase/serverless':
2858
+ optional: true
2859
+ '@op-engineering/op-sqlite':
2860
+ optional: true
2861
+ '@opentelemetry/api':
2862
+ optional: true
2863
+ '@planetscale/database':
2864
+ optional: true
2865
+ '@prisma/client':
2866
+ optional: true
2867
+ '@tidbcloud/serverless':
2868
+ optional: true
2869
+ '@types/better-sqlite3':
2870
+ optional: true
2871
+ '@types/pg':
2872
+ optional: true
2873
+ '@types/sql.js':
2874
+ optional: true
2875
+ '@upstash/redis':
2876
+ optional: true
2877
+ '@vercel/postgres':
2878
+ optional: true
2879
+ '@xata.io/client':
2880
+ optional: true
2881
+ better-sqlite3:
2882
+ optional: true
2883
+ bun-types:
2884
+ optional: true
2885
+ expo-sqlite:
2886
+ optional: true
2887
+ gel:
2888
+ optional: true
2889
+ knex:
2890
+ optional: true
2891
+ kysely:
2892
+ optional: true
2893
+ mysql2:
2894
+ optional: true
2895
+ pg:
2896
+ optional: true
2897
+ postgres:
2898
+ optional: true
2899
+ prisma:
2900
+ optional: true
2901
+ sql.js:
2902
+ optional: true
2903
+ sqlite3:
2904
+ optional: true
2905
+
2906
  dunder-proto@1.0.1:
2907
  resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
2908
  engines: {node: '>= 0.4'}
 
2961
  resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
2962
  engines: {node: '>= 0.4'}
2963
 
2964
+ esbuild-register@3.6.0:
2965
+ resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==}
2966
+ peerDependencies:
2967
+ esbuild: '>=0.12 <1'
2968
+
2969
+ esbuild@0.18.20:
2970
+ resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
2971
+ engines: {node: '>=12'}
2972
+ hasBin: true
2973
+
2974
  esbuild@0.21.5:
2975
  resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
2976
  engines: {node: '>=12'}
 
3026
  resolution: {integrity: sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==}
3027
  engines: {node: '>=6.0.0'}
3028
 
3029
+ fast-xml-parser@5.3.4:
3030
+ resolution: {integrity: sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==}
3031
+ hasBin: true
3032
+
3033
  fdir@6.5.0:
3034
  resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
3035
  engines: {node: '>=12.0.0'}
 
3089
  function-bind@1.1.2:
3090
  resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
3091
 
3092
+ generate-function@2.3.1:
3093
+ resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
3094
+
3095
  gensync@1.0.0-beta.2:
3096
  resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
3097
  engines: {node: '>=6.9.0'}
 
3194
  resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
3195
  engines: {node: '>=0.10.0'}
3196
 
3197
+ iconv-lite@0.7.2:
3198
+ resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
3199
+ engines: {node: '>=0.10.0'}
3200
+
3201
  inherits@2.0.4:
3202
  resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
3203
 
 
3237
  resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
3238
  engines: {node: '>=12'}
3239
 
3240
+ is-property@1.0.2:
3241
+ resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
3242
+
3243
+ is-what@4.1.16:
3244
+ resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
3245
+ engines: {node: '>=12.13'}
3246
+
3247
  jiti@2.6.1:
3248
  resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
3249
  hasBin: true
3250
 
3251
+ jose@6.1.0:
3252
+ resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==}
3253
+
3254
  js-tokens@4.0.0:
3255
  resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
3256
 
 
3358
  lodash@4.17.21:
3359
  resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
3360
 
3361
+ long@5.3.2:
3362
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
3363
+
3364
  longest-streak@3.1.0:
3365
  resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
3366
 
 
3374
  lru-cache@5.1.1:
3375
  resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
3376
 
3377
+ lru.min@1.1.4:
3378
+ resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==}
3379
+ engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
3380
+
3381
  lucide-react@0.453.0:
3382
  resolution: {integrity: sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==}
3383
  peerDependencies:
 
3594
  ms@2.1.3:
3595
  resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
3596
 
3597
+ mysql2@3.16.3:
3598
+ resolution: {integrity: sha512-+3XhQEt4FEFuvGV0JjIDj4eP2OT/oIj/54dYvqhblnSzlfcxVOuj+cd15Xz6hsG4HU1a+A5+BA9gm0618C4z7A==}
3599
+ engines: {node: '>= 8.0'}
3600
+
3601
+ named-placeholders@1.1.6:
3602
+ resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==}
3603
+ engines: {node: '>=8.0.0'}
3604
+
3605
  nanoid@3.3.11:
3606
  resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
3607
  engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
 
3913
  resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
3914
  engines: {node: '>= 0.8.0'}
3915
 
3916
+ seq-queue@0.0.5:
3917
+ resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}
3918
+
3919
  serve-static@1.16.2:
3920
  resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
3921
  engines: {node: '>= 0.8.0'}
 
3955
  resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
3956
  engines: {node: '>=0.10.0'}
3957
 
3958
+ source-map-support@0.5.21:
3959
+ resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
3960
+
3961
+ source-map@0.6.1:
3962
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
3963
+ engines: {node: '>=0.10.0'}
3964
+
3965
  space-separated-tokens@2.0.2:
3966
  resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
3967
 
3968
+ sqlstring@2.3.3:
3969
+ resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
3970
+ engines: {node: '>= 0.6'}
3971
+
3972
  stackback@0.0.2:
3973
  resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
3974
 
 
3987
  stringify-entities@4.0.4:
3988
  resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
3989
 
3990
+ stripe@20.3.1:
3991
+ resolution: {integrity: sha512-k990yOT5G5rhX3XluRPw5Y8RLdJDW4dzQ29wWT66piHrbnM2KyamJ1dKgPsw4HzGHRWjDiSSdcI2WdxQUPV3aQ==}
3992
+ engines: {node: '>=16'}
3993
+ peerDependencies:
3994
+ '@types/node': '>=16'
3995
+ peerDependenciesMeta:
3996
+ '@types/node':
3997
+ optional: true
3998
+
3999
+ strnum@2.1.2:
4000
+ resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==}
4001
+
4002
  style-to-js@1.1.18:
4003
  resolution: {integrity: sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==}
4004
 
 
4008
  stylis@4.3.6:
4009
  resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
4010
 
4011
+ superjson@1.13.3:
4012
+ resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==}
4013
+ engines: {node: '>=10'}
4014
+
4015
  tailwind-merge@3.3.1:
4016
  resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
4017
 
 
4088
  resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
4089
  engines: {node: '>= 0.6'}
4090
 
4091
+ typescript@5.9.3:
4092
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
4093
  engines: {node: '>=14.17'}
4094
  hasBin: true
4095
 
 
4350
 
4351
  '@antfu/utils@9.3.0': {}
4352
 
4353
+ '@aws-crypto/crc32@5.2.0':
4354
+ dependencies:
4355
+ '@aws-crypto/util': 5.2.0
4356
+ '@aws-sdk/types': 3.973.1
4357
+ tslib: 2.8.1
4358
+
4359
+ '@aws-crypto/crc32c@5.2.0':
4360
+ dependencies:
4361
+ '@aws-crypto/util': 5.2.0
4362
+ '@aws-sdk/types': 3.973.1
4363
+ tslib: 2.8.1
4364
+
4365
+ '@aws-crypto/sha1-browser@5.2.0':
4366
+ dependencies:
4367
+ '@aws-crypto/supports-web-crypto': 5.2.0
4368
+ '@aws-crypto/util': 5.2.0
4369
+ '@aws-sdk/types': 3.973.1
4370
+ '@aws-sdk/util-locate-window': 3.965.4
4371
+ '@smithy/util-utf8': 2.3.0
4372
+ tslib: 2.8.1
4373
+
4374
+ '@aws-crypto/sha256-browser@5.2.0':
4375
+ dependencies:
4376
+ '@aws-crypto/sha256-js': 5.2.0
4377
+ '@aws-crypto/supports-web-crypto': 5.2.0
4378
+ '@aws-crypto/util': 5.2.0
4379
+ '@aws-sdk/types': 3.973.1
4380
+ '@aws-sdk/util-locate-window': 3.965.4
4381
+ '@smithy/util-utf8': 2.3.0
4382
+ tslib: 2.8.1
4383
+
4384
+ '@aws-crypto/sha256-js@5.2.0':
4385
+ dependencies:
4386
+ '@aws-crypto/util': 5.2.0
4387
+ '@aws-sdk/types': 3.973.1
4388
+ tslib: 2.8.1
4389
+
4390
+ '@aws-crypto/supports-web-crypto@5.2.0':
4391
+ dependencies:
4392
+ tslib: 2.8.1
4393
+
4394
+ '@aws-crypto/util@5.2.0':
4395
+ dependencies:
4396
+ '@aws-sdk/types': 3.973.1
4397
+ '@smithy/util-utf8': 2.3.0
4398
+ tslib: 2.8.1
4399
+
4400
+ '@aws-sdk/client-s3@3.984.0':
4401
+ dependencies:
4402
+ '@aws-crypto/sha1-browser': 5.2.0
4403
+ '@aws-crypto/sha256-browser': 5.2.0
4404
+ '@aws-crypto/sha256-js': 5.2.0
4405
+ '@aws-sdk/core': 3.973.6
4406
+ '@aws-sdk/credential-provider-node': 3.972.5
4407
+ '@aws-sdk/middleware-bucket-endpoint': 3.972.3
4408
+ '@aws-sdk/middleware-expect-continue': 3.972.3
4409
+ '@aws-sdk/middleware-flexible-checksums': 3.972.4
4410
+ '@aws-sdk/middleware-host-header': 3.972.3
4411
+ '@aws-sdk/middleware-location-constraint': 3.972.3
4412
+ '@aws-sdk/middleware-logger': 3.972.3
4413
+ '@aws-sdk/middleware-recursion-detection': 3.972.3
4414
+ '@aws-sdk/middleware-sdk-s3': 3.972.6
4415
+ '@aws-sdk/middleware-ssec': 3.972.3
4416
+ '@aws-sdk/middleware-user-agent': 3.972.6
4417
+ '@aws-sdk/region-config-resolver': 3.972.3
4418
+ '@aws-sdk/signature-v4-multi-region': 3.984.0
4419
+ '@aws-sdk/types': 3.973.1
4420
+ '@aws-sdk/util-endpoints': 3.984.0
4421
+ '@aws-sdk/util-user-agent-browser': 3.972.3
4422
+ '@aws-sdk/util-user-agent-node': 3.972.4
4423
+ '@smithy/config-resolver': 4.4.6
4424
+ '@smithy/core': 3.22.1
4425
+ '@smithy/eventstream-serde-browser': 4.2.8
4426
+ '@smithy/eventstream-serde-config-resolver': 4.3.8
4427
+ '@smithy/eventstream-serde-node': 4.2.8
4428
+ '@smithy/fetch-http-handler': 5.3.9
4429
+ '@smithy/hash-blob-browser': 4.2.9
4430
+ '@smithy/hash-node': 4.2.8
4431
+ '@smithy/hash-stream-node': 4.2.8
4432
+ '@smithy/invalid-dependency': 4.2.8
4433
+ '@smithy/md5-js': 4.2.8
4434
+ '@smithy/middleware-content-length': 4.2.8
4435
+ '@smithy/middleware-endpoint': 4.4.13
4436
+ '@smithy/middleware-retry': 4.4.30
4437
+ '@smithy/middleware-serde': 4.2.9
4438
+ '@smithy/middleware-stack': 4.2.8
4439
+ '@smithy/node-config-provider': 4.3.8
4440
+ '@smithy/node-http-handler': 4.4.9
4441
+ '@smithy/protocol-http': 5.3.8
4442
+ '@smithy/smithy-client': 4.11.2
4443
+ '@smithy/types': 4.12.0
4444
+ '@smithy/url-parser': 4.2.8
4445
+ '@smithy/util-base64': 4.3.0
4446
+ '@smithy/util-body-length-browser': 4.2.0
4447
+ '@smithy/util-body-length-node': 4.2.1
4448
+ '@smithy/util-defaults-mode-browser': 4.3.29
4449
+ '@smithy/util-defaults-mode-node': 4.2.32
4450
+ '@smithy/util-endpoints': 3.2.8
4451
+ '@smithy/util-middleware': 4.2.8
4452
+ '@smithy/util-retry': 4.2.8
4453
+ '@smithy/util-stream': 4.5.11
4454
+ '@smithy/util-utf8': 4.2.0
4455
+ '@smithy/util-waiter': 4.2.8
4456
+ tslib: 2.8.1
4457
+ transitivePeerDependencies:
4458
+ - aws-crt
4459
+
4460
+ '@aws-sdk/client-sso@3.982.0':
4461
+ dependencies:
4462
+ '@aws-crypto/sha256-browser': 5.2.0
4463
+ '@aws-crypto/sha256-js': 5.2.0
4464
+ '@aws-sdk/core': 3.973.6
4465
+ '@aws-sdk/middleware-host-header': 3.972.3
4466
+ '@aws-sdk/middleware-logger': 3.972.3
4467
+ '@aws-sdk/middleware-recursion-detection': 3.972.3
4468
+ '@aws-sdk/middleware-user-agent': 3.972.6
4469
+ '@aws-sdk/region-config-resolver': 3.972.3
4470
+ '@aws-sdk/types': 3.973.1
4471
+ '@aws-sdk/util-endpoints': 3.982.0
4472
+ '@aws-sdk/util-user-agent-browser': 3.972.3
4473
+ '@aws-sdk/util-user-agent-node': 3.972.4
4474
+ '@smithy/config-resolver': 4.4.6
4475
+ '@smithy/core': 3.22.1
4476
+ '@smithy/fetch-http-handler': 5.3.9
4477
+ '@smithy/hash-node': 4.2.8
4478
+ '@smithy/invalid-dependency': 4.2.8
4479
+ '@smithy/middleware-content-length': 4.2.8
4480
+ '@smithy/middleware-endpoint': 4.4.13
4481
+ '@smithy/middleware-retry': 4.4.30
4482
+ '@smithy/middleware-serde': 4.2.9
4483
+ '@smithy/middleware-stack': 4.2.8
4484
+ '@smithy/node-config-provider': 4.3.8
4485
+ '@smithy/node-http-handler': 4.4.9
4486
+ '@smithy/protocol-http': 5.3.8
4487
+ '@smithy/smithy-client': 4.11.2
4488
+ '@smithy/types': 4.12.0
4489
+ '@smithy/url-parser': 4.2.8
4490
+ '@smithy/util-base64': 4.3.0
4491
+ '@smithy/util-body-length-browser': 4.2.0
4492
+ '@smithy/util-body-length-node': 4.2.1
4493
+ '@smithy/util-defaults-mode-browser': 4.3.29
4494
+ '@smithy/util-defaults-mode-node': 4.2.32
4495
+ '@smithy/util-endpoints': 3.2.8
4496
+ '@smithy/util-middleware': 4.2.8
4497
+ '@smithy/util-retry': 4.2.8
4498
+ '@smithy/util-utf8': 4.2.0
4499
+ tslib: 2.8.1
4500
+ transitivePeerDependencies:
4501
+ - aws-crt
4502
+
4503
+ '@aws-sdk/core@3.973.6':
4504
+ dependencies:
4505
+ '@aws-sdk/types': 3.973.1
4506
+ '@aws-sdk/xml-builder': 3.972.4
4507
+ '@smithy/core': 3.22.1
4508
+ '@smithy/node-config-provider': 4.3.8
4509
+ '@smithy/property-provider': 4.2.8
4510
+ '@smithy/protocol-http': 5.3.8
4511
+ '@smithy/signature-v4': 5.3.8
4512
+ '@smithy/smithy-client': 4.11.2
4513
+ '@smithy/types': 4.12.0
4514
+ '@smithy/util-base64': 4.3.0
4515
+ '@smithy/util-middleware': 4.2.8
4516
+ '@smithy/util-utf8': 4.2.0
4517
+ tslib: 2.8.1
4518
+
4519
+ '@aws-sdk/crc64-nvme@3.972.0':
4520
+ dependencies:
4521
+ '@smithy/types': 4.12.0
4522
+ tslib: 2.8.1
4523
+
4524
+ '@aws-sdk/credential-provider-env@3.972.4':
4525
+ dependencies:
4526
+ '@aws-sdk/core': 3.973.6
4527
+ '@aws-sdk/types': 3.973.1
4528
+ '@smithy/property-provider': 4.2.8
4529
+ '@smithy/types': 4.12.0
4530
+ tslib: 2.8.1
4531
+
4532
+ '@aws-sdk/credential-provider-http@3.972.6':
4533
+ dependencies:
4534
+ '@aws-sdk/core': 3.973.6
4535
+ '@aws-sdk/types': 3.973.1
4536
+ '@smithy/fetch-http-handler': 5.3.9
4537
+ '@smithy/node-http-handler': 4.4.9
4538
+ '@smithy/property-provider': 4.2.8
4539
+ '@smithy/protocol-http': 5.3.8
4540
+ '@smithy/smithy-client': 4.11.2
4541
+ '@smithy/types': 4.12.0
4542
+ '@smithy/util-stream': 4.5.11
4543
+ tslib: 2.8.1
4544
+
4545
+ '@aws-sdk/credential-provider-ini@3.972.4':
4546
+ dependencies:
4547
+ '@aws-sdk/core': 3.973.6
4548
+ '@aws-sdk/credential-provider-env': 3.972.4
4549
+ '@aws-sdk/credential-provider-http': 3.972.6
4550
+ '@aws-sdk/credential-provider-login': 3.972.4
4551
+ '@aws-sdk/credential-provider-process': 3.972.4
4552
+ '@aws-sdk/credential-provider-sso': 3.972.4
4553
+ '@aws-sdk/credential-provider-web-identity': 3.972.4
4554
+ '@aws-sdk/nested-clients': 3.982.0
4555
+ '@aws-sdk/types': 3.973.1
4556
+ '@smithy/credential-provider-imds': 4.2.8
4557
+ '@smithy/property-provider': 4.2.8
4558
+ '@smithy/shared-ini-file-loader': 4.4.3
4559
+ '@smithy/types': 4.12.0
4560
+ tslib: 2.8.1
4561
+ transitivePeerDependencies:
4562
+ - aws-crt
4563
+
4564
+ '@aws-sdk/credential-provider-login@3.972.4':
4565
+ dependencies:
4566
+ '@aws-sdk/core': 3.973.6
4567
+ '@aws-sdk/nested-clients': 3.982.0
4568
+ '@aws-sdk/types': 3.973.1
4569
+ '@smithy/property-provider': 4.2.8
4570
+ '@smithy/protocol-http': 5.3.8
4571
+ '@smithy/shared-ini-file-loader': 4.4.3
4572
+ '@smithy/types': 4.12.0
4573
+ tslib: 2.8.1
4574
+ transitivePeerDependencies:
4575
+ - aws-crt
4576
+
4577
+ '@aws-sdk/credential-provider-node@3.972.5':
4578
+ dependencies:
4579
+ '@aws-sdk/credential-provider-env': 3.972.4
4580
+ '@aws-sdk/credential-provider-http': 3.972.6
4581
+ '@aws-sdk/credential-provider-ini': 3.972.4
4582
+ '@aws-sdk/credential-provider-process': 3.972.4
4583
+ '@aws-sdk/credential-provider-sso': 3.972.4
4584
+ '@aws-sdk/credential-provider-web-identity': 3.972.4
4585
+ '@aws-sdk/types': 3.973.1
4586
+ '@smithy/credential-provider-imds': 4.2.8
4587
+ '@smithy/property-provider': 4.2.8
4588
+ '@smithy/shared-ini-file-loader': 4.4.3
4589
+ '@smithy/types': 4.12.0
4590
+ tslib: 2.8.1
4591
+ transitivePeerDependencies:
4592
+ - aws-crt
4593
+
4594
+ '@aws-sdk/credential-provider-process@3.972.4':
4595
+ dependencies:
4596
+ '@aws-sdk/core': 3.973.6
4597
+ '@aws-sdk/types': 3.973.1
4598
+ '@smithy/property-provider': 4.2.8
4599
+ '@smithy/shared-ini-file-loader': 4.4.3
4600
+ '@smithy/types': 4.12.0
4601
+ tslib: 2.8.1
4602
+
4603
+ '@aws-sdk/credential-provider-sso@3.972.4':
4604
+ dependencies:
4605
+ '@aws-sdk/client-sso': 3.982.0
4606
+ '@aws-sdk/core': 3.973.6
4607
+ '@aws-sdk/token-providers': 3.982.0
4608
+ '@aws-sdk/types': 3.973.1
4609
+ '@smithy/property-provider': 4.2.8
4610
+ '@smithy/shared-ini-file-loader': 4.4.3
4611
+ '@smithy/types': 4.12.0
4612
+ tslib: 2.8.1
4613
+ transitivePeerDependencies:
4614
+ - aws-crt
4615
+
4616
+ '@aws-sdk/credential-provider-web-identity@3.972.4':
4617
+ dependencies:
4618
+ '@aws-sdk/core': 3.973.6
4619
+ '@aws-sdk/nested-clients': 3.982.0
4620
+ '@aws-sdk/types': 3.973.1
4621
+ '@smithy/property-provider': 4.2.8
4622
+ '@smithy/shared-ini-file-loader': 4.4.3
4623
+ '@smithy/types': 4.12.0
4624
+ tslib: 2.8.1
4625
+ transitivePeerDependencies:
4626
+ - aws-crt
4627
+
4628
+ '@aws-sdk/middleware-bucket-endpoint@3.972.3':
4629
+ dependencies:
4630
+ '@aws-sdk/types': 3.973.1
4631
+ '@aws-sdk/util-arn-parser': 3.972.2
4632
+ '@smithy/node-config-provider': 4.3.8
4633
+ '@smithy/protocol-http': 5.3.8
4634
+ '@smithy/types': 4.12.0
4635
+ '@smithy/util-config-provider': 4.2.0
4636
+ tslib: 2.8.1
4637
+
4638
+ '@aws-sdk/middleware-expect-continue@3.972.3':
4639
+ dependencies:
4640
+ '@aws-sdk/types': 3.973.1
4641
+ '@smithy/protocol-http': 5.3.8
4642
+ '@smithy/types': 4.12.0
4643
+ tslib: 2.8.1
4644
+
4645
+ '@aws-sdk/middleware-flexible-checksums@3.972.4':
4646
+ dependencies:
4647
+ '@aws-crypto/crc32': 5.2.0
4648
+ '@aws-crypto/crc32c': 5.2.0
4649
+ '@aws-crypto/util': 5.2.0
4650
+ '@aws-sdk/core': 3.973.6
4651
+ '@aws-sdk/crc64-nvme': 3.972.0
4652
+ '@aws-sdk/types': 3.973.1
4653
+ '@smithy/is-array-buffer': 4.2.0
4654
+ '@smithy/node-config-provider': 4.3.8
4655
+ '@smithy/protocol-http': 5.3.8
4656
+ '@smithy/types': 4.12.0
4657
+ '@smithy/util-middleware': 4.2.8
4658
+ '@smithy/util-stream': 4.5.11
4659
+ '@smithy/util-utf8': 4.2.0
4660
+ tslib: 2.8.1
4661
+
4662
+ '@aws-sdk/middleware-host-header@3.972.3':
4663
+ dependencies:
4664
+ '@aws-sdk/types': 3.973.1
4665
+ '@smithy/protocol-http': 5.3.8
4666
+ '@smithy/types': 4.12.0
4667
+ tslib: 2.8.1
4668
+
4669
+ '@aws-sdk/middleware-location-constraint@3.972.3':
4670
+ dependencies:
4671
+ '@aws-sdk/types': 3.973.1
4672
+ '@smithy/types': 4.12.0
4673
+ tslib: 2.8.1
4674
+
4675
+ '@aws-sdk/middleware-logger@3.972.3':
4676
+ dependencies:
4677
+ '@aws-sdk/types': 3.973.1
4678
+ '@smithy/types': 4.12.0
4679
+ tslib: 2.8.1
4680
+
4681
+ '@aws-sdk/middleware-recursion-detection@3.972.3':
4682
+ dependencies:
4683
+ '@aws-sdk/types': 3.973.1
4684
+ '@aws/lambda-invoke-store': 0.2.3
4685
+ '@smithy/protocol-http': 5.3.8
4686
+ '@smithy/types': 4.12.0
4687
+ tslib: 2.8.1
4688
+
4689
+ '@aws-sdk/middleware-sdk-s3@3.972.6':
4690
+ dependencies:
4691
+ '@aws-sdk/core': 3.973.6
4692
+ '@aws-sdk/types': 3.973.1
4693
+ '@aws-sdk/util-arn-parser': 3.972.2
4694
+ '@smithy/core': 3.22.1
4695
+ '@smithy/node-config-provider': 4.3.8
4696
+ '@smithy/protocol-http': 5.3.8
4697
+ '@smithy/signature-v4': 5.3.8
4698
+ '@smithy/smithy-client': 4.11.2
4699
+ '@smithy/types': 4.12.0
4700
+ '@smithy/util-config-provider': 4.2.0
4701
+ '@smithy/util-middleware': 4.2.8
4702
+ '@smithy/util-stream': 4.5.11
4703
+ '@smithy/util-utf8': 4.2.0
4704
+ tslib: 2.8.1
4705
+
4706
+ '@aws-sdk/middleware-ssec@3.972.3':
4707
+ dependencies:
4708
+ '@aws-sdk/types': 3.973.1
4709
+ '@smithy/types': 4.12.0
4710
+ tslib: 2.8.1
4711
+
4712
+ '@aws-sdk/middleware-user-agent@3.972.6':
4713
+ dependencies:
4714
+ '@aws-sdk/core': 3.973.6
4715
+ '@aws-sdk/types': 3.973.1
4716
+ '@aws-sdk/util-endpoints': 3.982.0
4717
+ '@smithy/core': 3.22.1
4718
+ '@smithy/protocol-http': 5.3.8
4719
+ '@smithy/types': 4.12.0
4720
+ tslib: 2.8.1
4721
+
4722
+ '@aws-sdk/nested-clients@3.982.0':
4723
+ dependencies:
4724
+ '@aws-crypto/sha256-browser': 5.2.0
4725
+ '@aws-crypto/sha256-js': 5.2.0
4726
+ '@aws-sdk/core': 3.973.6
4727
+ '@aws-sdk/middleware-host-header': 3.972.3
4728
+ '@aws-sdk/middleware-logger': 3.972.3
4729
+ '@aws-sdk/middleware-recursion-detection': 3.972.3
4730
+ '@aws-sdk/middleware-user-agent': 3.972.6
4731
+ '@aws-sdk/region-config-resolver': 3.972.3
4732
+ '@aws-sdk/types': 3.973.1
4733
+ '@aws-sdk/util-endpoints': 3.982.0
4734
+ '@aws-sdk/util-user-agent-browser': 3.972.3
4735
+ '@aws-sdk/util-user-agent-node': 3.972.4
4736
+ '@smithy/config-resolver': 4.4.6
4737
+ '@smithy/core': 3.22.1
4738
+ '@smithy/fetch-http-handler': 5.3.9
4739
+ '@smithy/hash-node': 4.2.8
4740
+ '@smithy/invalid-dependency': 4.2.8
4741
+ '@smithy/middleware-content-length': 4.2.8
4742
+ '@smithy/middleware-endpoint': 4.4.13
4743
+ '@smithy/middleware-retry': 4.4.30
4744
+ '@smithy/middleware-serde': 4.2.9
4745
+ '@smithy/middleware-stack': 4.2.8
4746
+ '@smithy/node-config-provider': 4.3.8
4747
+ '@smithy/node-http-handler': 4.4.9
4748
+ '@smithy/protocol-http': 5.3.8
4749
+ '@smithy/smithy-client': 4.11.2
4750
+ '@smithy/types': 4.12.0
4751
+ '@smithy/url-parser': 4.2.8
4752
+ '@smithy/util-base64': 4.3.0
4753
+ '@smithy/util-body-length-browser': 4.2.0
4754
+ '@smithy/util-body-length-node': 4.2.1
4755
+ '@smithy/util-defaults-mode-browser': 4.3.29
4756
+ '@smithy/util-defaults-mode-node': 4.2.32
4757
+ '@smithy/util-endpoints': 3.2.8
4758
+ '@smithy/util-middleware': 4.2.8
4759
+ '@smithy/util-retry': 4.2.8
4760
+ '@smithy/util-utf8': 4.2.0
4761
+ tslib: 2.8.1
4762
+ transitivePeerDependencies:
4763
+ - aws-crt
4764
+
4765
+ '@aws-sdk/region-config-resolver@3.972.3':
4766
+ dependencies:
4767
+ '@aws-sdk/types': 3.973.1
4768
+ '@smithy/config-resolver': 4.4.6
4769
+ '@smithy/node-config-provider': 4.3.8
4770
+ '@smithy/types': 4.12.0
4771
+ tslib: 2.8.1
4772
+
4773
+ '@aws-sdk/s3-request-presigner@3.984.0':
4774
+ dependencies:
4775
+ '@aws-sdk/signature-v4-multi-region': 3.984.0
4776
+ '@aws-sdk/types': 3.973.1
4777
+ '@aws-sdk/util-format-url': 3.972.3
4778
+ '@smithy/middleware-endpoint': 4.4.13
4779
+ '@smithy/protocol-http': 5.3.8
4780
+ '@smithy/smithy-client': 4.11.2
4781
+ '@smithy/types': 4.12.0
4782
+ tslib: 2.8.1
4783
+
4784
+ '@aws-sdk/signature-v4-multi-region@3.984.0':
4785
+ dependencies:
4786
+ '@aws-sdk/middleware-sdk-s3': 3.972.6
4787
+ '@aws-sdk/types': 3.973.1
4788
+ '@smithy/protocol-http': 5.3.8
4789
+ '@smithy/signature-v4': 5.3.8
4790
+ '@smithy/types': 4.12.0
4791
+ tslib: 2.8.1
4792
+
4793
+ '@aws-sdk/token-providers@3.982.0':
4794
+ dependencies:
4795
+ '@aws-sdk/core': 3.973.6
4796
+ '@aws-sdk/nested-clients': 3.982.0
4797
+ '@aws-sdk/types': 3.973.1
4798
+ '@smithy/property-provider': 4.2.8
4799
+ '@smithy/shared-ini-file-loader': 4.4.3
4800
+ '@smithy/types': 4.12.0
4801
+ tslib: 2.8.1
4802
+ transitivePeerDependencies:
4803
+ - aws-crt
4804
+
4805
+ '@aws-sdk/types@3.973.1':
4806
+ dependencies:
4807
+ '@smithy/types': 4.12.0
4808
+ tslib: 2.8.1
4809
+
4810
+ '@aws-sdk/util-arn-parser@3.972.2':
4811
+ dependencies:
4812
+ tslib: 2.8.1
4813
+
4814
+ '@aws-sdk/util-endpoints@3.982.0':
4815
+ dependencies:
4816
+ '@aws-sdk/types': 3.973.1
4817
+ '@smithy/types': 4.12.0
4818
+ '@smithy/url-parser': 4.2.8
4819
+ '@smithy/util-endpoints': 3.2.8
4820
+ tslib: 2.8.1
4821
+
4822
+ '@aws-sdk/util-endpoints@3.984.0':
4823
+ dependencies:
4824
+ '@aws-sdk/types': 3.973.1
4825
+ '@smithy/types': 4.12.0
4826
+ '@smithy/url-parser': 4.2.8
4827
+ '@smithy/util-endpoints': 3.2.8
4828
+ tslib: 2.8.1
4829
+
4830
+ '@aws-sdk/util-format-url@3.972.3':
4831
+ dependencies:
4832
+ '@aws-sdk/types': 3.973.1
4833
+ '@smithy/querystring-builder': 4.2.8
4834
+ '@smithy/types': 4.12.0
4835
+ tslib: 2.8.1
4836
+
4837
+ '@aws-sdk/util-locate-window@3.965.4':
4838
+ dependencies:
4839
+ tslib: 2.8.1
4840
+
4841
+ '@aws-sdk/util-user-agent-browser@3.972.3':
4842
+ dependencies:
4843
+ '@aws-sdk/types': 3.973.1
4844
+ '@smithy/types': 4.12.0
4845
+ bowser: 2.13.1
4846
+ tslib: 2.8.1
4847
+
4848
+ '@aws-sdk/util-user-agent-node@3.972.4':
4849
+ dependencies:
4850
+ '@aws-sdk/middleware-user-agent': 3.972.6
4851
+ '@aws-sdk/types': 3.973.1
4852
+ '@smithy/node-config-provider': 4.3.8
4853
+ '@smithy/types': 4.12.0
4854
+ tslib: 2.8.1
4855
+
4856
+ '@aws-sdk/xml-builder@3.972.4':
4857
+ dependencies:
4858
+ '@smithy/types': 4.12.0
4859
+ fast-xml-parser: 5.3.4
4860
+ tslib: 2.8.1
4861
+
4862
+ '@aws/lambda-invoke-store@0.2.3': {}
4863
+
4864
  '@babel/code-frame@7.27.1':
4865
  dependencies:
4866
  '@babel/helper-validator-identifier': 7.27.1
 
5007
 
5008
  '@date-fns/tz@1.4.1': {}
5009
 
5010
+ '@drizzle-team/brocli@0.10.2': {}
5011
+
5012
+ '@esbuild-kit/core-utils@3.3.2':
5013
+ dependencies:
5014
+ esbuild: 0.18.20
5015
+ source-map-support: 0.5.21
5016
+
5017
+ '@esbuild-kit/esm-loader@2.6.5':
5018
+ dependencies:
5019
+ '@esbuild-kit/core-utils': 3.3.2
5020
+ get-tsconfig: 4.12.0
5021
+
5022
  '@esbuild/aix-ppc64@0.21.5':
5023
  optional: true
5024
 
5025
  '@esbuild/aix-ppc64@0.25.10':
5026
  optional: true
5027
 
5028
+ '@esbuild/android-arm64@0.18.20':
5029
+ optional: true
5030
+
5031
  '@esbuild/android-arm64@0.21.5':
5032
  optional: true
5033
 
5034
  '@esbuild/android-arm64@0.25.10':
5035
  optional: true
5036
 
5037
+ '@esbuild/android-arm@0.18.20':
5038
+ optional: true
5039
+
5040
  '@esbuild/android-arm@0.21.5':
5041
  optional: true
5042
 
5043
  '@esbuild/android-arm@0.25.10':
5044
  optional: true
5045
 
5046
+ '@esbuild/android-x64@0.18.20':
5047
+ optional: true
5048
+
5049
  '@esbuild/android-x64@0.21.5':
5050
  optional: true
5051
 
5052
  '@esbuild/android-x64@0.25.10':
5053
  optional: true
5054
 
5055
+ '@esbuild/darwin-arm64@0.18.20':
5056
+ optional: true
5057
+
5058
  '@esbuild/darwin-arm64@0.21.5':
5059
  optional: true
5060
 
5061
  '@esbuild/darwin-arm64@0.25.10':
5062
  optional: true
5063
 
5064
+ '@esbuild/darwin-x64@0.18.20':
5065
+ optional: true
5066
+
5067
  '@esbuild/darwin-x64@0.21.5':
5068
  optional: true
5069
 
5070
  '@esbuild/darwin-x64@0.25.10':
5071
  optional: true
5072
 
5073
+ '@esbuild/freebsd-arm64@0.18.20':
5074
+ optional: true
5075
+
5076
  '@esbuild/freebsd-arm64@0.21.5':
5077
  optional: true
5078
 
5079
  '@esbuild/freebsd-arm64@0.25.10':
5080
  optional: true
5081
 
5082
+ '@esbuild/freebsd-x64@0.18.20':
5083
+ optional: true
5084
+
5085
  '@esbuild/freebsd-x64@0.21.5':
5086
  optional: true
5087
 
5088
  '@esbuild/freebsd-x64@0.25.10':
5089
  optional: true
5090
 
5091
+ '@esbuild/linux-arm64@0.18.20':
5092
+ optional: true
5093
+
5094
  '@esbuild/linux-arm64@0.21.5':
5095
  optional: true
5096
 
5097
  '@esbuild/linux-arm64@0.25.10':
5098
  optional: true
5099
 
5100
+ '@esbuild/linux-arm@0.18.20':
5101
+ optional: true
5102
+
5103
  '@esbuild/linux-arm@0.21.5':
5104
  optional: true
5105
 
5106
  '@esbuild/linux-arm@0.25.10':
5107
  optional: true
5108
 
5109
+ '@esbuild/linux-ia32@0.18.20':
5110
+ optional: true
5111
+
5112
  '@esbuild/linux-ia32@0.21.5':
5113
  optional: true
5114
 
5115
  '@esbuild/linux-ia32@0.25.10':
5116
  optional: true
5117
 
5118
+ '@esbuild/linux-loong64@0.18.20':
5119
+ optional: true
5120
+
5121
  '@esbuild/linux-loong64@0.21.5':
5122
  optional: true
5123
 
5124
  '@esbuild/linux-loong64@0.25.10':
5125
  optional: true
5126
 
5127
+ '@esbuild/linux-mips64el@0.18.20':
5128
+ optional: true
5129
+
5130
  '@esbuild/linux-mips64el@0.21.5':
5131
  optional: true
5132
 
5133
  '@esbuild/linux-mips64el@0.25.10':
5134
  optional: true
5135
 
5136
+ '@esbuild/linux-ppc64@0.18.20':
5137
+ optional: true
5138
+
5139
  '@esbuild/linux-ppc64@0.21.5':
5140
  optional: true
5141
 
5142
  '@esbuild/linux-ppc64@0.25.10':
5143
  optional: true
5144
 
5145
+ '@esbuild/linux-riscv64@0.18.20':
5146
+ optional: true
5147
+
5148
  '@esbuild/linux-riscv64@0.21.5':
5149
  optional: true
5150
 
5151
  '@esbuild/linux-riscv64@0.25.10':
5152
  optional: true
5153
 
5154
+ '@esbuild/linux-s390x@0.18.20':
5155
+ optional: true
5156
+
5157
  '@esbuild/linux-s390x@0.21.5':
5158
  optional: true
5159
 
5160
  '@esbuild/linux-s390x@0.25.10':
5161
  optional: true
5162
 
5163
+ '@esbuild/linux-x64@0.18.20':
5164
+ optional: true
5165
+
5166
  '@esbuild/linux-x64@0.21.5':
5167
  optional: true
5168
 
 
5172
  '@esbuild/netbsd-arm64@0.25.10':
5173
  optional: true
5174
 
5175
+ '@esbuild/netbsd-x64@0.18.20':
5176
+ optional: true
5177
+
5178
  '@esbuild/netbsd-x64@0.21.5':
5179
  optional: true
5180
 
 
5184
  '@esbuild/openbsd-arm64@0.25.10':
5185
  optional: true
5186
 
5187
+ '@esbuild/openbsd-x64@0.18.20':
5188
+ optional: true
5189
+
5190
  '@esbuild/openbsd-x64@0.21.5':
5191
  optional: true
5192
 
 
5196
  '@esbuild/openharmony-arm64@0.25.10':
5197
  optional: true
5198
 
5199
+ '@esbuild/sunos-x64@0.18.20':
5200
+ optional: true
5201
+
5202
  '@esbuild/sunos-x64@0.21.5':
5203
  optional: true
5204
 
5205
  '@esbuild/sunos-x64@0.25.10':
5206
  optional: true
5207
 
5208
+ '@esbuild/win32-arm64@0.18.20':
5209
+ optional: true
5210
+
5211
  '@esbuild/win32-arm64@0.21.5':
5212
  optional: true
5213
 
5214
  '@esbuild/win32-arm64@0.25.10':
5215
  optional: true
5216
 
5217
+ '@esbuild/win32-ia32@0.18.20':
5218
+ optional: true
5219
+
5220
  '@esbuild/win32-ia32@0.21.5':
5221
  optional: true
5222
 
5223
  '@esbuild/win32-ia32@0.25.10':
5224
  optional: true
5225
 
5226
+ '@esbuild/win32-x64@0.18.20':
5227
+ optional: true
5228
+
5229
  '@esbuild/win32-x64@0.21.5':
5230
  optional: true
5231
 
 
6052
 
6053
  '@shikijs/vscode-textmate@10.0.2': {}
6054
 
6055
+ '@smithy/abort-controller@4.2.8':
6056
+ dependencies:
6057
+ '@smithy/types': 4.12.0
6058
+ tslib: 2.8.1
6059
+
6060
+ '@smithy/chunked-blob-reader-native@4.2.1':
6061
+ dependencies:
6062
+ '@smithy/util-base64': 4.3.0
6063
+ tslib: 2.8.1
6064
+
6065
+ '@smithy/chunked-blob-reader@5.2.0':
6066
+ dependencies:
6067
+ tslib: 2.8.1
6068
+
6069
+ '@smithy/config-resolver@4.4.6':
6070
+ dependencies:
6071
+ '@smithy/node-config-provider': 4.3.8
6072
+ '@smithy/types': 4.12.0
6073
+ '@smithy/util-config-provider': 4.2.0
6074
+ '@smithy/util-endpoints': 3.2.8
6075
+ '@smithy/util-middleware': 4.2.8
6076
+ tslib: 2.8.1
6077
+
6078
+ '@smithy/core@3.22.1':
6079
+ dependencies:
6080
+ '@smithy/middleware-serde': 4.2.9
6081
+ '@smithy/protocol-http': 5.3.8
6082
+ '@smithy/types': 4.12.0
6083
+ '@smithy/util-base64': 4.3.0
6084
+ '@smithy/util-body-length-browser': 4.2.0
6085
+ '@smithy/util-middleware': 4.2.8
6086
+ '@smithy/util-stream': 4.5.11
6087
+ '@smithy/util-utf8': 4.2.0
6088
+ '@smithy/uuid': 1.1.0
6089
+ tslib: 2.8.1
6090
+
6091
+ '@smithy/credential-provider-imds@4.2.8':
6092
+ dependencies:
6093
+ '@smithy/node-config-provider': 4.3.8
6094
+ '@smithy/property-provider': 4.2.8
6095
+ '@smithy/types': 4.12.0
6096
+ '@smithy/url-parser': 4.2.8
6097
+ tslib: 2.8.1
6098
+
6099
+ '@smithy/eventstream-codec@4.2.8':
6100
+ dependencies:
6101
+ '@aws-crypto/crc32': 5.2.0
6102
+ '@smithy/types': 4.12.0
6103
+ '@smithy/util-hex-encoding': 4.2.0
6104
+ tslib: 2.8.1
6105
+
6106
+ '@smithy/eventstream-serde-browser@4.2.8':
6107
+ dependencies:
6108
+ '@smithy/eventstream-serde-universal': 4.2.8
6109
+ '@smithy/types': 4.12.0
6110
+ tslib: 2.8.1
6111
+
6112
+ '@smithy/eventstream-serde-config-resolver@4.3.8':
6113
+ dependencies:
6114
+ '@smithy/types': 4.12.0
6115
+ tslib: 2.8.1
6116
+
6117
+ '@smithy/eventstream-serde-node@4.2.8':
6118
+ dependencies:
6119
+ '@smithy/eventstream-serde-universal': 4.2.8
6120
+ '@smithy/types': 4.12.0
6121
+ tslib: 2.8.1
6122
+
6123
+ '@smithy/eventstream-serde-universal@4.2.8':
6124
+ dependencies:
6125
+ '@smithy/eventstream-codec': 4.2.8
6126
+ '@smithy/types': 4.12.0
6127
+ tslib: 2.8.1
6128
+
6129
+ '@smithy/fetch-http-handler@5.3.9':
6130
+ dependencies:
6131
+ '@smithy/protocol-http': 5.3.8
6132
+ '@smithy/querystring-builder': 4.2.8
6133
+ '@smithy/types': 4.12.0
6134
+ '@smithy/util-base64': 4.3.0
6135
+ tslib: 2.8.1
6136
+
6137
+ '@smithy/hash-blob-browser@4.2.9':
6138
+ dependencies:
6139
+ '@smithy/chunked-blob-reader': 5.2.0
6140
+ '@smithy/chunked-blob-reader-native': 4.2.1
6141
+ '@smithy/types': 4.12.0
6142
+ tslib: 2.8.1
6143
+
6144
+ '@smithy/hash-node@4.2.8':
6145
+ dependencies:
6146
+ '@smithy/types': 4.12.0
6147
+ '@smithy/util-buffer-from': 4.2.0
6148
+ '@smithy/util-utf8': 4.2.0
6149
+ tslib: 2.8.1
6150
+
6151
+ '@smithy/hash-stream-node@4.2.8':
6152
+ dependencies:
6153
+ '@smithy/types': 4.12.0
6154
+ '@smithy/util-utf8': 4.2.0
6155
+ tslib: 2.8.1
6156
+
6157
+ '@smithy/invalid-dependency@4.2.8':
6158
+ dependencies:
6159
+ '@smithy/types': 4.12.0
6160
+ tslib: 2.8.1
6161
+
6162
+ '@smithy/is-array-buffer@2.2.0':
6163
+ dependencies:
6164
+ tslib: 2.8.1
6165
+
6166
+ '@smithy/is-array-buffer@4.2.0':
6167
+ dependencies:
6168
+ tslib: 2.8.1
6169
+
6170
+ '@smithy/md5-js@4.2.8':
6171
+ dependencies:
6172
+ '@smithy/types': 4.12.0
6173
+ '@smithy/util-utf8': 4.2.0
6174
+ tslib: 2.8.1
6175
+
6176
+ '@smithy/middleware-content-length@4.2.8':
6177
+ dependencies:
6178
+ '@smithy/protocol-http': 5.3.8
6179
+ '@smithy/types': 4.12.0
6180
+ tslib: 2.8.1
6181
+
6182
+ '@smithy/middleware-endpoint@4.4.13':
6183
+ dependencies:
6184
+ '@smithy/core': 3.22.1
6185
+ '@smithy/middleware-serde': 4.2.9
6186
+ '@smithy/node-config-provider': 4.3.8
6187
+ '@smithy/shared-ini-file-loader': 4.4.3
6188
+ '@smithy/types': 4.12.0
6189
+ '@smithy/url-parser': 4.2.8
6190
+ '@smithy/util-middleware': 4.2.8
6191
+ tslib: 2.8.1
6192
+
6193
+ '@smithy/middleware-retry@4.4.30':
6194
+ dependencies:
6195
+ '@smithy/node-config-provider': 4.3.8
6196
+ '@smithy/protocol-http': 5.3.8
6197
+ '@smithy/service-error-classification': 4.2.8
6198
+ '@smithy/smithy-client': 4.11.2
6199
+ '@smithy/types': 4.12.0
6200
+ '@smithy/util-middleware': 4.2.8
6201
+ '@smithy/util-retry': 4.2.8
6202
+ '@smithy/uuid': 1.1.0
6203
+ tslib: 2.8.1
6204
+
6205
+ '@smithy/middleware-serde@4.2.9':
6206
+ dependencies:
6207
+ '@smithy/protocol-http': 5.3.8
6208
+ '@smithy/types': 4.12.0
6209
+ tslib: 2.8.1
6210
+
6211
+ '@smithy/middleware-stack@4.2.8':
6212
+ dependencies:
6213
+ '@smithy/types': 4.12.0
6214
+ tslib: 2.8.1
6215
+
6216
+ '@smithy/node-config-provider@4.3.8':
6217
+ dependencies:
6218
+ '@smithy/property-provider': 4.2.8
6219
+ '@smithy/shared-ini-file-loader': 4.4.3
6220
+ '@smithy/types': 4.12.0
6221
+ tslib: 2.8.1
6222
+
6223
+ '@smithy/node-http-handler@4.4.9':
6224
+ dependencies:
6225
+ '@smithy/abort-controller': 4.2.8
6226
+ '@smithy/protocol-http': 5.3.8
6227
+ '@smithy/querystring-builder': 4.2.8
6228
+ '@smithy/types': 4.12.0
6229
+ tslib: 2.8.1
6230
+
6231
+ '@smithy/property-provider@4.2.8':
6232
+ dependencies:
6233
+ '@smithy/types': 4.12.0
6234
+ tslib: 2.8.1
6235
+
6236
+ '@smithy/protocol-http@5.3.8':
6237
+ dependencies:
6238
+ '@smithy/types': 4.12.0
6239
+ tslib: 2.8.1
6240
+
6241
+ '@smithy/querystring-builder@4.2.8':
6242
+ dependencies:
6243
+ '@smithy/types': 4.12.0
6244
+ '@smithy/util-uri-escape': 4.2.0
6245
+ tslib: 2.8.1
6246
+
6247
+ '@smithy/querystring-parser@4.2.8':
6248
+ dependencies:
6249
+ '@smithy/types': 4.12.0
6250
+ tslib: 2.8.1
6251
+
6252
+ '@smithy/service-error-classification@4.2.8':
6253
+ dependencies:
6254
+ '@smithy/types': 4.12.0
6255
+
6256
+ '@smithy/shared-ini-file-loader@4.4.3':
6257
+ dependencies:
6258
+ '@smithy/types': 4.12.0
6259
+ tslib: 2.8.1
6260
+
6261
+ '@smithy/signature-v4@5.3.8':
6262
+ dependencies:
6263
+ '@smithy/is-array-buffer': 4.2.0
6264
+ '@smithy/protocol-http': 5.3.8
6265
+ '@smithy/types': 4.12.0
6266
+ '@smithy/util-hex-encoding': 4.2.0
6267
+ '@smithy/util-middleware': 4.2.8
6268
+ '@smithy/util-uri-escape': 4.2.0
6269
+ '@smithy/util-utf8': 4.2.0
6270
+ tslib: 2.8.1
6271
+
6272
+ '@smithy/smithy-client@4.11.2':
6273
+ dependencies:
6274
+ '@smithy/core': 3.22.1
6275
+ '@smithy/middleware-endpoint': 4.4.13
6276
+ '@smithy/middleware-stack': 4.2.8
6277
+ '@smithy/protocol-http': 5.3.8
6278
+ '@smithy/types': 4.12.0
6279
+ '@smithy/util-stream': 4.5.11
6280
+ tslib: 2.8.1
6281
+
6282
+ '@smithy/types@4.12.0':
6283
+ dependencies:
6284
+ tslib: 2.8.1
6285
+
6286
+ '@smithy/url-parser@4.2.8':
6287
+ dependencies:
6288
+ '@smithy/querystring-parser': 4.2.8
6289
+ '@smithy/types': 4.12.0
6290
+ tslib: 2.8.1
6291
+
6292
+ '@smithy/util-base64@4.3.0':
6293
+ dependencies:
6294
+ '@smithy/util-buffer-from': 4.2.0
6295
+ '@smithy/util-utf8': 4.2.0
6296
+ tslib: 2.8.1
6297
+
6298
+ '@smithy/util-body-length-browser@4.2.0':
6299
+ dependencies:
6300
+ tslib: 2.8.1
6301
+
6302
+ '@smithy/util-body-length-node@4.2.1':
6303
+ dependencies:
6304
+ tslib: 2.8.1
6305
+
6306
+ '@smithy/util-buffer-from@2.2.0':
6307
+ dependencies:
6308
+ '@smithy/is-array-buffer': 2.2.0
6309
+ tslib: 2.8.1
6310
+
6311
+ '@smithy/util-buffer-from@4.2.0':
6312
+ dependencies:
6313
+ '@smithy/is-array-buffer': 4.2.0
6314
+ tslib: 2.8.1
6315
+
6316
+ '@smithy/util-config-provider@4.2.0':
6317
+ dependencies:
6318
+ tslib: 2.8.1
6319
+
6320
+ '@smithy/util-defaults-mode-browser@4.3.29':
6321
+ dependencies:
6322
+ '@smithy/property-provider': 4.2.8
6323
+ '@smithy/smithy-client': 4.11.2
6324
+ '@smithy/types': 4.12.0
6325
+ tslib: 2.8.1
6326
+
6327
+ '@smithy/util-defaults-mode-node@4.2.32':
6328
+ dependencies:
6329
+ '@smithy/config-resolver': 4.4.6
6330
+ '@smithy/credential-provider-imds': 4.2.8
6331
+ '@smithy/node-config-provider': 4.3.8
6332
+ '@smithy/property-provider': 4.2.8
6333
+ '@smithy/smithy-client': 4.11.2
6334
+ '@smithy/types': 4.12.0
6335
+ tslib: 2.8.1
6336
+
6337
+ '@smithy/util-endpoints@3.2.8':
6338
+ dependencies:
6339
+ '@smithy/node-config-provider': 4.3.8
6340
+ '@smithy/types': 4.12.0
6341
+ tslib: 2.8.1
6342
+
6343
+ '@smithy/util-hex-encoding@4.2.0':
6344
+ dependencies:
6345
+ tslib: 2.8.1
6346
+
6347
+ '@smithy/util-middleware@4.2.8':
6348
+ dependencies:
6349
+ '@smithy/types': 4.12.0
6350
+ tslib: 2.8.1
6351
+
6352
+ '@smithy/util-retry@4.2.8':
6353
+ dependencies:
6354
+ '@smithy/service-error-classification': 4.2.8
6355
+ '@smithy/types': 4.12.0
6356
+ tslib: 2.8.1
6357
+
6358
+ '@smithy/util-stream@4.5.11':
6359
+ dependencies:
6360
+ '@smithy/fetch-http-handler': 5.3.9
6361
+ '@smithy/node-http-handler': 4.4.9
6362
+ '@smithy/types': 4.12.0
6363
+ '@smithy/util-base64': 4.3.0
6364
+ '@smithy/util-buffer-from': 4.2.0
6365
+ '@smithy/util-hex-encoding': 4.2.0
6366
+ '@smithy/util-utf8': 4.2.0
6367
+ tslib: 2.8.1
6368
+
6369
+ '@smithy/util-uri-escape@4.2.0':
6370
+ dependencies:
6371
+ tslib: 2.8.1
6372
+
6373
+ '@smithy/util-utf8@2.3.0':
6374
+ dependencies:
6375
+ '@smithy/util-buffer-from': 2.2.0
6376
+ tslib: 2.8.1
6377
+
6378
+ '@smithy/util-utf8@4.2.0':
6379
+ dependencies:
6380
+ '@smithy/util-buffer-from': 4.2.0
6381
+ tslib: 2.8.1
6382
+
6383
+ '@smithy/util-waiter@4.2.8':
6384
+ dependencies:
6385
+ '@smithy/abort-controller': 4.2.8
6386
+ '@smithy/types': 4.12.0
6387
+ tslib: 2.8.1
6388
+
6389
+ '@smithy/uuid@1.1.0':
6390
+ dependencies:
6391
+ tslib: 2.8.1
6392
+
6393
  '@standard-schema/utils@0.3.0': {}
6394
 
6395
  '@tailwindcss/node@4.1.14':
 
6468
  tailwindcss: 4.1.14
6469
  vite: 7.1.9(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)
6470
 
6471
+ '@tanstack/query-core@5.90.20': {}
6472
+
6473
+ '@tanstack/react-query@5.90.20(react@19.2.1)':
6474
+ dependencies:
6475
+ '@tanstack/query-core': 5.90.20
6476
+ react: 19.2.1
6477
+
6478
+ '@trpc/client@11.9.0(@trpc/server@11.9.0(typescript@5.9.3))(typescript@5.9.3)':
6479
+ dependencies:
6480
+ '@trpc/server': 11.9.0(typescript@5.9.3)
6481
+ typescript: 5.9.3
6482
+
6483
+ '@trpc/react-query@11.9.0(@tanstack/react-query@5.90.20(react@19.2.1))(@trpc/client@11.9.0(@trpc/server@11.9.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.9.0(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)':
6484
+ dependencies:
6485
+ '@tanstack/react-query': 5.90.20(react@19.2.1)
6486
+ '@trpc/client': 11.9.0(@trpc/server@11.9.0(typescript@5.9.3))(typescript@5.9.3)
6487
+ '@trpc/server': 11.9.0(typescript@5.9.3)
6488
+ react: 19.2.1
6489
+ react-dom: 19.2.1(react@19.2.1)
6490
+ typescript: 5.9.3
6491
+
6492
+ '@trpc/server@11.9.0(typescript@5.9.3)':
6493
+ dependencies:
6494
+ typescript: 5.9.3
6495
+
6496
  '@types/babel__core@7.20.5':
6497
  dependencies:
6498
  '@babel/parser': 7.28.4
 
6805
  postcss: 8.5.6
6806
  postcss-value-parser: 4.2.0
6807
 
6808
+ aws-ssl-profiles@1.1.2: {}
6809
+
6810
  axios@1.12.2:
6811
  dependencies:
6812
  follow-redirects: 1.15.11
 
6836
  transitivePeerDependencies:
6837
  - supports-color
6838
 
6839
+ bowser@2.13.1: {}
6840
+
6841
  browserslist@4.26.3:
6842
  dependencies:
6843
  baseline-browser-mapping: 2.8.13
 
6846
  node-releases: 2.0.23
6847
  update-browserslist-db: 1.1.3(browserslist@4.26.3)
6848
 
6849
+ buffer-from@1.1.2: {}
6850
+
6851
  bytes@3.1.2: {}
6852
 
6853
  cac@6.7.14: {}
 
6944
 
6945
  cookie@0.7.1: {}
6946
 
6947
+ cookie@1.1.1: {}
6948
+
6949
+ copy-anything@3.0.5:
6950
+ dependencies:
6951
+ is-what: 4.1.16
6952
+
6953
  cose-base@1.0.3:
6954
  dependencies:
6955
  layout-base: 1.0.2
 
7174
 
7175
  delayed-stream@1.0.0: {}
7176
 
7177
+ denque@2.1.0: {}
7178
+
7179
  depd@2.0.0: {}
7180
 
7181
  dequal@2.0.3: {}
 
7199
  optionalDependencies:
7200
  '@types/trusted-types': 2.0.7
7201
 
7202
+ dotenv@17.2.4: {}
7203
+
7204
+ drizzle-kit@0.31.8:
7205
+ dependencies:
7206
+ '@drizzle-team/brocli': 0.10.2
7207
+ '@esbuild-kit/esm-loader': 2.6.5
7208
+ esbuild: 0.25.10
7209
+ esbuild-register: 3.6.0(esbuild@0.25.10)
7210
+ transitivePeerDependencies:
7211
+ - supports-color
7212
+
7213
+ drizzle-orm@0.44.7(mysql2@3.16.3):
7214
+ optionalDependencies:
7215
+ mysql2: 3.16.3
7216
+
7217
  dunder-proto@1.0.1:
7218
  dependencies:
7219
  call-bind-apply-helpers: 1.0.2
 
7264
  has-tostringtag: 1.0.2
7265
  hasown: 2.0.2
7266
 
7267
+ esbuild-register@3.6.0(esbuild@0.25.10):
7268
+ dependencies:
7269
+ debug: 4.4.3
7270
+ esbuild: 0.25.10
7271
+ transitivePeerDependencies:
7272
+ - supports-color
7273
+
7274
+ esbuild@0.18.20:
7275
+ optionalDependencies:
7276
+ '@esbuild/android-arm': 0.18.20
7277
+ '@esbuild/android-arm64': 0.18.20
7278
+ '@esbuild/android-x64': 0.18.20
7279
+ '@esbuild/darwin-arm64': 0.18.20
7280
+ '@esbuild/darwin-x64': 0.18.20
7281
+ '@esbuild/freebsd-arm64': 0.18.20
7282
+ '@esbuild/freebsd-x64': 0.18.20
7283
+ '@esbuild/linux-arm': 0.18.20
7284
+ '@esbuild/linux-arm64': 0.18.20
7285
+ '@esbuild/linux-ia32': 0.18.20
7286
+ '@esbuild/linux-loong64': 0.18.20
7287
+ '@esbuild/linux-mips64el': 0.18.20
7288
+ '@esbuild/linux-ppc64': 0.18.20
7289
+ '@esbuild/linux-riscv64': 0.18.20
7290
+ '@esbuild/linux-s390x': 0.18.20
7291
+ '@esbuild/linux-x64': 0.18.20
7292
+ '@esbuild/netbsd-x64': 0.18.20
7293
+ '@esbuild/openbsd-x64': 0.18.20
7294
+ '@esbuild/sunos-x64': 0.18.20
7295
+ '@esbuild/win32-arm64': 0.18.20
7296
+ '@esbuild/win32-ia32': 0.18.20
7297
+ '@esbuild/win32-x64': 0.18.20
7298
+
7299
  esbuild@0.21.5:
7300
  optionalDependencies:
7301
  '@esbuild/aix-ppc64': 0.21.5
 
7413
 
7414
  fast-equals@5.3.2: {}
7415
 
7416
+ fast-xml-parser@5.3.4:
7417
+ dependencies:
7418
+ strnum: 2.1.2
7419
+
7420
  fdir@6.5.0(picomatch@4.0.3):
7421
  optionalDependencies:
7422
  picomatch: 4.0.3
 
7463
 
7464
  function-bind@1.1.2: {}
7465
 
7466
+ generate-function@2.3.1:
7467
+ dependencies:
7468
+ is-property: 1.0.2
7469
+
7470
  gensync@1.0.0-beta.2: {}
7471
 
7472
  get-intrinsic@1.3.0:
 
7651
  dependencies:
7652
  safer-buffer: 2.1.2
7653
 
7654
+ iconv-lite@0.7.2:
7655
+ dependencies:
7656
+ safer-buffer: 2.1.2
7657
+
7658
  inherits@2.0.4: {}
7659
 
7660
  inline-style-parser@0.2.4: {}
 
7683
 
7684
  is-plain-obj@4.1.0: {}
7685
 
7686
+ is-property@1.0.2: {}
7687
+
7688
+ is-what@4.1.16: {}
7689
+
7690
  jiti@2.6.1: {}
7691
 
7692
+ jose@6.1.0: {}
7693
+
7694
  js-tokens@4.0.0: {}
7695
 
7696
  jsesc@3.1.0: {}
 
7772
 
7773
  lodash@4.17.21: {}
7774
 
7775
+ long@5.3.2: {}
7776
+
7777
  longest-streak@3.1.0: {}
7778
 
7779
  loose-envify@1.4.0:
 
7786
  dependencies:
7787
  yallist: 3.1.1
7788
 
7789
+ lru.min@1.1.4: {}
7790
+
7791
  lucide-react@0.453.0(react@19.2.1):
7792
  dependencies:
7793
  react: 19.2.1
 
8238
 
8239
  ms@2.1.3: {}
8240
 
8241
+ mysql2@3.16.3:
8242
+ dependencies:
8243
+ aws-ssl-profiles: 1.1.2
8244
+ denque: 2.1.0
8245
+ generate-function: 2.3.1
8246
+ iconv-lite: 0.7.2
8247
+ long: 5.3.2
8248
+ lru.min: 1.1.4
8249
+ named-placeholders: 1.1.6
8250
+ seq-queue: 0.0.5
8251
+ sqlstring: 2.3.3
8252
+
8253
+ named-placeholders@1.1.6:
8254
+ dependencies:
8255
+ lru.min: 1.1.4
8256
+
8257
  nanoid@3.3.11: {}
8258
 
8259
  nanoid@5.1.6: {}
 
8633
  transitivePeerDependencies:
8634
  - supports-color
8635
 
8636
+ seq-queue@0.0.5: {}
8637
+
8638
  serve-static@1.16.2:
8639
  dependencies:
8640
  encodeurl: 2.0.0
 
8694
 
8695
  source-map-js@1.2.1: {}
8696
 
8697
+ source-map-support@0.5.21:
8698
+ dependencies:
8699
+ buffer-from: 1.1.2
8700
+ source-map: 0.6.1
8701
+
8702
+ source-map@0.6.1: {}
8703
+
8704
  space-separated-tokens@2.0.2: {}
8705
 
8706
+ sqlstring@2.3.3: {}
8707
+
8708
  stackback@0.0.2: {}
8709
 
8710
  statuses@2.0.1: {}
 
8736
  character-entities-html4: 2.1.0
8737
  character-entities-legacy: 3.0.0
8738
 
8739
+ stripe@20.3.1(@types/node@24.7.0):
8740
+ optionalDependencies:
8741
+ '@types/node': 24.7.0
8742
+
8743
+ strnum@2.1.2: {}
8744
+
8745
  style-to-js@1.1.18:
8746
  dependencies:
8747
  style-to-object: 1.0.11
 
8752
 
8753
  stylis@4.3.6: {}
8754
 
8755
+ superjson@1.13.3:
8756
+ dependencies:
8757
+ copy-anything: 3.0.5
8758
+
8759
  tailwind-merge@3.3.1: {}
8760
 
8761
  tailwindcss-animate@1.0.7(tailwindcss@4.1.14):
 
8817
  media-typer: 0.3.0
8818
  mime-types: 2.1.35
8819
 
8820
+ typescript@5.9.3: {}
8821
 
8822
  ufo@1.6.1: {}
8823
 
server/_core/context.ts ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
2
+ import type { User } from "../../drizzle/schema";
3
+ import { sdk } from "./sdk";
4
+
5
+ export type TrpcContext = {
6
+ req: CreateExpressContextOptions["req"];
7
+ res: CreateExpressContextOptions["res"];
8
+ user: User | null;
9
+ };
10
+
11
+ export async function createContext(
12
+ opts: CreateExpressContextOptions
13
+ ): Promise<TrpcContext> {
14
+ let user: User | null = null;
15
+
16
+ try {
17
+ user = await sdk.authenticateRequest(opts.req);
18
+ } catch (error) {
19
+ // Authentication is optional for public procedures.
20
+ user = null;
21
+ }
22
+
23
+ return {
24
+ req: opts.req,
25
+ res: opts.res,
26
+ user,
27
+ };
28
+ }
server/_core/cookies.ts ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { CookieOptions, Request } from "express";
2
+
3
+ const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
4
+
5
+ function isIpAddress(host: string) {
6
+ // Basic IPv4 check and IPv6 presence detection.
7
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
8
+ return host.includes(":");
9
+ }
10
+
11
+ function isSecureRequest(req: Request) {
12
+ if (req.protocol === "https") return true;
13
+
14
+ const forwardedProto = req.headers["x-forwarded-proto"];
15
+ if (!forwardedProto) return false;
16
+
17
+ const protoList = Array.isArray(forwardedProto)
18
+ ? forwardedProto
19
+ : forwardedProto.split(",");
20
+
21
+ return protoList.some(proto => proto.trim().toLowerCase() === "https");
22
+ }
23
+
24
+ export function getSessionCookieOptions(
25
+ req: Request
26
+ ): Pick<CookieOptions, "domain" | "httpOnly" | "path" | "sameSite" | "secure"> {
27
+ // const hostname = req.hostname;
28
+ // const shouldSetDomain =
29
+ // hostname &&
30
+ // !LOCAL_HOSTS.has(hostname) &&
31
+ // !isIpAddress(hostname) &&
32
+ // hostname !== "127.0.0.1" &&
33
+ // hostname !== "::1";
34
+
35
+ // const domain =
36
+ // shouldSetDomain && !hostname.startsWith(".")
37
+ // ? `.${hostname}`
38
+ // : shouldSetDomain
39
+ // ? hostname
40
+ // : undefined;
41
+
42
+ return {
43
+ httpOnly: true,
44
+ path: "/",
45
+ sameSite: "none",
46
+ secure: isSecureRequest(req),
47
+ };
48
+ }
server/_core/dataApi.ts ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Quick example (matches curl usage):
3
+ * await callDataApi("Youtube/search", {
4
+ * query: { gl: "US", hl: "en", q: "manus" },
5
+ * })
6
+ */
7
+ import { ENV } from "./env";
8
+
9
+ export type DataApiCallOptions = {
10
+ query?: Record<string, unknown>;
11
+ body?: Record<string, unknown>;
12
+ pathParams?: Record<string, unknown>;
13
+ formData?: Record<string, unknown>;
14
+ };
15
+
16
+ export async function callDataApi(
17
+ apiId: string,
18
+ options: DataApiCallOptions = {}
19
+ ): Promise<unknown> {
20
+ if (!ENV.forgeApiUrl) {
21
+ throw new Error("BUILT_IN_FORGE_API_URL is not configured");
22
+ }
23
+ if (!ENV.forgeApiKey) {
24
+ throw new Error("BUILT_IN_FORGE_API_KEY is not configured");
25
+ }
26
+
27
+ // Build the full URL by appending the service path to the base URL
28
+ const baseUrl = ENV.forgeApiUrl.endsWith("/") ? ENV.forgeApiUrl : `${ENV.forgeApiUrl}/`;
29
+ const fullUrl = new URL("webdevtoken.v1.WebDevService/CallApi", baseUrl).toString();
30
+
31
+ const response = await fetch(fullUrl, {
32
+ method: "POST",
33
+ headers: {
34
+ accept: "application/json",
35
+ "content-type": "application/json",
36
+ "connect-protocol-version": "1",
37
+ authorization: `Bearer ${ENV.forgeApiKey}`,
38
+ },
39
+ body: JSON.stringify({
40
+ apiId,
41
+ query: options.query,
42
+ body: options.body,
43
+ path_params: options.pathParams,
44
+ multipart_form_data: options.formData,
45
+ }),
46
+ });
47
+
48
+ if (!response.ok) {
49
+ const detail = await response.text().catch(() => "");
50
+ throw new Error(
51
+ `Data API request failed (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`
52
+ );
53
+ }
54
+
55
+ const payload = await response.json().catch(() => ({}));
56
+ if (payload && typeof payload === "object" && "jsonData" in payload) {
57
+ try {
58
+ return JSON.parse((payload as Record<string, string>).jsonData ?? "{}");
59
+ } catch {
60
+ return (payload as Record<string, unknown>).jsonData;
61
+ }
62
+ }
63
+ return payload;
64
+ }
server/_core/env.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const ENV = {
2
+ appId: process.env.VITE_APP_ID ?? "",
3
+ cookieSecret: process.env.JWT_SECRET ?? "",
4
+ databaseUrl: process.env.DATABASE_URL ?? "",
5
+ oAuthServerUrl: process.env.OAUTH_SERVER_URL ?? "",
6
+ ownerOpenId: process.env.OWNER_OPEN_ID ?? "",
7
+ isProduction: process.env.NODE_ENV === "production",
8
+ forgeApiUrl: process.env.BUILT_IN_FORGE_API_URL ?? "",
9
+ forgeApiKey: process.env.BUILT_IN_FORGE_API_KEY ?? "",
10
+ stripeSecretKey: process.env.STRIPE_SECRET_KEY ?? "",
11
+ stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET ?? "",
12
+ };
server/_core/imageGeneration.ts ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Image generation helper using internal ImageService
3
+ *
4
+ * Example usage:
5
+ * const { url: imageUrl } = await generateImage({
6
+ * prompt: "A serene landscape with mountains"
7
+ * });
8
+ *
9
+ * For editing:
10
+ * const { url: imageUrl } = await generateImage({
11
+ * prompt: "Add a rainbow to this landscape",
12
+ * originalImages: [{
13
+ * url: "https://example.com/original.jpg",
14
+ * mimeType: "image/jpeg"
15
+ * }]
16
+ * });
17
+ */
18
+ import { storagePut } from "server/storage";
19
+ import { ENV } from "./env";
20
+
21
+ export type GenerateImageOptions = {
22
+ prompt: string;
23
+ originalImages?: Array<{
24
+ url?: string;
25
+ b64Json?: string;
26
+ mimeType?: string;
27
+ }>;
28
+ };
29
+
30
+ export type GenerateImageResponse = {
31
+ url?: string;
32
+ };
33
+
34
+ export async function generateImage(
35
+ options: GenerateImageOptions
36
+ ): Promise<GenerateImageResponse> {
37
+ if (!ENV.forgeApiUrl) {
38
+ throw new Error("BUILT_IN_FORGE_API_URL is not configured");
39
+ }
40
+ if (!ENV.forgeApiKey) {
41
+ throw new Error("BUILT_IN_FORGE_API_KEY is not configured");
42
+ }
43
+
44
+ // Build the full URL by appending the service path to the base URL
45
+ const baseUrl = ENV.forgeApiUrl.endsWith("/")
46
+ ? ENV.forgeApiUrl
47
+ : `${ENV.forgeApiUrl}/`;
48
+ const fullUrl = new URL(
49
+ "images.v1.ImageService/GenerateImage",
50
+ baseUrl
51
+ ).toString();
52
+
53
+ const response = await fetch(fullUrl, {
54
+ method: "POST",
55
+ headers: {
56
+ accept: "application/json",
57
+ "content-type": "application/json",
58
+ "connect-protocol-version": "1",
59
+ authorization: `Bearer ${ENV.forgeApiKey}`,
60
+ },
61
+ body: JSON.stringify({
62
+ prompt: options.prompt,
63
+ original_images: options.originalImages || [],
64
+ }),
65
+ });
66
+
67
+ if (!response.ok) {
68
+ const detail = await response.text().catch(() => "");
69
+ throw new Error(
70
+ `Image generation request failed (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`
71
+ );
72
+ }
73
+
74
+ const result = (await response.json()) as {
75
+ image: {
76
+ b64Json: string;
77
+ mimeType: string;
78
+ };
79
+ };
80
+ const base64Data = result.image.b64Json;
81
+ const buffer = Buffer.from(base64Data, "base64");
82
+
83
+ // Save to S3
84
+ const { url } = await storagePut(
85
+ `generated/${Date.now()}.png`,
86
+ buffer,
87
+ result.image.mimeType
88
+ );
89
+ return {
90
+ url,
91
+ };
92
+ }
server/_core/index.ts ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "dotenv/config";
2
+ import express from "express";
3
+ import { createServer } from "http";
4
+ import net from "net";
5
+ import { createExpressMiddleware } from "@trpc/server/adapters/express";
6
+ import { registerOAuthRoutes } from "./oauth";
7
+ import { appRouter } from "../routers";
8
+ import { createContext } from "./context";
9
+ import { serveStatic, setupVite } from "./vite";
10
+ import { handleStripeWebhook } from "../webhooks/stripe";
11
+
12
+ function isPortAvailable(port: number): Promise<boolean> {
13
+ return new Promise(resolve => {
14
+ const server = net.createServer();
15
+ server.listen(port, () => {
16
+ server.close(() => resolve(true));
17
+ });
18
+ server.on("error", () => resolve(false));
19
+ });
20
+ }
21
+
22
+ async function findAvailablePort(startPort: number = 3000): Promise<number> {
23
+ for (let port = startPort; port < startPort + 20; port++) {
24
+ if (await isPortAvailable(port)) {
25
+ return port;
26
+ }
27
+ }
28
+ throw new Error(`No available port found starting from ${startPort}`);
29
+ }
30
+
31
+ async function startServer() {
32
+ const app = express();
33
+ const server = createServer(app);
34
+
35
+ // Stripe webhook must be registered BEFORE express.json() to access raw body
36
+ app.post('/api/stripe/webhook', express.raw({ type: 'application/json' }), handleStripeWebhook);
37
+
38
+ // Configure body parser with larger size limit for file uploads
39
+ app.use(express.json({ limit: "50mb" }));
40
+ app.use(express.urlencoded({ limit: "50mb", extended: true }));
41
+ // OAuth callback under /api/oauth/callback
42
+ registerOAuthRoutes(app);
43
+ // tRPC API
44
+ app.use(
45
+ "/api/trpc",
46
+ createExpressMiddleware({
47
+ router: appRouter,
48
+ createContext,
49
+ })
50
+ );
51
+ // development mode uses Vite, production mode uses static files
52
+ if (process.env.NODE_ENV === "development") {
53
+ await setupVite(app, server);
54
+ } else {
55
+ serveStatic(app);
56
+ }
57
+
58
+ const preferredPort = parseInt(process.env.PORT || "3000");
59
+ const port = await findAvailablePort(preferredPort);
60
+
61
+ if (port !== preferredPort) {
62
+ console.log(`Port ${preferredPort} is busy, using port ${port} instead`);
63
+ }
64
+
65
+ server.listen(port, () => {
66
+ console.log(`Server running on http://localhost:${port}/`);
67
+ });
68
+ }
69
+
70
+ startServer().catch(console.error);
server/_core/llm.ts ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ENV } from "./env";
2
+
3
+ export type Role = "system" | "user" | "assistant" | "tool" | "function";
4
+
5
+ export type TextContent = {
6
+ type: "text";
7
+ text: string;
8
+ };
9
+
10
+ export type ImageContent = {
11
+ type: "image_url";
12
+ image_url: {
13
+ url: string;
14
+ detail?: "auto" | "low" | "high";
15
+ };
16
+ };
17
+
18
+ export type FileContent = {
19
+ type: "file_url";
20
+ file_url: {
21
+ url: string;
22
+ mime_type?: "audio/mpeg" | "audio/wav" | "application/pdf" | "audio/mp4" | "video/mp4" ;
23
+ };
24
+ };
25
+
26
+ export type MessageContent = string | TextContent | ImageContent | FileContent;
27
+
28
+ export type Message = {
29
+ role: Role;
30
+ content: MessageContent | MessageContent[];
31
+ name?: string;
32
+ tool_call_id?: string;
33
+ };
34
+
35
+ export type Tool = {
36
+ type: "function";
37
+ function: {
38
+ name: string;
39
+ description?: string;
40
+ parameters?: Record<string, unknown>;
41
+ };
42
+ };
43
+
44
+ export type ToolChoicePrimitive = "none" | "auto" | "required";
45
+ export type ToolChoiceByName = { name: string };
46
+ export type ToolChoiceExplicit = {
47
+ type: "function";
48
+ function: {
49
+ name: string;
50
+ };
51
+ };
52
+
53
+ export type ToolChoice =
54
+ | ToolChoicePrimitive
55
+ | ToolChoiceByName
56
+ | ToolChoiceExplicit;
57
+
58
+ export type InvokeParams = {
59
+ messages: Message[];
60
+ tools?: Tool[];
61
+ toolChoice?: ToolChoice;
62
+ tool_choice?: ToolChoice;
63
+ maxTokens?: number;
64
+ max_tokens?: number;
65
+ outputSchema?: OutputSchema;
66
+ output_schema?: OutputSchema;
67
+ responseFormat?: ResponseFormat;
68
+ response_format?: ResponseFormat;
69
+ };
70
+
71
+ export type ToolCall = {
72
+ id: string;
73
+ type: "function";
74
+ function: {
75
+ name: string;
76
+ arguments: string;
77
+ };
78
+ };
79
+
80
+ export type InvokeResult = {
81
+ id: string;
82
+ created: number;
83
+ model: string;
84
+ choices: Array<{
85
+ index: number;
86
+ message: {
87
+ role: Role;
88
+ content: string | Array<TextContent | ImageContent | FileContent>;
89
+ tool_calls?: ToolCall[];
90
+ };
91
+ finish_reason: string | null;
92
+ }>;
93
+ usage?: {
94
+ prompt_tokens: number;
95
+ completion_tokens: number;
96
+ total_tokens: number;
97
+ };
98
+ };
99
+
100
+ export type JsonSchema = {
101
+ name: string;
102
+ schema: Record<string, unknown>;
103
+ strict?: boolean;
104
+ };
105
+
106
+ export type OutputSchema = JsonSchema;
107
+
108
+ export type ResponseFormat =
109
+ | { type: "text" }
110
+ | { type: "json_object" }
111
+ | { type: "json_schema"; json_schema: JsonSchema };
112
+
113
+ const ensureArray = (
114
+ value: MessageContent | MessageContent[]
115
+ ): MessageContent[] => (Array.isArray(value) ? value : [value]);
116
+
117
+ const normalizeContentPart = (
118
+ part: MessageContent
119
+ ): TextContent | ImageContent | FileContent => {
120
+ if (typeof part === "string") {
121
+ return { type: "text", text: part };
122
+ }
123
+
124
+ if (part.type === "text") {
125
+ return part;
126
+ }
127
+
128
+ if (part.type === "image_url") {
129
+ return part;
130
+ }
131
+
132
+ if (part.type === "file_url") {
133
+ return part;
134
+ }
135
+
136
+ throw new Error("Unsupported message content part");
137
+ };
138
+
139
+ const normalizeMessage = (message: Message) => {
140
+ const { role, name, tool_call_id } = message;
141
+
142
+ if (role === "tool" || role === "function") {
143
+ const content = ensureArray(message.content)
144
+ .map(part => (typeof part === "string" ? part : JSON.stringify(part)))
145
+ .join("\n");
146
+
147
+ return {
148
+ role,
149
+ name,
150
+ tool_call_id,
151
+ content,
152
+ };
153
+ }
154
+
155
+ const contentParts = ensureArray(message.content).map(normalizeContentPart);
156
+
157
+ // If there's only text content, collapse to a single string for compatibility
158
+ if (contentParts.length === 1 && contentParts[0].type === "text") {
159
+ return {
160
+ role,
161
+ name,
162
+ content: contentParts[0].text,
163
+ };
164
+ }
165
+
166
+ return {
167
+ role,
168
+ name,
169
+ content: contentParts,
170
+ };
171
+ };
172
+
173
+ const normalizeToolChoice = (
174
+ toolChoice: ToolChoice | undefined,
175
+ tools: Tool[] | undefined
176
+ ): "none" | "auto" | ToolChoiceExplicit | undefined => {
177
+ if (!toolChoice) return undefined;
178
+
179
+ if (toolChoice === "none" || toolChoice === "auto") {
180
+ return toolChoice;
181
+ }
182
+
183
+ if (toolChoice === "required") {
184
+ if (!tools || tools.length === 0) {
185
+ throw new Error(
186
+ "tool_choice 'required' was provided but no tools were configured"
187
+ );
188
+ }
189
+
190
+ if (tools.length > 1) {
191
+ throw new Error(
192
+ "tool_choice 'required' needs a single tool or specify the tool name explicitly"
193
+ );
194
+ }
195
+
196
+ return {
197
+ type: "function",
198
+ function: { name: tools[0].function.name },
199
+ };
200
+ }
201
+
202
+ if ("name" in toolChoice) {
203
+ return {
204
+ type: "function",
205
+ function: { name: toolChoice.name },
206
+ };
207
+ }
208
+
209
+ return toolChoice;
210
+ };
211
+
212
+ const resolveApiUrl = () =>
213
+ ENV.forgeApiUrl && ENV.forgeApiUrl.trim().length > 0
214
+ ? `${ENV.forgeApiUrl.replace(/\/$/, "")}/v1/chat/completions`
215
+ : "https://forge.manus.im/v1/chat/completions";
216
+
217
+ const assertApiKey = () => {
218
+ if (!ENV.forgeApiKey) {
219
+ throw new Error("OPENAI_API_KEY is not configured");
220
+ }
221
+ };
222
+
223
+ const normalizeResponseFormat = ({
224
+ responseFormat,
225
+ response_format,
226
+ outputSchema,
227
+ output_schema,
228
+ }: {
229
+ responseFormat?: ResponseFormat;
230
+ response_format?: ResponseFormat;
231
+ outputSchema?: OutputSchema;
232
+ output_schema?: OutputSchema;
233
+ }):
234
+ | { type: "json_schema"; json_schema: JsonSchema }
235
+ | { type: "text" }
236
+ | { type: "json_object" }
237
+ | undefined => {
238
+ const explicitFormat = responseFormat || response_format;
239
+ if (explicitFormat) {
240
+ if (
241
+ explicitFormat.type === "json_schema" &&
242
+ !explicitFormat.json_schema?.schema
243
+ ) {
244
+ throw new Error(
245
+ "responseFormat json_schema requires a defined schema object"
246
+ );
247
+ }
248
+ return explicitFormat;
249
+ }
250
+
251
+ const schema = outputSchema || output_schema;
252
+ if (!schema) return undefined;
253
+
254
+ if (!schema.name || !schema.schema) {
255
+ throw new Error("outputSchema requires both name and schema");
256
+ }
257
+
258
+ return {
259
+ type: "json_schema",
260
+ json_schema: {
261
+ name: schema.name,
262
+ schema: schema.schema,
263
+ ...(typeof schema.strict === "boolean" ? { strict: schema.strict } : {}),
264
+ },
265
+ };
266
+ };
267
+
268
+ export async function invokeLLM(params: InvokeParams): Promise<InvokeResult> {
269
+ assertApiKey();
270
+
271
+ const {
272
+ messages,
273
+ tools,
274
+ toolChoice,
275
+ tool_choice,
276
+ outputSchema,
277
+ output_schema,
278
+ responseFormat,
279
+ response_format,
280
+ } = params;
281
+
282
+ const payload: Record<string, unknown> = {
283
+ model: "gemini-2.5-flash",
284
+ messages: messages.map(normalizeMessage),
285
+ };
286
+
287
+ if (tools && tools.length > 0) {
288
+ payload.tools = tools;
289
+ }
290
+
291
+ const normalizedToolChoice = normalizeToolChoice(
292
+ toolChoice || tool_choice,
293
+ tools
294
+ );
295
+ if (normalizedToolChoice) {
296
+ payload.tool_choice = normalizedToolChoice;
297
+ }
298
+
299
+ payload.max_tokens = 32768
300
+ payload.thinking = {
301
+ "budget_tokens": 128
302
+ }
303
+
304
+ const normalizedResponseFormat = normalizeResponseFormat({
305
+ responseFormat,
306
+ response_format,
307
+ outputSchema,
308
+ output_schema,
309
+ });
310
+
311
+ if (normalizedResponseFormat) {
312
+ payload.response_format = normalizedResponseFormat;
313
+ }
314
+
315
+ const response = await fetch(resolveApiUrl(), {
316
+ method: "POST",
317
+ headers: {
318
+ "content-type": "application/json",
319
+ authorization: `Bearer ${ENV.forgeApiKey}`,
320
+ },
321
+ body: JSON.stringify(payload),
322
+ });
323
+
324
+ if (!response.ok) {
325
+ const errorText = await response.text();
326
+ throw new Error(
327
+ `LLM invoke failed: ${response.status} ${response.statusText} – ${errorText}`
328
+ );
329
+ }
330
+
331
+ return (await response.json()) as InvokeResult;
332
+ }
server/_core/map.ts ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Google Maps API Integration for Manus WebDev Templates
3
+ *
4
+ * Main function: makeRequest<T>(endpoint, params) - Makes authenticated requests to Google Maps APIs
5
+ * All credentials are automatically injected. Array parameters use | as separator.
6
+ *
7
+ * See API examples below the type definitions for usage patterns.
8
+ */
9
+
10
+ import { ENV } from "./env";
11
+
12
+ // ============================================================================
13
+ // Configuration
14
+ // ============================================================================
15
+
16
+ type MapsConfig = {
17
+ baseUrl: string;
18
+ apiKey: string;
19
+ };
20
+
21
+ function getMapsConfig(): MapsConfig {
22
+ const baseUrl = ENV.forgeApiUrl;
23
+ const apiKey = ENV.forgeApiKey;
24
+
25
+ if (!baseUrl || !apiKey) {
26
+ throw new Error(
27
+ "Google Maps proxy credentials missing: set BUILT_IN_FORGE_API_URL and BUILT_IN_FORGE_API_KEY"
28
+ );
29
+ }
30
+
31
+ return {
32
+ baseUrl: baseUrl.replace(/\/+$/, ""),
33
+ apiKey,
34
+ };
35
+ }
36
+
37
+ // ============================================================================
38
+ // Core Request Handler
39
+ // ============================================================================
40
+
41
+ interface RequestOptions {
42
+ method?: "GET" | "POST";
43
+ body?: Record<string, unknown>;
44
+ }
45
+
46
+ /**
47
+ * Make authenticated requests to Google Maps APIs
48
+ *
49
+ * @param endpoint - The API endpoint (e.g., "/maps/api/geocode/json")
50
+ * @param params - Query parameters for the request
51
+ * @param options - Additional request options
52
+ * @returns The API response
53
+ */
54
+ export async function makeRequest<T = unknown>(
55
+ endpoint: string,
56
+ params: Record<string, unknown> = {},
57
+ options: RequestOptions = {}
58
+ ): Promise<T> {
59
+ const { baseUrl, apiKey } = getMapsConfig();
60
+
61
+ // Construct full URL: baseUrl + /v1/maps/proxy + endpoint
62
+ const url = new URL(`${baseUrl}/v1/maps/proxy${endpoint}`);
63
+
64
+ // Add API key as query parameter (standard Google Maps API authentication)
65
+ url.searchParams.append("key", apiKey);
66
+
67
+ // Add other query parameters
68
+ Object.entries(params).forEach(([key, value]) => {
69
+ if (value !== undefined && value !== null) {
70
+ url.searchParams.append(key, String(value));
71
+ }
72
+ });
73
+
74
+ const response = await fetch(url.toString(), {
75
+ method: options.method || "GET",
76
+ headers: {
77
+ "Content-Type": "application/json",
78
+ },
79
+ body: options.body ? JSON.stringify(options.body) : undefined,
80
+ });
81
+
82
+ if (!response.ok) {
83
+ const errorText = await response.text();
84
+ throw new Error(
85
+ `Google Maps API request failed (${response.status} ${response.statusText}): ${errorText}`
86
+ );
87
+ }
88
+
89
+ return (await response.json()) as T;
90
+ }
91
+
92
+ // ============================================================================
93
+ // Type Definitions
94
+ // ============================================================================
95
+
96
+ export type TravelMode = "driving" | "walking" | "bicycling" | "transit";
97
+ export type MapType = "roadmap" | "satellite" | "terrain" | "hybrid";
98
+ export type SpeedUnit = "KPH" | "MPH";
99
+
100
+ export type LatLng = {
101
+ lat: number;
102
+ lng: number;
103
+ };
104
+
105
+ export type DirectionsResult = {
106
+ routes: Array<{
107
+ legs: Array<{
108
+ distance: { text: string; value: number };
109
+ duration: { text: string; value: number };
110
+ start_address: string;
111
+ end_address: string;
112
+ start_location: LatLng;
113
+ end_location: LatLng;
114
+ steps: Array<{
115
+ distance: { text: string; value: number };
116
+ duration: { text: string; value: number };
117
+ html_instructions: string;
118
+ travel_mode: string;
119
+ start_location: LatLng;
120
+ end_location: LatLng;
121
+ }>;
122
+ }>;
123
+ overview_polyline: { points: string };
124
+ summary: string;
125
+ warnings: string[];
126
+ waypoint_order: number[];
127
+ }>;
128
+ status: string;
129
+ };
130
+
131
+ export type DistanceMatrixResult = {
132
+ rows: Array<{
133
+ elements: Array<{
134
+ distance: { text: string; value: number };
135
+ duration: { text: string; value: number };
136
+ status: string;
137
+ }>;
138
+ }>;
139
+ origin_addresses: string[];
140
+ destination_addresses: string[];
141
+ status: string;
142
+ };
143
+
144
+ export type GeocodingResult = {
145
+ results: Array<{
146
+ address_components: Array<{
147
+ long_name: string;
148
+ short_name: string;
149
+ types: string[];
150
+ }>;
151
+ formatted_address: string;
152
+ geometry: {
153
+ location: LatLng;
154
+ location_type: string;
155
+ viewport: {
156
+ northeast: LatLng;
157
+ southwest: LatLng;
158
+ };
159
+ };
160
+ place_id: string;
161
+ types: string[];
162
+ }>;
163
+ status: string;
164
+ };
165
+
166
+ export type PlacesSearchResult = {
167
+ results: Array<{
168
+ place_id: string;
169
+ name: string;
170
+ formatted_address: string;
171
+ geometry: {
172
+ location: LatLng;
173
+ };
174
+ rating?: number;
175
+ user_ratings_total?: number;
176
+ business_status?: string;
177
+ types: string[];
178
+ }>;
179
+ status: string;
180
+ };
181
+
182
+ export type PlaceDetailsResult = {
183
+ result: {
184
+ place_id: string;
185
+ name: string;
186
+ formatted_address: string;
187
+ formatted_phone_number?: string;
188
+ international_phone_number?: string;
189
+ website?: string;
190
+ rating?: number;
191
+ user_ratings_total?: number;
192
+ reviews?: Array<{
193
+ author_name: string;
194
+ rating: number;
195
+ text: string;
196
+ time: number;
197
+ }>;
198
+ opening_hours?: {
199
+ open_now: boolean;
200
+ weekday_text: string[];
201
+ };
202
+ geometry: {
203
+ location: LatLng;
204
+ };
205
+ };
206
+ status: string;
207
+ };
208
+
209
+ export type ElevationResult = {
210
+ results: Array<{
211
+ elevation: number;
212
+ location: LatLng;
213
+ resolution: number;
214
+ }>;
215
+ status: string;
216
+ };
217
+
218
+ export type TimeZoneResult = {
219
+ dstOffset: number;
220
+ rawOffset: number;
221
+ status: string;
222
+ timeZoneId: string;
223
+ timeZoneName: string;
224
+ };
225
+
226
+ export type RoadsResult = {
227
+ snappedPoints: Array<{
228
+ location: LatLng;
229
+ originalIndex?: number;
230
+ placeId: string;
231
+ }>;
232
+ };
233
+
234
+ // ============================================================================
235
+ // Google Maps API Reference
236
+ // ============================================================================
237
+
238
+ /**
239
+ * GEOCODING - Convert between addresses and coordinates
240
+ * Endpoint: /maps/api/geocode/json
241
+ * Input: { address: string } OR { latlng: string } // latlng: "37.42,-122.08"
242
+ * Output: GeocodingResult // results[0].geometry.location, results[0].formatted_address
243
+ */
244
+
245
+ /**
246
+ * DIRECTIONS - Get navigation routes between locations
247
+ * Endpoint: /maps/api/directions/json
248
+ * Input: { origin: string, destination: string, mode?: TravelMode, waypoints?: string, alternatives?: boolean }
249
+ * Output: DirectionsResult // routes[0].legs[0].distance, duration, steps
250
+ */
251
+
252
+ /**
253
+ * DISTANCE MATRIX - Calculate travel times/distances for multiple origin-destination pairs
254
+ * Endpoint: /maps/api/distancematrix/json
255
+ * Input: { origins: string, destinations: string, mode?: TravelMode, units?: "metric"|"imperial" } // origins: "NYC|Boston"
256
+ * Output: DistanceMatrixResult // rows[0].elements[1] = first origin to second destination
257
+ */
258
+
259
+ /**
260
+ * PLACE SEARCH - Find businesses/POIs by text query
261
+ * Endpoint: /maps/api/place/textsearch/json
262
+ * Input: { query: string, location?: string, radius?: number, type?: string } // location: "40.7,-74.0"
263
+ * Output: PlacesSearchResult // results[].name, rating, geometry.location, place_id
264
+ */
265
+
266
+ /**
267
+ * NEARBY SEARCH - Find places near a specific location
268
+ * Endpoint: /maps/api/place/nearbysearch/json
269
+ * Input: { location: string, radius: number, type?: string, keyword?: string } // location: "40.7,-74.0"
270
+ * Output: PlacesSearchResult
271
+ */
272
+
273
+ /**
274
+ * PLACE DETAILS - Get comprehensive information about a specific place
275
+ * Endpoint: /maps/api/place/details/json
276
+ * Input: { place_id: string, fields?: string } // fields: "name,rating,opening_hours,website"
277
+ * Output: PlaceDetailsResult // result.name, rating, opening_hours, etc.
278
+ */
279
+
280
+ /**
281
+ * ELEVATION - Get altitude data for geographic points
282
+ * Endpoint: /maps/api/elevation/json
283
+ * Input: { locations?: string, path?: string, samples?: number } // locations: "39.73,-104.98|36.45,-116.86"
284
+ * Output: ElevationResult // results[].elevation (meters)
285
+ */
286
+
287
+ /**
288
+ * TIME ZONE - Get timezone information for a location
289
+ * Endpoint: /maps/api/timezone/json
290
+ * Input: { location: string, timestamp: number } // timestamp: Math.floor(Date.now()/1000)
291
+ * Output: TimeZoneResult // timeZoneId, timeZoneName
292
+ */
293
+
294
+ /**
295
+ * ROADS - Snap GPS traces to roads, find nearest roads, get speed limits
296
+ * - /v1/snapToRoads: Input: { path: string, interpolate?: boolean } // path: "lat,lng|lat,lng"
297
+ * - /v1/nearestRoads: Input: { points: string } // points: "lat,lng|lat,lng"
298
+ * - /v1/speedLimits: Input: { path: string, units?: SpeedUnit }
299
+ * Output: RoadsResult
300
+ */
301
+
302
+ /**
303
+ * PLACE AUTOCOMPLETE - Real-time place suggestions as user types
304
+ * Endpoint: /maps/api/place/autocomplete/json
305
+ * Input: { input: string, location?: string, radius?: number }
306
+ * Output: { predictions: Array<{ description: string, place_id: string }> }
307
+ */
308
+
309
+ /**
310
+ * STATIC MAPS - Generate map images as URLs (for emails, reports, <img> tags)
311
+ * Endpoint: /maps/api/staticmap
312
+ * Input: URL params - center: string, zoom: number, size: string, markers?: string, maptype?: MapType
313
+ * Output: Image URL (not JSON) - use directly in <img src={url} />
314
+ * Note: Construct URL manually with getMapsConfig() for auth
315
+ */
316
+
317
+
318
+
319
+
server/_core/notification.ts ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { TRPCError } from "@trpc/server";
2
+ import { ENV } from "./env";
3
+
4
+ export type NotificationPayload = {
5
+ title: string;
6
+ content: string;
7
+ };
8
+
9
+ const TITLE_MAX_LENGTH = 1200;
10
+ const CONTENT_MAX_LENGTH = 20000;
11
+
12
+ const trimValue = (value: string): string => value.trim();
13
+ const isNonEmptyString = (value: unknown): value is string =>
14
+ typeof value === "string" && value.trim().length > 0;
15
+
16
+ const buildEndpointUrl = (baseUrl: string): string => {
17
+ const normalizedBase = baseUrl.endsWith("/")
18
+ ? baseUrl
19
+ : `${baseUrl}/`;
20
+ return new URL(
21
+ "webdevtoken.v1.WebDevService/SendNotification",
22
+ normalizedBase
23
+ ).toString();
24
+ };
25
+
26
+ const validatePayload = (input: NotificationPayload): NotificationPayload => {
27
+ if (!isNonEmptyString(input.title)) {
28
+ throw new TRPCError({
29
+ code: "BAD_REQUEST",
30
+ message: "Notification title is required.",
31
+ });
32
+ }
33
+ if (!isNonEmptyString(input.content)) {
34
+ throw new TRPCError({
35
+ code: "BAD_REQUEST",
36
+ message: "Notification content is required.",
37
+ });
38
+ }
39
+
40
+ const title = trimValue(input.title);
41
+ const content = trimValue(input.content);
42
+
43
+ if (title.length > TITLE_MAX_LENGTH) {
44
+ throw new TRPCError({
45
+ code: "BAD_REQUEST",
46
+ message: `Notification title must be at most ${TITLE_MAX_LENGTH} characters.`,
47
+ });
48
+ }
49
+
50
+ if (content.length > CONTENT_MAX_LENGTH) {
51
+ throw new TRPCError({
52
+ code: "BAD_REQUEST",
53
+ message: `Notification content must be at most ${CONTENT_MAX_LENGTH} characters.`,
54
+ });
55
+ }
56
+
57
+ return { title, content };
58
+ };
59
+
60
+ /**
61
+ * Dispatches a project-owner notification through the Manus Notification Service.
62
+ * Returns `true` if the request was accepted, `false` when the upstream service
63
+ * cannot be reached (callers can fall back to email/slack). Validation errors
64
+ * bubble up as TRPC errors so callers can fix the payload.
65
+ */
66
+ export async function notifyOwner(
67
+ payload: NotificationPayload
68
+ ): Promise<boolean> {
69
+ const { title, content } = validatePayload(payload);
70
+
71
+ if (!ENV.forgeApiUrl) {
72
+ throw new TRPCError({
73
+ code: "INTERNAL_SERVER_ERROR",
74
+ message: "Notification service URL is not configured.",
75
+ });
76
+ }
77
+
78
+ if (!ENV.forgeApiKey) {
79
+ throw new TRPCError({
80
+ code: "INTERNAL_SERVER_ERROR",
81
+ message: "Notification service API key is not configured.",
82
+ });
83
+ }
84
+
85
+ const endpoint = buildEndpointUrl(ENV.forgeApiUrl);
86
+
87
+ try {
88
+ const response = await fetch(endpoint, {
89
+ method: "POST",
90
+ headers: {
91
+ accept: "application/json",
92
+ authorization: `Bearer ${ENV.forgeApiKey}`,
93
+ "content-type": "application/json",
94
+ "connect-protocol-version": "1",
95
+ },
96
+ body: JSON.stringify({ title, content }),
97
+ });
98
+
99
+ if (!response.ok) {
100
+ const detail = await response.text().catch(() => "");
101
+ console.warn(
102
+ `[Notification] Failed to notify owner (${response.status} ${response.statusText})${
103
+ detail ? `: ${detail}` : ""
104
+ }`
105
+ );
106
+ return false;
107
+ }
108
+
109
+ return true;
110
+ } catch (error) {
111
+ console.warn("[Notification] Error calling notification service:", error);
112
+ return false;
113
+ }
114
+ }
server/_core/oauth.ts ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { COOKIE_NAME, ONE_YEAR_MS } from "@shared/const";
2
+ import type { Express, Request, Response } from "express";
3
+ import * as db from "../db";
4
+ import { getSessionCookieOptions } from "./cookies";
5
+ import { sdk } from "./sdk";
6
+
7
+ function getQueryParam(req: Request, key: string): string | undefined {
8
+ const value = req.query[key];
9
+ return typeof value === "string" ? value : undefined;
10
+ }
11
+
12
+ export function registerOAuthRoutes(app: Express) {
13
+ app.get("/api/oauth/callback", async (req: Request, res: Response) => {
14
+ const code = getQueryParam(req, "code");
15
+ const state = getQueryParam(req, "state");
16
+
17
+ if (!code || !state) {
18
+ res.status(400).json({ error: "code and state are required" });
19
+ return;
20
+ }
21
+
22
+ try {
23
+ const tokenResponse = await sdk.exchangeCodeForToken(code, state);
24
+ const userInfo = await sdk.getUserInfo(tokenResponse.accessToken);
25
+
26
+ if (!userInfo.openId) {
27
+ res.status(400).json({ error: "openId missing from user info" });
28
+ return;
29
+ }
30
+
31
+ await db.upsertUser({
32
+ openId: userInfo.openId,
33
+ name: userInfo.name || null,
34
+ email: userInfo.email ?? null,
35
+ loginMethod: userInfo.loginMethod ?? userInfo.platform ?? null,
36
+ lastSignedIn: new Date(),
37
+ });
38
+
39
+ const sessionToken = await sdk.createSessionToken(userInfo.openId, {
40
+ name: userInfo.name || "",
41
+ expiresInMs: ONE_YEAR_MS,
42
+ });
43
+
44
+ const cookieOptions = getSessionCookieOptions(req);
45
+ res.cookie(COOKIE_NAME, sessionToken, { ...cookieOptions, maxAge: ONE_YEAR_MS });
46
+
47
+ res.redirect(302, "/");
48
+ } catch (error) {
49
+ console.error("[OAuth] Callback failed", error);
50
+ res.status(500).json({ error: "OAuth callback failed" });
51
+ }
52
+ });
53
+ }
server/_core/sdk.ts ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AXIOS_TIMEOUT_MS, COOKIE_NAME, ONE_YEAR_MS } from "@shared/const";
2
+ import { ForbiddenError } from "@shared/_core/errors";
3
+ import axios, { type AxiosInstance } from "axios";
4
+ import { parse as parseCookieHeader } from "cookie";
5
+ import type { Request } from "express";
6
+ import { SignJWT, jwtVerify } from "jose";
7
+ import type { User } from "../../drizzle/schema";
8
+ import * as db from "../db";
9
+ import { ENV } from "./env";
10
+ import type {
11
+ ExchangeTokenRequest,
12
+ ExchangeTokenResponse,
13
+ GetUserInfoResponse,
14
+ GetUserInfoWithJwtRequest,
15
+ GetUserInfoWithJwtResponse,
16
+ } from "./types/manusTypes";
17
+ // Utility function
18
+ const isNonEmptyString = (value: unknown): value is string =>
19
+ typeof value === "string" && value.length > 0;
20
+
21
+ export type SessionPayload = {
22
+ openId: string;
23
+ appId: string;
24
+ name: string;
25
+ };
26
+
27
+ const EXCHANGE_TOKEN_PATH = `/webdev.v1.WebDevAuthPublicService/ExchangeToken`;
28
+ const GET_USER_INFO_PATH = `/webdev.v1.WebDevAuthPublicService/GetUserInfo`;
29
+ const GET_USER_INFO_WITH_JWT_PATH = `/webdev.v1.WebDevAuthPublicService/GetUserInfoWithJwt`;
30
+
31
+ class OAuthService {
32
+ constructor(private client: ReturnType<typeof axios.create>) {
33
+ console.log("[OAuth] Initialized with baseURL:", ENV.oAuthServerUrl);
34
+ if (!ENV.oAuthServerUrl) {
35
+ console.error(
36
+ "[OAuth] ERROR: OAUTH_SERVER_URL is not configured! Set OAUTH_SERVER_URL environment variable."
37
+ );
38
+ }
39
+ }
40
+
41
+ private decodeState(state: string): string {
42
+ const redirectUri = atob(state);
43
+ return redirectUri;
44
+ }
45
+
46
+ async getTokenByCode(
47
+ code: string,
48
+ state: string
49
+ ): Promise<ExchangeTokenResponse> {
50
+ const payload: ExchangeTokenRequest = {
51
+ clientId: ENV.appId,
52
+ grantType: "authorization_code",
53
+ code,
54
+ redirectUri: this.decodeState(state),
55
+ };
56
+
57
+ const { data } = await this.client.post<ExchangeTokenResponse>(
58
+ EXCHANGE_TOKEN_PATH,
59
+ payload
60
+ );
61
+
62
+ return data;
63
+ }
64
+
65
+ async getUserInfoByToken(
66
+ token: ExchangeTokenResponse
67
+ ): Promise<GetUserInfoResponse> {
68
+ const { data } = await this.client.post<GetUserInfoResponse>(
69
+ GET_USER_INFO_PATH,
70
+ {
71
+ accessToken: token.accessToken,
72
+ }
73
+ );
74
+
75
+ return data;
76
+ }
77
+ }
78
+
79
+ const createOAuthHttpClient = (): AxiosInstance =>
80
+ axios.create({
81
+ baseURL: ENV.oAuthServerUrl,
82
+ timeout: AXIOS_TIMEOUT_MS,
83
+ });
84
+
85
+ class SDKServer {
86
+ private readonly client: AxiosInstance;
87
+ private readonly oauthService: OAuthService;
88
+
89
+ constructor(client: AxiosInstance = createOAuthHttpClient()) {
90
+ this.client = client;
91
+ this.oauthService = new OAuthService(this.client);
92
+ }
93
+
94
+ private deriveLoginMethod(
95
+ platforms: unknown,
96
+ fallback: string | null | undefined
97
+ ): string | null {
98
+ if (fallback && fallback.length > 0) return fallback;
99
+ if (!Array.isArray(platforms) || platforms.length === 0) return null;
100
+ const set = new Set<string>(
101
+ platforms.filter((p): p is string => typeof p === "string")
102
+ );
103
+ if (set.has("REGISTERED_PLATFORM_EMAIL")) return "email";
104
+ if (set.has("REGISTERED_PLATFORM_GOOGLE")) return "google";
105
+ if (set.has("REGISTERED_PLATFORM_APPLE")) return "apple";
106
+ if (
107
+ set.has("REGISTERED_PLATFORM_MICROSOFT") ||
108
+ set.has("REGISTERED_PLATFORM_AZURE")
109
+ )
110
+ return "microsoft";
111
+ if (set.has("REGISTERED_PLATFORM_GITHUB")) return "github";
112
+ const first = Array.from(set)[0];
113
+ return first ? first.toLowerCase() : null;
114
+ }
115
+
116
+ /**
117
+ * Exchange OAuth authorization code for access token
118
+ * @example
119
+ * const tokenResponse = await sdk.exchangeCodeForToken(code, state);
120
+ */
121
+ async exchangeCodeForToken(
122
+ code: string,
123
+ state: string
124
+ ): Promise<ExchangeTokenResponse> {
125
+ return this.oauthService.getTokenByCode(code, state);
126
+ }
127
+
128
+ /**
129
+ * Get user information using access token
130
+ * @example
131
+ * const userInfo = await sdk.getUserInfo(tokenResponse.accessToken);
132
+ */
133
+ async getUserInfo(accessToken: string): Promise<GetUserInfoResponse> {
134
+ const data = await this.oauthService.getUserInfoByToken({
135
+ accessToken,
136
+ } as ExchangeTokenResponse);
137
+ const loginMethod = this.deriveLoginMethod(
138
+ (data as any)?.platforms,
139
+ (data as any)?.platform ?? data.platform ?? null
140
+ );
141
+ return {
142
+ ...(data as any),
143
+ platform: loginMethod,
144
+ loginMethod,
145
+ } as GetUserInfoResponse;
146
+ }
147
+
148
+ private parseCookies(cookieHeader: string | undefined) {
149
+ if (!cookieHeader) {
150
+ return new Map<string, string>();
151
+ }
152
+
153
+ const parsed = parseCookieHeader(cookieHeader);
154
+ return new Map(Object.entries(parsed));
155
+ }
156
+
157
+ private getSessionSecret() {
158
+ const secret = ENV.cookieSecret;
159
+ return new TextEncoder().encode(secret);
160
+ }
161
+
162
+ /**
163
+ * Create a session token for a Manus user openId
164
+ * @example
165
+ * const sessionToken = await sdk.createSessionToken(userInfo.openId);
166
+ */
167
+ async createSessionToken(
168
+ openId: string,
169
+ options: { expiresInMs?: number; name?: string } = {}
170
+ ): Promise<string> {
171
+ return this.signSession(
172
+ {
173
+ openId,
174
+ appId: ENV.appId,
175
+ name: options.name || "",
176
+ },
177
+ options
178
+ );
179
+ }
180
+
181
+ async signSession(
182
+ payload: SessionPayload,
183
+ options: { expiresInMs?: number } = {}
184
+ ): Promise<string> {
185
+ const issuedAt = Date.now();
186
+ const expiresInMs = options.expiresInMs ?? ONE_YEAR_MS;
187
+ const expirationSeconds = Math.floor((issuedAt + expiresInMs) / 1000);
188
+ const secretKey = this.getSessionSecret();
189
+
190
+ return new SignJWT({
191
+ openId: payload.openId,
192
+ appId: payload.appId,
193
+ name: payload.name,
194
+ })
195
+ .setProtectedHeader({ alg: "HS256", typ: "JWT" })
196
+ .setExpirationTime(expirationSeconds)
197
+ .sign(secretKey);
198
+ }
199
+
200
+ async verifySession(
201
+ cookieValue: string | undefined | null
202
+ ): Promise<{ openId: string; appId: string; name: string } | null> {
203
+ if (!cookieValue) {
204
+ console.warn("[Auth] Missing session cookie");
205
+ return null;
206
+ }
207
+
208
+ try {
209
+ const secretKey = this.getSessionSecret();
210
+ const { payload } = await jwtVerify(cookieValue, secretKey, {
211
+ algorithms: ["HS256"],
212
+ });
213
+ const { openId, appId, name } = payload as Record<string, unknown>;
214
+
215
+ if (
216
+ !isNonEmptyString(openId) ||
217
+ !isNonEmptyString(appId) ||
218
+ !isNonEmptyString(name)
219
+ ) {
220
+ console.warn("[Auth] Session payload missing required fields");
221
+ return null;
222
+ }
223
+
224
+ return {
225
+ openId,
226
+ appId,
227
+ name,
228
+ };
229
+ } catch (error) {
230
+ console.warn("[Auth] Session verification failed", String(error));
231
+ return null;
232
+ }
233
+ }
234
+
235
+ async getUserInfoWithJwt(
236
+ jwtToken: string
237
+ ): Promise<GetUserInfoWithJwtResponse> {
238
+ const payload: GetUserInfoWithJwtRequest = {
239
+ jwtToken,
240
+ projectId: ENV.appId,
241
+ };
242
+
243
+ const { data } = await this.client.post<GetUserInfoWithJwtResponse>(
244
+ GET_USER_INFO_WITH_JWT_PATH,
245
+ payload
246
+ );
247
+
248
+ const loginMethod = this.deriveLoginMethod(
249
+ (data as any)?.platforms,
250
+ (data as any)?.platform ?? data.platform ?? null
251
+ );
252
+ return {
253
+ ...(data as any),
254
+ platform: loginMethod,
255
+ loginMethod,
256
+ } as GetUserInfoWithJwtResponse;
257
+ }
258
+
259
+ async authenticateRequest(req: Request): Promise<User> {
260
+ // Regular authentication flow
261
+ const cookies = this.parseCookies(req.headers.cookie);
262
+ const sessionCookie = cookies.get(COOKIE_NAME);
263
+ const session = await this.verifySession(sessionCookie);
264
+
265
+ if (!session) {
266
+ throw ForbiddenError("Invalid session cookie");
267
+ }
268
+
269
+ const sessionUserId = session.openId;
270
+ const signedInAt = new Date();
271
+ let user = await db.getUserByOpenId(sessionUserId);
272
+
273
+ // If user not in DB, sync from OAuth server automatically
274
+ if (!user) {
275
+ try {
276
+ const userInfo = await this.getUserInfoWithJwt(sessionCookie ?? "");
277
+ await db.upsertUser({
278
+ openId: userInfo.openId,
279
+ name: userInfo.name || null,
280
+ email: userInfo.email ?? null,
281
+ loginMethod: userInfo.loginMethod ?? userInfo.platform ?? null,
282
+ lastSignedIn: signedInAt,
283
+ });
284
+ user = await db.getUserByOpenId(userInfo.openId);
285
+ } catch (error) {
286
+ console.error("[Auth] Failed to sync user from OAuth:", error);
287
+ throw ForbiddenError("Failed to sync user info");
288
+ }
289
+ }
290
+
291
+ if (!user) {
292
+ throw ForbiddenError("User not found");
293
+ }
294
+
295
+ await db.upsertUser({
296
+ openId: user.openId,
297
+ lastSignedIn: signedInAt,
298
+ });
299
+
300
+ return user;
301
+ }
302
+ }
303
+
304
+ export const sdk = new SDKServer();
server/_core/systemRouter.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { z } from "zod";
2
+ import { notifyOwner } from "./notification";
3
+ import { adminProcedure, publicProcedure, router } from "./trpc";
4
+
5
+ export const systemRouter = router({
6
+ health: publicProcedure
7
+ .input(
8
+ z.object({
9
+ timestamp: z.number().min(0, "timestamp cannot be negative"),
10
+ })
11
+ )
12
+ .query(() => ({
13
+ ok: true,
14
+ })),
15
+
16
+ notifyOwner: adminProcedure
17
+ .input(
18
+ z.object({
19
+ title: z.string().min(1, "title is required"),
20
+ content: z.string().min(1, "content is required"),
21
+ })
22
+ )
23
+ .mutation(async ({ input }) => {
24
+ const delivered = await notifyOwner(input);
25
+ return {
26
+ success: delivered,
27
+ } as const;
28
+ }),
29
+ });
server/_core/trpc.ts ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NOT_ADMIN_ERR_MSG, UNAUTHED_ERR_MSG } from '@shared/const';
2
+ import { initTRPC, TRPCError } from "@trpc/server";
3
+ import superjson from "superjson";
4
+ import type { TrpcContext } from "./context";
5
+
6
+ const t = initTRPC.context<TrpcContext>().create({
7
+ transformer: superjson,
8
+ });
9
+
10
+ export const router = t.router;
11
+ export const publicProcedure = t.procedure;
12
+
13
+ const requireUser = t.middleware(async opts => {
14
+ const { ctx, next } = opts;
15
+
16
+ if (!ctx.user) {
17
+ throw new TRPCError({ code: "UNAUTHORIZED", message: UNAUTHED_ERR_MSG });
18
+ }
19
+
20
+ return next({
21
+ ctx: {
22
+ ...ctx,
23
+ user: ctx.user,
24
+ },
25
+ });
26
+ });
27
+
28
+ export const protectedProcedure = t.procedure.use(requireUser);
29
+
30
+ export const adminProcedure = t.procedure.use(
31
+ t.middleware(async opts => {
32
+ const { ctx, next } = opts;
33
+
34
+ if (!ctx.user || ctx.user.role !== 'admin') {
35
+ throw new TRPCError({ code: "FORBIDDEN", message: NOT_ADMIN_ERR_MSG });
36
+ }
37
+
38
+ return next({
39
+ ctx: {
40
+ ...ctx,
41
+ user: ctx.user,
42
+ },
43
+ });
44
+ }),
45
+ );
server/_core/types/cookie.d.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ declare module "cookie" {
2
+ export function parse(
3
+ str: string,
4
+ options?: Record<string, unknown>
5
+ ): Record<string, string>;
6
+ }
server/_core/types/manusTypes.ts ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // WebDev Auth TypeScript types
2
+ // Auto-generated from protobuf definitions
3
+ // Generated on: 2025-09-24T05:57:57.338Z
4
+
5
+ export interface AuthorizeRequest {
6
+ redirectUri: string;
7
+ projectId: string;
8
+ state: string;
9
+ responseType: string;
10
+ scope: string;
11
+ }
12
+
13
+ export interface AuthorizeResponse {
14
+ redirectUrl: string;
15
+ }
16
+
17
+ export interface ExchangeTokenRequest {
18
+ grantType: string;
19
+ code: string;
20
+ refreshToken?: string;
21
+ clientId: string;
22
+ clientSecret?: string;
23
+ redirectUri: string;
24
+ }
25
+
26
+ export interface ExchangeTokenResponse {
27
+ accessToken: string;
28
+ tokenType: string;
29
+ expiresIn: number;
30
+ refreshToken?: string;
31
+ scope: string;
32
+ idToken: string;
33
+ }
34
+
35
+ export interface GetUserInfoRequest {
36
+ accessToken: string;
37
+ }
38
+
39
+ export interface GetUserInfoResponse {
40
+ openId: string;
41
+ projectId: string;
42
+ name: string;
43
+ email?: string | null;
44
+ platform?: string | null;
45
+ loginMethod?: string | null;
46
+ }
47
+
48
+ export interface CanAccessRequest {
49
+ openId: string;
50
+ projectId: string;
51
+ }
52
+
53
+ export interface CanAccessResponse {
54
+ canAccess: boolean;
55
+ }
56
+
57
+ export interface GetUserInfoWithJwtRequest {
58
+ jwtToken: string;
59
+ projectId: string;
60
+ }
61
+
62
+ export interface GetUserInfoWithJwtResponse {
63
+ openId: string;
64
+ projectId: string;
65
+ name: string;
66
+ email?: string | null;
67
+ platform?: string | null;
68
+ loginMethod?: string | null;
69
+ }
server/_core/vite.ts ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express, { type Express } from "express";
2
+ import fs from "fs";
3
+ import { type Server } from "http";
4
+ import { nanoid } from "nanoid";
5
+ import path from "path";
6
+ import { createServer as createViteServer } from "vite";
7
+ import viteConfig from "../../vite.config";
8
+
9
+ export async function setupVite(app: Express, server: Server) {
10
+ const serverOptions = {
11
+ middlewareMode: true,
12
+ hmr: { server },
13
+ allowedHosts: true as const,
14
+ };
15
+
16
+ const vite = await createViteServer({
17
+ ...viteConfig,
18
+ configFile: false,
19
+ server: serverOptions,
20
+ appType: "custom",
21
+ });
22
+
23
+ app.use(vite.middlewares);
24
+ app.use("*", async (req, res, next) => {
25
+ const url = req.originalUrl;
26
+
27
+ try {
28
+ const clientTemplate = path.resolve(
29
+ import.meta.dirname,
30
+ "../..",
31
+ "client",
32
+ "index.html"
33
+ );
34
+
35
+ // always reload the index.html file from disk incase it changes
36
+ let template = await fs.promises.readFile(clientTemplate, "utf-8");
37
+ template = template.replace(
38
+ `src="/src/main.tsx"`,
39
+ `src="/src/main.tsx?v=${nanoid()}"`
40
+ );
41
+ const page = await vite.transformIndexHtml(url, template);
42
+ res.status(200).set({ "Content-Type": "text/html" }).end(page);
43
+ } catch (e) {
44
+ vite.ssrFixStacktrace(e as Error);
45
+ next(e);
46
+ }
47
+ });
48
+ }
49
+
50
+ export function serveStatic(app: Express) {
51
+ const distPath =
52
+ process.env.NODE_ENV === "development"
53
+ ? path.resolve(import.meta.dirname, "../..", "dist", "public")
54
+ : path.resolve(import.meta.dirname, "public");
55
+ if (!fs.existsSync(distPath)) {
56
+ console.error(
57
+ `Could not find the build directory: ${distPath}, make sure to build the client first`
58
+ );
59
+ }
60
+
61
+ app.use(express.static(distPath));
62
+
63
+ // fall through to index.html if the file doesn't exist
64
+ app.use("*", (_req, res) => {
65
+ res.sendFile(path.resolve(distPath, "index.html"));
66
+ });
67
+ }
server/_core/voiceTranscription.ts ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Voice transcription helper using internal Speech-to-Text service
3
+ *
4
+ * Frontend implementation guide:
5
+ * 1. Capture audio using MediaRecorder API
6
+ * 2. Upload audio to storage (e.g., S3) to get URL
7
+ * 3. Call transcription with the URL
8
+ *
9
+ * Example usage:
10
+ * ```tsx
11
+ * // Frontend component
12
+ * const transcribeMutation = trpc.voice.transcribe.useMutation({
13
+ * onSuccess: (data) => {
14
+ * console.log(data.text); // Full transcription
15
+ * console.log(data.language); // Detected language
16
+ * console.log(data.segments); // Timestamped segments
17
+ * }
18
+ * });
19
+ *
20
+ * // After uploading audio to storage
21
+ * transcribeMutation.mutate({
22
+ * audioUrl: uploadedAudioUrl,
23
+ * language: 'en', // optional
24
+ * prompt: 'Transcribe the meeting' // optional
25
+ * });
26
+ * ```
27
+ */
28
+ import { ENV } from "./env";
29
+
30
+ export type TranscribeOptions = {
31
+ audioUrl: string; // URL to the audio file (e.g., S3 URL)
32
+ language?: string; // Optional: specify language code (e.g., "en", "es", "zh")
33
+ prompt?: string; // Optional: custom prompt for the transcription
34
+ };
35
+
36
+ // Native Whisper API segment format
37
+ export type WhisperSegment = {
38
+ id: number;
39
+ seek: number;
40
+ start: number;
41
+ end: number;
42
+ text: string;
43
+ tokens: number[];
44
+ temperature: number;
45
+ avg_logprob: number;
46
+ compression_ratio: number;
47
+ no_speech_prob: number;
48
+ };
49
+
50
+ // Native Whisper API response format
51
+ export type WhisperResponse = {
52
+ task: "transcribe";
53
+ language: string;
54
+ duration: number;
55
+ text: string;
56
+ segments: WhisperSegment[];
57
+ };
58
+
59
+ export type TranscriptionResponse = WhisperResponse; // Return native Whisper API response directly
60
+
61
+ export type TranscriptionError = {
62
+ error: string;
63
+ code: "FILE_TOO_LARGE" | "INVALID_FORMAT" | "TRANSCRIPTION_FAILED" | "UPLOAD_FAILED" | "SERVICE_ERROR";
64
+ details?: string;
65
+ };
66
+
67
+ /**
68
+ * Transcribe audio to text using the internal Speech-to-Text service
69
+ *
70
+ * @param options - Audio data and metadata
71
+ * @returns Transcription result or error
72
+ */
73
+ export async function transcribeAudio(
74
+ options: TranscribeOptions
75
+ ): Promise<TranscriptionResponse | TranscriptionError> {
76
+ try {
77
+ // Step 1: Validate environment configuration
78
+ if (!ENV.forgeApiUrl) {
79
+ return {
80
+ error: "Voice transcription service is not configured",
81
+ code: "SERVICE_ERROR",
82
+ details: "BUILT_IN_FORGE_API_URL is not set"
83
+ };
84
+ }
85
+ if (!ENV.forgeApiKey) {
86
+ return {
87
+ error: "Voice transcription service authentication is missing",
88
+ code: "SERVICE_ERROR",
89
+ details: "BUILT_IN_FORGE_API_KEY is not set"
90
+ };
91
+ }
92
+
93
+ // Step 2: Download audio from URL
94
+ let audioBuffer: Buffer;
95
+ let mimeType: string;
96
+ try {
97
+ const response = await fetch(options.audioUrl);
98
+ if (!response.ok) {
99
+ return {
100
+ error: "Failed to download audio file",
101
+ code: "INVALID_FORMAT",
102
+ details: `HTTP ${response.status}: ${response.statusText}`
103
+ };
104
+ }
105
+
106
+ audioBuffer = Buffer.from(await response.arrayBuffer());
107
+ mimeType = response.headers.get('content-type') || 'audio/mpeg';
108
+
109
+ // Check file size (16MB limit)
110
+ const sizeMB = audioBuffer.length / (1024 * 1024);
111
+ if (sizeMB > 16) {
112
+ return {
113
+ error: "Audio file exceeds maximum size limit",
114
+ code: "FILE_TOO_LARGE",
115
+ details: `File size is ${sizeMB.toFixed(2)}MB, maximum allowed is 16MB`
116
+ };
117
+ }
118
+ } catch (error) {
119
+ return {
120
+ error: "Failed to fetch audio file",
121
+ code: "SERVICE_ERROR",
122
+ details: error instanceof Error ? error.message : "Unknown error"
123
+ };
124
+ }
125
+
126
+ // Step 3: Create FormData for multipart upload to Whisper API
127
+ const formData = new FormData();
128
+
129
+ // Create a Blob from the buffer and append to form
130
+ const filename = `audio.${getFileExtension(mimeType)}`;
131
+ const audioBlob = new Blob([new Uint8Array(audioBuffer)], { type: mimeType });
132
+ formData.append("file", audioBlob, filename);
133
+
134
+ formData.append("model", "whisper-1");
135
+ formData.append("response_format", "verbose_json");
136
+
137
+ // Add prompt - use custom prompt if provided, otherwise generate based on language
138
+ const prompt = options.prompt || (
139
+ options.language
140
+ ? `Transcribe the user's voice to text, the user's working language is ${getLanguageName(options.language)}`
141
+ : "Transcribe the user's voice to text"
142
+ );
143
+ formData.append("prompt", prompt);
144
+
145
+ // Step 4: Call the transcription service
146
+ const baseUrl = ENV.forgeApiUrl.endsWith("/")
147
+ ? ENV.forgeApiUrl
148
+ : `${ENV.forgeApiUrl}/`;
149
+
150
+ const fullUrl = new URL(
151
+ "v1/audio/transcriptions",
152
+ baseUrl
153
+ ).toString();
154
+
155
+ const response = await fetch(fullUrl, {
156
+ method: "POST",
157
+ headers: {
158
+ authorization: `Bearer ${ENV.forgeApiKey}`,
159
+ "Accept-Encoding": "identity",
160
+ },
161
+ body: formData,
162
+ });
163
+
164
+ if (!response.ok) {
165
+ const errorText = await response.text().catch(() => "");
166
+ return {
167
+ error: "Transcription service request failed",
168
+ code: "TRANSCRIPTION_FAILED",
169
+ details: `${response.status} ${response.statusText}${errorText ? `: ${errorText}` : ""}`
170
+ };
171
+ }
172
+
173
+ // Step 5: Parse and return the transcription result
174
+ const whisperResponse = await response.json() as WhisperResponse;
175
+
176
+ // Validate response structure
177
+ if (!whisperResponse.text || typeof whisperResponse.text !== 'string') {
178
+ return {
179
+ error: "Invalid transcription response",
180
+ code: "SERVICE_ERROR",
181
+ details: "Transcription service returned an invalid response format"
182
+ };
183
+ }
184
+
185
+ return whisperResponse; // Return native Whisper API response directly
186
+
187
+ } catch (error) {
188
+ // Handle unexpected errors
189
+ return {
190
+ error: "Voice transcription failed",
191
+ code: "SERVICE_ERROR",
192
+ details: error instanceof Error ? error.message : "An unexpected error occurred"
193
+ };
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Helper function to get file extension from MIME type
199
+ */
200
+ function getFileExtension(mimeType: string): string {
201
+ const mimeToExt: Record<string, string> = {
202
+ 'audio/webm': 'webm',
203
+ 'audio/mp3': 'mp3',
204
+ 'audio/mpeg': 'mp3',
205
+ 'audio/wav': 'wav',
206
+ 'audio/wave': 'wav',
207
+ 'audio/ogg': 'ogg',
208
+ 'audio/m4a': 'm4a',
209
+ 'audio/mp4': 'm4a',
210
+ };
211
+
212
+ return mimeToExt[mimeType] || 'audio';
213
+ }
214
+
215
+ /**
216
+ * Helper function to get full language name from ISO code
217
+ */
218
+ function getLanguageName(langCode: string): string {
219
+ const langMap: Record<string, string> = {
220
+ 'en': 'English',
221
+ 'es': 'Spanish',
222
+ 'fr': 'French',
223
+ 'de': 'German',
224
+ 'it': 'Italian',
225
+ 'pt': 'Portuguese',
226
+ 'ru': 'Russian',
227
+ 'ja': 'Japanese',
228
+ 'ko': 'Korean',
229
+ 'zh': 'Chinese',
230
+ 'ar': 'Arabic',
231
+ 'hi': 'Hindi',
232
+ 'nl': 'Dutch',
233
+ 'pl': 'Polish',
234
+ 'tr': 'Turkish',
235
+ 'sv': 'Swedish',
236
+ 'da': 'Danish',
237
+ 'no': 'Norwegian',
238
+ 'fi': 'Finnish',
239
+ };
240
+
241
+ return langMap[langCode] || langCode;
242
+ }
243
+
244
+ /**
245
+ * Example tRPC procedure implementation:
246
+ *
247
+ * ```ts
248
+ * // In server/routers.ts
249
+ * import { transcribeAudio } from "./_core/voiceTranscription";
250
+ *
251
+ * export const voiceRouter = router({
252
+ * transcribe: protectedProcedure
253
+ * .input(z.object({
254
+ * audioUrl: z.string(),
255
+ * language: z.string().optional(),
256
+ * prompt: z.string().optional(),
257
+ * }))
258
+ * .mutation(async ({ input, ctx }) => {
259
+ * const result = await transcribeAudio(input);
260
+ *
261
+ * // Check if it's an error
262
+ * if ('error' in result) {
263
+ * throw new TRPCError({
264
+ * code: 'BAD_REQUEST',
265
+ * message: result.error,
266
+ * cause: result,
267
+ * });
268
+ * }
269
+ *
270
+ * // Optionally save transcription to database
271
+ * await db.insert(transcriptions).values({
272
+ * userId: ctx.user.id,
273
+ * text: result.text,
274
+ * duration: result.duration,
275
+ * language: result.language,
276
+ * audioUrl: input.audioUrl,
277
+ * createdAt: new Date(),
278
+ * });
279
+ *
280
+ * return result;
281
+ * }),
282
+ * });
283
+ * ```
284
+ */
server/auth.logout.test.ts ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { describe, expect, it } from "vitest";
2
+ import { appRouter } from "./routers";
3
+ import { COOKIE_NAME } from "../shared/const";
4
+ import type { TrpcContext } from "./_core/context";
5
+
6
+ type CookieCall = {
7
+ name: string;
8
+ options: Record<string, unknown>;
9
+ };
10
+
11
+ type AuthenticatedUser = NonNullable<TrpcContext["user"]>;
12
+
13
+ function createAuthContext(): { ctx: TrpcContext; clearedCookies: CookieCall[] } {
14
+ const clearedCookies: CookieCall[] = [];
15
+
16
+ const user: AuthenticatedUser = {
17
+ id: 1,
18
+ openId: "sample-user",
19
+ email: "sample@example.com",
20
+ name: "Sample User",
21
+ loginMethod: "manus",
22
+ role: "user",
23
+ createdAt: new Date(),
24
+ updatedAt: new Date(),
25
+ lastSignedIn: new Date(),
26
+ };
27
+
28
+ const ctx: TrpcContext = {
29
+ user,
30
+ req: {
31
+ protocol: "https",
32
+ headers: {},
33
+ } as TrpcContext["req"],
34
+ res: {
35
+ clearCookie: (name: string, options: Record<string, unknown>) => {
36
+ clearedCookies.push({ name, options });
37
+ },
38
+ } as TrpcContext["res"],
39
+ };
40
+
41
+ return { ctx, clearedCookies };
42
+ }
43
+
44
+ describe("auth.logout", () => {
45
+ it("clears the session cookie and reports success", async () => {
46
+ const { ctx, clearedCookies } = createAuthContext();
47
+ const caller = appRouter.createCaller(ctx);
48
+
49
+ const result = await caller.auth.logout();
50
+
51
+ expect(result).toEqual({ success: true });
52
+ expect(clearedCookies).toHaveLength(1);
53
+ expect(clearedCookies[0]?.name).toBe(COOKIE_NAME);
54
+ expect(clearedCookies[0]?.options).toMatchObject({
55
+ maxAge: -1,
56
+ secure: true,
57
+ sameSite: "none",
58
+ httpOnly: true,
59
+ path: "/",
60
+ });
61
+ });
62
+ });
server/db.ts ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { eq } from "drizzle-orm";
2
+ import { drizzle } from "drizzle-orm/mysql2";
3
+ import { InsertUser, users } from "../drizzle/schema";
4
+ import { ENV } from './_core/env';
5
+
6
+ let _db: ReturnType<typeof drizzle> | null = null;
7
+
8
+ // Lazily create the drizzle instance so local tooling can run without a DB.
9
+ export async function getDb() {
10
+ if (!_db && process.env.DATABASE_URL) {
11
+ try {
12
+ _db = drizzle(process.env.DATABASE_URL);
13
+ } catch (error) {
14
+ console.warn("[Database] Failed to connect:", error);
15
+ _db = null;
16
+ }
17
+ }
18
+ return _db;
19
+ }
20
+
21
+ export async function upsertUser(user: InsertUser): Promise<void> {
22
+ if (!user.openId) {
23
+ throw new Error("User openId is required for upsert");
24
+ }
25
+
26
+ const db = await getDb();
27
+ if (!db) {
28
+ console.warn("[Database] Cannot upsert user: database not available");
29
+ return;
30
+ }
31
+
32
+ try {
33
+ const values: InsertUser = {
34
+ openId: user.openId,
35
+ };
36
+ const updateSet: Record<string, unknown> = {};
37
+
38
+ const textFields = ["name", "email", "loginMethod"] as const;
39
+ type TextField = (typeof textFields)[number];
40
+
41
+ const assignNullable = (field: TextField) => {
42
+ const value = user[field];
43
+ if (value === undefined) return;
44
+ const normalized = value ?? null;
45
+ values[field] = normalized;
46
+ updateSet[field] = normalized;
47
+ };
48
+
49
+ textFields.forEach(assignNullable);
50
+
51
+ if (user.lastSignedIn !== undefined) {
52
+ values.lastSignedIn = user.lastSignedIn;
53
+ updateSet.lastSignedIn = user.lastSignedIn;
54
+ }
55
+ if (user.role !== undefined) {
56
+ values.role = user.role;
57
+ updateSet.role = user.role;
58
+ } else if (user.openId === ENV.ownerOpenId) {
59
+ values.role = 'admin';
60
+ updateSet.role = 'admin';
61
+ }
62
+
63
+ if (!values.lastSignedIn) {
64
+ values.lastSignedIn = new Date();
65
+ }
66
+
67
+ if (Object.keys(updateSet).length === 0) {
68
+ updateSet.lastSignedIn = new Date();
69
+ }
70
+
71
+ await db.insert(users).values(values).onDuplicateKeyUpdate({
72
+ set: updateSet,
73
+ });
74
+ } catch (error) {
75
+ console.error("[Database] Failed to upsert user:", error);
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ export async function getUserByOpenId(openId: string) {
81
+ const db = await getDb();
82
+ if (!db) {
83
+ console.warn("[Database] Cannot get user: database not available");
84
+ return undefined;
85
+ }
86
+
87
+ const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1);
88
+
89
+ return result.length > 0 ? result[0] : undefined;
90
+ }
91
+
92
+ // TODO: add feature queries here as your schema grows.
server/routers.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { COOKIE_NAME } from "@shared/const";
2
+ import { getSessionCookieOptions } from "./_core/cookies";
3
+ import { systemRouter } from "./_core/systemRouter";
4
+ import { publicProcedure, router } from "./_core/trpc";
5
+ import { paymentsRouter } from "./routers/payments";
6
+
7
+ export const appRouter = router({
8
+ // if you need to use socket.io, read and register route in server/_core/index.ts, all api should start with '/api/' so that the gateway can route correctly
9
+ system: systemRouter,
10
+ auth: router({
11
+ me: publicProcedure.query(opts => opts.ctx.user),
12
+ logout: publicProcedure.mutation(({ ctx }) => {
13
+ const cookieOptions = getSessionCookieOptions(ctx.req);
14
+ ctx.res.clearCookie(COOKIE_NAME, { ...cookieOptions, maxAge: -1 });
15
+ return {
16
+ success: true,
17
+ } as const;
18
+ }),
19
+ }),
20
+
21
+ payments: paymentsRouter,
22
+
23
+ // TODO: add feature routers here, e.g.
24
+ // todo: router({
25
+ // list: protectedProcedure.query(({ ctx }) =>
26
+ // db.getUserTodos(ctx.user.id)
27
+ // ),
28
+ // }),
29
+ });
30
+
31
+ export type AppRouter = typeof appRouter;
server/routers/payments.ts ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { z } from 'zod';
2
+ import { protectedProcedure, publicProcedure, router } from '../_core/trpc';
3
+ import Stripe from 'stripe';
4
+ import { ENV } from '../_core/env';
5
+ import { getProductById } from '../../shared/products';
6
+
7
+ const stripe = new Stripe(ENV.stripeSecretKey);
8
+
9
+ export const paymentsRouter = router({
10
+ /**
11
+ * Create a Stripe checkout session for a product
12
+ */
13
+ createCheckoutSession: protectedProcedure
14
+ .input(
15
+ z.object({
16
+ productId: z.string(),
17
+ successUrl: z.string().url(),
18
+ cancelUrl: z.string().url(),
19
+ })
20
+ )
21
+ .mutation(async ({ input, ctx }) => {
22
+ const product = getProductById(input.productId);
23
+ if (!product) {
24
+ throw new Error('Product not found');
25
+ }
26
+
27
+ try {
28
+ const session = await stripe.checkout.sessions.create({
29
+ payment_method_types: ['card'],
30
+ mode: product.type === 'subscription' ? 'subscription' : 'payment',
31
+ customer_email: ctx.user.email || undefined,
32
+ client_reference_id: ctx.user.id.toString(),
33
+ metadata: {
34
+ user_id: ctx.user.id.toString(),
35
+ customer_email: ctx.user.email || '',
36
+ customer_name: ctx.user.name || '',
37
+ product_id: product.id,
38
+ },
39
+ line_items: [
40
+ {
41
+ price_data: {
42
+ currency: product.currency.toLowerCase(),
43
+ product_data: {
44
+ name: product.name,
45
+ description: product.description,
46
+ },
47
+ unit_amount: product.price,
48
+ ...(product.type === 'subscription' && product.interval
49
+ ? {
50
+ recurring: {
51
+ interval: product.interval,
52
+ },
53
+ }
54
+ : {}),
55
+ },
56
+ quantity: 1,
57
+ },
58
+ ],
59
+ success_url: input.successUrl,
60
+ cancel_url: input.cancelUrl,
61
+ allow_promotion_codes: true,
62
+ });
63
+
64
+ return {
65
+ sessionId: session.id,
66
+ url: session.url,
67
+ };
68
+ } catch (error) {
69
+ console.error('[Stripe] Checkout session creation failed:', error);
70
+ throw new Error('Failed to create checkout session');
71
+ }
72
+ }),
73
+
74
+ /**
75
+ * Get payment history for the current user
76
+ */
77
+ getPaymentHistory: protectedProcedure.query(async ({ ctx }) => {
78
+ try {
79
+ // Get customer ID from Stripe for this user
80
+ const customers = await stripe.customers.list({
81
+ email: ctx.user.email || undefined,
82
+ limit: 1,
83
+ });
84
+
85
+ if (customers.data.length === 0) {
86
+ return [];
87
+ }
88
+
89
+ const customerId = customers.data[0].id;
90
+
91
+ // Get payment intents for this customer
92
+ const paymentIntents = await stripe.paymentIntents.list({
93
+ customer: customerId,
94
+ limit: 50,
95
+ });
96
+
97
+ // Get subscriptions for this customer
98
+ const subscriptions = await stripe.subscriptions.list({
99
+ customer: customerId,
100
+ limit: 50,
101
+ });
102
+
103
+ // Format payment history
104
+ const payments = paymentIntents.data.map((intent: Stripe.PaymentIntent) => ({
105
+ id: intent.id,
106
+ type: 'one-time' as const,
107
+ amount: intent.amount,
108
+ currency: intent.currency.toUpperCase(),
109
+ status: intent.status,
110
+ created: new Date(intent.created * 1000),
111
+ description: intent.description || 'Payment',
112
+ }));
113
+
114
+ const subs = subscriptions.data.map((sub: Stripe.Subscription) => ({
115
+ id: sub.id,
116
+ type: 'subscription' as const,
117
+ amount: sub.items.data[0]?.price.unit_amount || 0,
118
+ currency: sub.currency.toUpperCase(),
119
+ status: sub.status,
120
+ created: new Date(sub.created * 1000),
121
+ description: sub.items.data[0]?.price.product
122
+ ? `Subscription: ${sub.items.data[0].price.product}`
123
+ : 'Subscription',
124
+ currentPeriodEnd: (sub as any).current_period_end
125
+ ? new Date((sub as any).current_period_end * 1000)
126
+ : undefined,
127
+ }));
128
+
129
+ return [...payments, ...subs].sort((a, b) => b.created.getTime() - a.created.getTime());
130
+ } catch (error) {
131
+ console.error('[Stripe] Failed to fetch payment history:', error);
132
+ throw new Error('Failed to fetch payment history');
133
+ }
134
+ }),
135
+
136
+ /**
137
+ * Get subscription details for the current user
138
+ */
139
+ getSubscriptions: protectedProcedure.query(async ({ ctx }) => {
140
+ try {
141
+ const customers = await stripe.customers.list({
142
+ email: ctx.user.email || undefined,
143
+ limit: 1,
144
+ });
145
+
146
+ if (customers.data.length === 0) {
147
+ return [];
148
+ }
149
+
150
+ const customerId = customers.data[0].id;
151
+ const subscriptions = await stripe.subscriptions.list({
152
+ customer: customerId,
153
+ status: 'active',
154
+ limit: 50,
155
+ });
156
+
157
+ return subscriptions.data.map((sub: Stripe.Subscription) => ({
158
+ id: sub.id,
159
+ status: sub.status,
160
+ currentPeriodStart: new Date((sub as any).current_period_start * 1000),
161
+ currentPeriodEnd: new Date((sub as any).current_period_end * 1000),
162
+ amount: sub.items.data[0]?.price.unit_amount || 0,
163
+ currency: sub.currency.toUpperCase(),
164
+ interval: sub.items.data[0]?.price.recurring?.interval || 'month',
165
+ }));
166
+ } catch (error) {
167
+ console.error('[Stripe] Failed to fetch subscriptions:', error);
168
+ throw new Error('Failed to fetch subscriptions');
169
+ }
170
+ }),
171
+
172
+ /**
173
+ * Cancel a subscription
174
+ */
175
+ cancelSubscription: protectedProcedure
176
+ .input(z.object({ subscriptionId: z.string() }))
177
+ .mutation(async ({ input, ctx }) => {
178
+ try {
179
+ // Verify the subscription belongs to this user
180
+ const subscription = await stripe.subscriptions.retrieve(input.subscriptionId);
181
+ const customer = await stripe.customers.retrieve(subscription.customer as string);
182
+
183
+ if ((customer as any).email !== ctx.user.email) {
184
+ throw new Error('Unauthorized');
185
+ }
186
+
187
+ const cancelled = await stripe.subscriptions.cancel(input.subscriptionId);
188
+
189
+ return {
190
+ success: true,
191
+ cancelledAt: new Date(cancelled.canceled_at! * 1000),
192
+ };
193
+ } catch (error) {
194
+ console.error('[Stripe] Failed to cancel subscription:', error);
195
+ throw new Error('Failed to cancel subscription');
196
+ }
197
+ }),
198
+ });
server/storage.ts ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Preconfigured storage helpers for Manus WebDev templates
2
+ // Uses the Biz-provided storage proxy (Authorization: Bearer <token>)
3
+
4
+ import { ENV } from './_core/env';
5
+
6
+ type StorageConfig = { baseUrl: string; apiKey: string };
7
+
8
+ function getStorageConfig(): StorageConfig {
9
+ const baseUrl = ENV.forgeApiUrl;
10
+ const apiKey = ENV.forgeApiKey;
11
+
12
+ if (!baseUrl || !apiKey) {
13
+ throw new Error(
14
+ "Storage proxy credentials missing: set BUILT_IN_FORGE_API_URL and BUILT_IN_FORGE_API_KEY"
15
+ );
16
+ }
17
+
18
+ return { baseUrl: baseUrl.replace(/\/+$/, ""), apiKey };
19
+ }
20
+
21
+ function buildUploadUrl(baseUrl: string, relKey: string): URL {
22
+ const url = new URL("v1/storage/upload", ensureTrailingSlash(baseUrl));
23
+ url.searchParams.set("path", normalizeKey(relKey));
24
+ return url;
25
+ }
26
+
27
+ async function buildDownloadUrl(
28
+ baseUrl: string,
29
+ relKey: string,
30
+ apiKey: string
31
+ ): Promise<string> {
32
+ const downloadApiUrl = new URL(
33
+ "v1/storage/downloadUrl",
34
+ ensureTrailingSlash(baseUrl)
35
+ );
36
+ downloadApiUrl.searchParams.set("path", normalizeKey(relKey));
37
+ const response = await fetch(downloadApiUrl, {
38
+ method: "GET",
39
+ headers: buildAuthHeaders(apiKey),
40
+ });
41
+ return (await response.json()).url;
42
+ }
43
+
44
+ function ensureTrailingSlash(value: string): string {
45
+ return value.endsWith("/") ? value : `${value}/`;
46
+ }
47
+
48
+ function normalizeKey(relKey: string): string {
49
+ return relKey.replace(/^\/+/, "");
50
+ }
51
+
52
+ function toFormData(
53
+ data: Buffer | Uint8Array | string,
54
+ contentType: string,
55
+ fileName: string
56
+ ): FormData {
57
+ const blob =
58
+ typeof data === "string"
59
+ ? new Blob([data], { type: contentType })
60
+ : new Blob([data as any], { type: contentType });
61
+ const form = new FormData();
62
+ form.append("file", blob, fileName || "file");
63
+ return form;
64
+ }
65
+
66
+ function buildAuthHeaders(apiKey: string): HeadersInit {
67
+ return { Authorization: `Bearer ${apiKey}` };
68
+ }
69
+
70
+ export async function storagePut(
71
+ relKey: string,
72
+ data: Buffer | Uint8Array | string,
73
+ contentType = "application/octet-stream"
74
+ ): Promise<{ key: string; url: string }> {
75
+ const { baseUrl, apiKey } = getStorageConfig();
76
+ const key = normalizeKey(relKey);
77
+ const uploadUrl = buildUploadUrl(baseUrl, key);
78
+ const formData = toFormData(data, contentType, key.split("/").pop() ?? key);
79
+ const response = await fetch(uploadUrl, {
80
+ method: "POST",
81
+ headers: buildAuthHeaders(apiKey),
82
+ body: formData,
83
+ });
84
+
85
+ if (!response.ok) {
86
+ const message = await response.text().catch(() => response.statusText);
87
+ throw new Error(
88
+ `Storage upload failed (${response.status} ${response.statusText}): ${message}`
89
+ );
90
+ }
91
+ const url = (await response.json()).url;
92
+ return { key, url };
93
+ }
94
+
95
+ export async function storageGet(relKey: string): Promise<{ key: string; url: string; }> {
96
+ const { baseUrl, apiKey } = getStorageConfig();
97
+ const key = normalizeKey(relKey);
98
+ return {
99
+ key,
100
+ url: await buildDownloadUrl(baseUrl, key, apiKey),
101
+ };
102
+ }
server/webhooks/stripe.ts ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Stripe from 'stripe';
2
+ import { Request, Response } from 'express';
3
+ import { ENV } from '../_core/env';
4
+
5
+ const stripe = new Stripe(ENV.stripeSecretKey);
6
+
7
+ /**
8
+ * Handle Stripe webhook events
9
+ * This endpoint receives events from Stripe when payments are processed
10
+ */
11
+ export async function handleStripeWebhook(req: Request, res: Response) {
12
+ const sig = req.headers['stripe-signature'] as string;
13
+
14
+ let event: Stripe.Event;
15
+
16
+ try {
17
+ event = stripe.webhooks.constructEvent(
18
+ req.body,
19
+ sig,
20
+ ENV.stripeWebhookSecret
21
+ );
22
+ } catch (err) {
23
+ console.error('[Stripe Webhook] Signature verification failed:', err);
24
+ return res.status(400).send(`Webhook Error: ${err}`);
25
+ }
26
+
27
+ // Handle test events
28
+ if (event.id.startsWith('evt_test_')) {
29
+ console.log('[Stripe Webhook] Test event detected, returning verification response');
30
+ return res.json({
31
+ verified: true,
32
+ });
33
+ }
34
+
35
+ try {
36
+ switch (event.type) {
37
+ case 'checkout.session.completed': {
38
+ const session = event.data.object as Stripe.Checkout.Session;
39
+ console.log('[Stripe Webhook] Checkout session completed:', session.id);
40
+
41
+ // Extract metadata
42
+ const userId = session.metadata?.user_id;
43
+ const productId = session.metadata?.product_id;
44
+ const customerEmail = session.customer_email;
45
+
46
+ console.log('[Stripe Webhook] Order details:', {
47
+ sessionId: session.id,
48
+ userId,
49
+ productId,
50
+ customerEmail,
51
+ amount: session.amount_total,
52
+ currency: session.currency,
53
+ });
54
+
55
+ // TODO: Update database with order information
56
+ // - Save order record
57
+ // - Update user subscription status if applicable
58
+ // - Send confirmation email
59
+ // - Trigger fulfillment workflow
60
+
61
+ break;
62
+ }
63
+
64
+ case 'payment_intent.succeeded': {
65
+ const paymentIntent = event.data.object as Stripe.PaymentIntent;
66
+ console.log('[Stripe Webhook] Payment intent succeeded:', paymentIntent.id);
67
+
68
+ // TODO: Handle payment success
69
+ // - Update order status
70
+ // - Send receipt email
71
+ // - Trigger fulfillment
72
+
73
+ break;
74
+ }
75
+
76
+ case 'payment_intent.payment_failed': {
77
+ const paymentIntent = event.data.object as Stripe.PaymentIntent;
78
+ console.log('[Stripe Webhook] Payment intent failed:', paymentIntent.id);
79
+
80
+ // TODO: Handle payment failure
81
+ // - Update order status
82
+ // - Send failure notification
83
+ // - Trigger retry logic
84
+
85
+ break;
86
+ }
87
+
88
+ case 'customer.subscription.created': {
89
+ const subscription = event.data.object as Stripe.Subscription;
90
+ console.log('[Stripe Webhook] Subscription created:', subscription.id);
91
+
92
+ // TODO: Handle subscription creation
93
+ // - Save subscription record
94
+ // - Grant access to features
95
+ // - Send welcome email
96
+
97
+ break;
98
+ }
99
+
100
+ case 'customer.subscription.updated': {
101
+ const subscription = event.data.object as Stripe.Subscription;
102
+ console.log('[Stripe Webhook] Subscription updated:', subscription.id);
103
+
104
+ // TODO: Handle subscription update
105
+ // - Update subscription record
106
+ // - Adjust feature access if plan changed
107
+
108
+ break;
109
+ }
110
+
111
+ case 'customer.subscription.deleted': {
112
+ const subscription = event.data.object as Stripe.Subscription;
113
+ console.log('[Stripe Webhook] Subscription deleted:', subscription.id);
114
+
115
+ // TODO: Handle subscription cancellation
116
+ // - Mark subscription as cancelled
117
+ // - Revoke access to features
118
+ // - Send cancellation confirmation
119
+
120
+ break;
121
+ }
122
+
123
+ case 'invoice.paid': {
124
+ const invoice = event.data.object as Stripe.Invoice;
125
+ console.log('[Stripe Webhook] Invoice paid:', invoice.id);
126
+
127
+ // TODO: Handle invoice payment
128
+ // - Update invoice status
129
+ // - Send receipt
130
+ // - Trigger renewal logic
131
+
132
+ break;
133
+ }
134
+
135
+ case 'invoice.payment_failed': {
136
+ const invoice = event.data.object as Stripe.Invoice;
137
+ console.log('[Stripe Webhook] Invoice payment failed:', invoice.id);
138
+
139
+ // TODO: Handle invoice payment failure
140
+ // - Update invoice status
141
+ // - Send retry notification
142
+ // - Trigger dunning process
143
+
144
+ break;
145
+ }
146
+
147
+ default:
148
+ console.log('[Stripe Webhook] Unhandled event type:', event.type);
149
+ }
150
+
151
+ // Acknowledge receipt of event
152
+ res.json({ received: true });
153
+ } catch (error) {
154
+ console.error('[Stripe Webhook] Error processing event:', error);
155
+ res.status(500).json({ error: 'Webhook processing failed' });
156
+ }
157
+ }
shared/_core/errors.ts ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Base HTTP error class with status code.
3
+ * Throw this from route handlers to send specific HTTP errors.
4
+ */
5
+ export class HttpError extends Error {
6
+ constructor(
7
+ public statusCode: number,
8
+ message: string
9
+ ) {
10
+ super(message);
11
+ this.name = "HttpError";
12
+ }
13
+ }
14
+
15
+ // Convenience constructors
16
+ export const BadRequestError = (msg: string) => new HttpError(400, msg);
17
+ export const UnauthorizedError = (msg: string) => new HttpError(401, msg);
18
+ export const ForbiddenError = (msg: string) => new HttpError(403, msg);
19
+ export const NotFoundError = (msg: string) => new HttpError(404, msg);
shared/const.ts CHANGED
@@ -1,2 +1,5 @@
1
  export const COOKIE_NAME = "app_session_id";
2
  export const ONE_YEAR_MS = 1000 * 60 * 60 * 24 * 365;
 
 
 
 
1
  export const COOKIE_NAME = "app_session_id";
2
  export const ONE_YEAR_MS = 1000 * 60 * 60 * 24 * 365;
3
+ export const AXIOS_TIMEOUT_MS = 30_000;
4
+ export const UNAUTHED_ERR_MSG = 'Please login (10001)';
5
+ export const NOT_ADMIN_ERR_MSG = 'You do not have required permission (10002)';
shared/products.ts ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Product and Service Configuration for Stripe Integration
3
+ * Define all products, subscriptions, and pricing here
4
+ */
5
+
6
+ export interface Product {
7
+ id: string;
8
+ name: string;
9
+ description: string;
10
+ price: number; // in cents (e.g., 9900 = $99.00)
11
+ currency: string;
12
+ type: 'one-time' | 'subscription';
13
+ interval?: 'month' | 'year'; // for subscriptions
14
+ stripeProductId?: string;
15
+ stripePriceId?: string;
16
+ features?: string[];
17
+ icon?: string;
18
+ }
19
+
20
+ export const PRODUCTS: Product[] = [
21
+ {
22
+ id: 'aurora-starter',
23
+ name: 'Aurora Starter',
24
+ description: 'Perfect for small teams getting started with AI automation',
25
+ price: 29900, // $299/month
26
+ currency: 'USD',
27
+ type: 'subscription',
28
+ interval: 'month',
29
+ features: [
30
+ 'Up to 5 users',
31
+ 'Basic AI automation',
32
+ 'Email support',
33
+ '10GB storage',
34
+ 'Monthly reports',
35
+ ],
36
+ icon: 'Rocket',
37
+ },
38
+ {
39
+ id: 'aurora-pro',
40
+ name: 'Aurora Pro',
41
+ description: 'For growing organizations needing advanced features',
42
+ price: 79900, // $799/month
43
+ currency: 'USD',
44
+ type: 'subscription',
45
+ interval: 'month',
46
+ features: [
47
+ 'Up to 25 users',
48
+ 'Advanced AI automation',
49
+ 'Priority support',
50
+ '100GB storage',
51
+ 'Real-time analytics',
52
+ 'Custom integrations',
53
+ 'API access',
54
+ ],
55
+ icon: 'Zap',
56
+ },
57
+ {
58
+ id: 'aurora-enterprise',
59
+ name: 'Aurora Enterprise',
60
+ description: 'Full-featured solution for large enterprises',
61
+ price: 0, // Custom pricing
62
+ currency: 'USD',
63
+ type: 'subscription',
64
+ interval: 'month',
65
+ features: [
66
+ 'Unlimited users',
67
+ 'Full AI automation suite',
68
+ '24/7 dedicated support',
69
+ 'Unlimited storage',
70
+ 'Advanced analytics & reporting',
71
+ 'Custom integrations',
72
+ 'API access',
73
+ 'SLA guarantee',
74
+ 'On-premise deployment option',
75
+ ],
76
+ icon: 'Shield',
77
+ },
78
+ {
79
+ id: 'drex-integration',
80
+ name: 'Drex Digital Integration',
81
+ description: 'One-time setup fee for Drex Digital payment integration',
82
+ price: 49900, // $499
83
+ currency: 'USD',
84
+ type: 'one-time',
85
+ features: [
86
+ 'Drex Digital wallet integration',
87
+ 'Payment processing setup',
88
+ 'Technical documentation',
89
+ 'Implementation support',
90
+ '30 days of technical assistance',
91
+ ],
92
+ icon: 'TrendingUp',
93
+ },
94
+ {
95
+ id: 'consulting-package',
96
+ name: 'Implementation Consulting',
97
+ description: 'Professional consulting for Aurora platform implementation',
98
+ price: 15000, // $150/hour (example)
99
+ currency: 'USD',
100
+ type: 'one-time',
101
+ features: [
102
+ 'Architecture review',
103
+ 'Custom workflow design',
104
+ 'Team training',
105
+ 'Integration planning',
106
+ 'Performance optimization',
107
+ ],
108
+ icon: 'Lightbulb',
109
+ },
110
+ ];
111
+
112
+ /**
113
+ * Get product by ID
114
+ */
115
+ export function getProductById(id: string): Product | undefined {
116
+ return PRODUCTS.find((p) => p.id === id);
117
+ }
118
+
119
+ /**
120
+ * Get all subscription products
121
+ */
122
+ export function getSubscriptionProducts(): Product[] {
123
+ return PRODUCTS.filter((p) => p.type === 'subscription');
124
+ }
125
+
126
+ /**
127
+ * Get all one-time purchase products
128
+ */
129
+ export function getOneTimeProducts(): Product[] {
130
+ return PRODUCTS.filter((p) => p.type === 'one-time');
131
+ }
132
+
133
+ /**
134
+ * Format price for display
135
+ */
136
+ export function formatPrice(priceInCents: number, currency: string = 'USD'): string {
137
+ const formatter = new Intl.NumberFormat('en-US', {
138
+ style: 'currency',
139
+ currency,
140
+ });
141
+ return formatter.format(priceInCents / 100);
142
+ }