@realtimex/email-automator 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/server.ts +0 -6
- package/api/src/config/index.ts +3 -0
- package/bin/email-automator-setup.js +2 -3
- package/bin/email-automator.js +23 -7
- package/package.json +1 -2
- package/src/App.tsx +0 -622
- package/src/components/AccountSettings.tsx +0 -310
- package/src/components/AccountSettingsPage.tsx +0 -390
- package/src/components/Configuration.tsx +0 -1345
- package/src/components/Dashboard.tsx +0 -940
- package/src/components/ErrorBoundary.tsx +0 -71
- package/src/components/LiveTerminal.tsx +0 -308
- package/src/components/LoadingSpinner.tsx +0 -39
- package/src/components/Login.tsx +0 -371
- package/src/components/Logo.tsx +0 -57
- package/src/components/SetupWizard.tsx +0 -388
- package/src/components/Toast.tsx +0 -109
- package/src/components/migration/MigrationBanner.tsx +0 -97
- package/src/components/migration/MigrationModal.tsx +0 -458
- package/src/components/migration/MigrationPulseIndicator.tsx +0 -38
- package/src/components/mode-toggle.tsx +0 -24
- package/src/components/theme-provider.tsx +0 -72
- package/src/components/ui/alert.tsx +0 -66
- package/src/components/ui/button.tsx +0 -57
- package/src/components/ui/card.tsx +0 -75
- package/src/components/ui/dialog.tsx +0 -133
- package/src/components/ui/input.tsx +0 -22
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/otp-input.tsx +0 -184
- package/src/context/AppContext.tsx +0 -422
- package/src/context/MigrationContext.tsx +0 -53
- package/src/context/TerminalContext.tsx +0 -31
- package/src/core/actions.ts +0 -76
- package/src/core/auth.ts +0 -108
- package/src/core/intelligence.ts +0 -76
- package/src/core/processor.ts +0 -112
- package/src/hooks/useRealtimeEmails.ts +0 -111
- package/src/index.css +0 -140
- package/src/lib/api-config.ts +0 -42
- package/src/lib/api-old.ts +0 -228
- package/src/lib/api.ts +0 -421
- package/src/lib/migration-check.ts +0 -264
- package/src/lib/sounds.ts +0 -120
- package/src/lib/supabase-config.ts +0 -117
- package/src/lib/supabase.ts +0 -28
- package/src/lib/types.ts +0 -166
- package/src/lib/utils.ts +0 -6
- package/src/main.tsx +0 -10
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MigrationModal Component
|
|
3
|
-
*
|
|
4
|
-
* Displays detailed migration instructions in a modal dialog.
|
|
5
|
-
* Shows step-by-step guide for users to run the migration command.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useMemo, useState, useEffect, useRef } from "react";
|
|
9
|
-
import {
|
|
10
|
-
AlertTriangle,
|
|
11
|
-
Copy,
|
|
12
|
-
Check,
|
|
13
|
-
ExternalLink,
|
|
14
|
-
Info,
|
|
15
|
-
Loader2,
|
|
16
|
-
Terminal,
|
|
17
|
-
} from "lucide-react";
|
|
18
|
-
import {
|
|
19
|
-
Dialog,
|
|
20
|
-
DialogContent,
|
|
21
|
-
DialogDescription,
|
|
22
|
-
DialogFooter,
|
|
23
|
-
DialogHeader,
|
|
24
|
-
DialogTitle,
|
|
25
|
-
} from "../ui/dialog";
|
|
26
|
-
import { Button } from "../ui/button";
|
|
27
|
-
import { Alert, AlertDescription } from "../ui/alert";
|
|
28
|
-
import { Input } from "../ui/input";
|
|
29
|
-
import { Label } from "../ui/label";
|
|
30
|
-
import { toast } from "../Toast";
|
|
31
|
-
import { getSupabaseConfig } from "../../lib/supabase-config";
|
|
32
|
-
import type { MigrationStatus } from "../../lib/migration-check";
|
|
33
|
-
|
|
34
|
-
interface MigrationModalProps {
|
|
35
|
-
/** Whether the modal is open */
|
|
36
|
-
open: boolean;
|
|
37
|
-
/** Callback when modal is closed */
|
|
38
|
-
onOpenChange: (open: boolean) => void;
|
|
39
|
-
/** Migration status */
|
|
40
|
-
status: MigrationStatus;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface CodeBlockProps {
|
|
44
|
-
code: string;
|
|
45
|
-
label?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function CodeBlock({ code, label }: CodeBlockProps) {
|
|
49
|
-
const [copied, setCopied] = useState(false);
|
|
50
|
-
|
|
51
|
-
const canCopy =
|
|
52
|
-
typeof navigator !== "undefined" && !!navigator.clipboard?.writeText;
|
|
53
|
-
|
|
54
|
-
const handleCopy = async () => {
|
|
55
|
-
if (!canCopy) {
|
|
56
|
-
toast.error("Clipboard not supported");
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
await navigator.clipboard.writeText(code);
|
|
62
|
-
setCopied(true);
|
|
63
|
-
window.setTimeout(() => setCopied(false), 2000);
|
|
64
|
-
toast.success("Copied to clipboard");
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error("Failed to copy:", error);
|
|
67
|
-
toast.error("Failed to copy to clipboard");
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<div className="relative">
|
|
73
|
-
{label && (
|
|
74
|
-
<div className="mb-2 text-sm font-medium text-muted-foreground">
|
|
75
|
-
{label}
|
|
76
|
-
</div>
|
|
77
|
-
)}
|
|
78
|
-
<div className="group relative">
|
|
79
|
-
<pre className="overflow-hidden rounded-md bg-muted p-3 pr-12 text-sm">
|
|
80
|
-
<code className="block whitespace-pre-wrap break-all">{code}</code>
|
|
81
|
-
</pre>
|
|
82
|
-
<Button
|
|
83
|
-
type="button"
|
|
84
|
-
size="icon"
|
|
85
|
-
variant="ghost"
|
|
86
|
-
className="absolute right-2 top-2 h-8 w-8"
|
|
87
|
-
onClick={handleCopy}
|
|
88
|
-
disabled={!canCopy}
|
|
89
|
-
>
|
|
90
|
-
{copied ? (
|
|
91
|
-
<Check className="h-4 w-4 text-green-600" />
|
|
92
|
-
) : (
|
|
93
|
-
<Copy className="h-4 w-4" />
|
|
94
|
-
)}
|
|
95
|
-
<span className="sr-only">
|
|
96
|
-
{copied ? "Copied" : "Copy code"}
|
|
97
|
-
</span>
|
|
98
|
-
</Button>
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function MigrationModal({
|
|
105
|
-
open,
|
|
106
|
-
onOpenChange,
|
|
107
|
-
status,
|
|
108
|
-
}: MigrationModalProps) {
|
|
109
|
-
const config = getSupabaseConfig();
|
|
110
|
-
|
|
111
|
-
// Auto-migration state
|
|
112
|
-
const [showAutoMigrate, setShowAutoMigrate] = useState(true);
|
|
113
|
-
const [isMigrating, setIsMigrating] = useState(false);
|
|
114
|
-
const [migrationLogs, setMigrationLogs] = useState<string[]>([]);
|
|
115
|
-
const [dbPassword, setDbPassword] = useState("");
|
|
116
|
-
const [accessToken, setAccessToken] = useState("");
|
|
117
|
-
const logsEndRef = useRef<HTMLDivElement>(null);
|
|
118
|
-
|
|
119
|
-
const projectId = useMemo(() => {
|
|
120
|
-
const url = config?.url;
|
|
121
|
-
if (!url) return "";
|
|
122
|
-
try {
|
|
123
|
-
const host = new URL(url).hostname;
|
|
124
|
-
return host.split(".")[0] || "";
|
|
125
|
-
} catch {
|
|
126
|
-
return "";
|
|
127
|
-
}
|
|
128
|
-
}, [config?.url]);
|
|
129
|
-
|
|
130
|
-
// Scroll logs to bottom
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
if (logsEndRef.current) {
|
|
133
|
-
logsEndRef.current.scrollIntoView({ behavior: "smooth" });
|
|
134
|
-
}
|
|
135
|
-
}, [migrationLogs]);
|
|
136
|
-
|
|
137
|
-
const handleAutoMigrate = async () => {
|
|
138
|
-
if (!projectId) {
|
|
139
|
-
toast.error("Missing Project ID");
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
setIsMigrating(true);
|
|
144
|
-
setMigrationLogs(["Initializing migration..."]);
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
const response = await fetch("/api/migrate", {
|
|
148
|
-
method: "POST",
|
|
149
|
-
headers: { "Content-Type": "application/json" },
|
|
150
|
-
body: JSON.stringify({
|
|
151
|
-
projectRef: projectId,
|
|
152
|
-
dbPassword,
|
|
153
|
-
accessToken,
|
|
154
|
-
}),
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
if (!response.ok) {
|
|
158
|
-
throw new Error(
|
|
159
|
-
`Server returned ${response.status}: ${response.statusText}`,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const reader = response.body?.getReader();
|
|
164
|
-
if (!reader) throw new Error("No response stream received.");
|
|
165
|
-
|
|
166
|
-
const decoder = new TextDecoder();
|
|
167
|
-
|
|
168
|
-
while (true) {
|
|
169
|
-
const { done, value } = await reader.read();
|
|
170
|
-
if (done) break;
|
|
171
|
-
|
|
172
|
-
const text = decoder.decode(value);
|
|
173
|
-
const lines = text.split("\n").filter(Boolean);
|
|
174
|
-
setMigrationLogs((prev) => [...prev, ...lines]);
|
|
175
|
-
}
|
|
176
|
-
} catch (err) {
|
|
177
|
-
console.error(err);
|
|
178
|
-
setMigrationLogs((prev) => [
|
|
179
|
-
...prev,
|
|
180
|
-
`Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
181
|
-
]);
|
|
182
|
-
toast.error("Migration failed. Check logs for details.");
|
|
183
|
-
} finally {
|
|
184
|
-
setIsMigrating(false);
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
return (
|
|
189
|
-
<Dialog
|
|
190
|
-
open={open}
|
|
191
|
-
onOpenChange={(val) => !isMigrating && onOpenChange(val)}
|
|
192
|
-
>
|
|
193
|
-
<DialogContent className="max-h-[90vh] sm:max-w-5xl overflow-y-auto">
|
|
194
|
-
<DialogHeader>
|
|
195
|
-
<DialogTitle className="flex items-center gap-2 text-xl">
|
|
196
|
-
<AlertTriangle className="h-6 w-6 text-red-700 dark:text-red-600" />
|
|
197
|
-
Database Migration Required
|
|
198
|
-
</DialogTitle>
|
|
199
|
-
<DialogDescription>
|
|
200
|
-
Your application version ({status.appVersion}) requires a database schema update to function correctly.
|
|
201
|
-
</DialogDescription>
|
|
202
|
-
</DialogHeader>
|
|
203
|
-
|
|
204
|
-
<div className="space-y-6">
|
|
205
|
-
{/* Overview Alert */}
|
|
206
|
-
<Alert>
|
|
207
|
-
<Info className="h-4 w-4" />
|
|
208
|
-
<AlertDescription>
|
|
209
|
-
<strong>Why is this needed?</strong>
|
|
210
|
-
<ul className="mt-2 list-inside list-disc space-y-1 text-sm">
|
|
211
|
-
<li>
|
|
212
|
-
Updates your database schema to match version {status.appVersion}
|
|
213
|
-
</li>
|
|
214
|
-
<li>
|
|
215
|
-
Enables new features and performance improvements
|
|
216
|
-
</li>
|
|
217
|
-
<li>
|
|
218
|
-
Your existing data will be preserved (safe migration)
|
|
219
|
-
</li>
|
|
220
|
-
</ul>
|
|
221
|
-
</AlertDescription>
|
|
222
|
-
</Alert>
|
|
223
|
-
|
|
224
|
-
{/* Mode Selection Tabs */}
|
|
225
|
-
<div className="flex border-b">
|
|
226
|
-
<button
|
|
227
|
-
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${showAutoMigrate ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`}
|
|
228
|
-
onClick={() => setShowAutoMigrate(true)}
|
|
229
|
-
>
|
|
230
|
-
Automatic (Recommended)
|
|
231
|
-
</button>
|
|
232
|
-
<button
|
|
233
|
-
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${!showAutoMigrate ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`}
|
|
234
|
-
onClick={() => setShowAutoMigrate(false)}
|
|
235
|
-
>
|
|
236
|
-
Manual CLI
|
|
237
|
-
</button>
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
{showAutoMigrate ? (
|
|
241
|
-
<div className="space-y-4 py-2">
|
|
242
|
-
<div className="rounded-lg border bg-card text-card-foreground shadow-sm p-6">
|
|
243
|
-
<h3 className="text-lg font-semibold mb-2">
|
|
244
|
-
Automated Migration
|
|
245
|
-
</h3>
|
|
246
|
-
<p className="text-sm text-muted-foreground mb-4">
|
|
247
|
-
Run the migration directly from your browser. Requires your database password.
|
|
248
|
-
</p>
|
|
249
|
-
|
|
250
|
-
<div className="grid gap-4">
|
|
251
|
-
<div className="grid gap-2">
|
|
252
|
-
<Label htmlFor="project-id">
|
|
253
|
-
Supabase Project ID
|
|
254
|
-
</Label>
|
|
255
|
-
<Input
|
|
256
|
-
id="project-id"
|
|
257
|
-
value={projectId}
|
|
258
|
-
disabled
|
|
259
|
-
readOnly
|
|
260
|
-
className="bg-muted"
|
|
261
|
-
/>
|
|
262
|
-
</div>
|
|
263
|
-
|
|
264
|
-
<div className="grid gap-2">
|
|
265
|
-
<div className="flex justify-between items-center">
|
|
266
|
-
<Label htmlFor="access-token">
|
|
267
|
-
Access Token (Optional)
|
|
268
|
-
</Label>
|
|
269
|
-
<a
|
|
270
|
-
href="https://supabase.com/dashboard/account/tokens"
|
|
271
|
-
target="_blank"
|
|
272
|
-
rel="noopener noreferrer"
|
|
273
|
-
className="text-xs text-primary hover:underline flex items-center gap-1"
|
|
274
|
-
>
|
|
275
|
-
Generate Token <ExternalLink className="h-3 w-3" />
|
|
276
|
-
</a>
|
|
277
|
-
</div>
|
|
278
|
-
<Input
|
|
279
|
-
id="access-token"
|
|
280
|
-
type="password"
|
|
281
|
-
placeholder="sbp_..."
|
|
282
|
-
value={accessToken}
|
|
283
|
-
onChange={(e) => setAccessToken(e.target.value)}
|
|
284
|
-
disabled={isMigrating}
|
|
285
|
-
/>
|
|
286
|
-
<p className="text-xs text-muted-foreground">
|
|
287
|
-
Recommended for more reliable authentication.
|
|
288
|
-
</p>
|
|
289
|
-
</div>
|
|
290
|
-
|
|
291
|
-
<div className="grid gap-2">
|
|
292
|
-
<Label htmlFor="db-password">
|
|
293
|
-
Database Password
|
|
294
|
-
</Label>
|
|
295
|
-
<Input
|
|
296
|
-
id="db-password"
|
|
297
|
-
type="password"
|
|
298
|
-
placeholder="Your database password"
|
|
299
|
-
value={dbPassword}
|
|
300
|
-
onChange={(e) => setDbPassword(e.target.value)}
|
|
301
|
-
disabled={isMigrating}
|
|
302
|
-
/>
|
|
303
|
-
<p className="text-xs text-muted-foreground">
|
|
304
|
-
Required if no access token is provided.
|
|
305
|
-
</p>
|
|
306
|
-
</div>
|
|
307
|
-
|
|
308
|
-
<Button
|
|
309
|
-
onClick={handleAutoMigrate}
|
|
310
|
-
disabled={isMigrating}
|
|
311
|
-
className="w-full"
|
|
312
|
-
>
|
|
313
|
-
{isMigrating ? (
|
|
314
|
-
<>
|
|
315
|
-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
316
|
-
Migrating...
|
|
317
|
-
</>
|
|
318
|
-
) : (
|
|
319
|
-
<>
|
|
320
|
-
<Terminal className="mr-2 h-4 w-4" />
|
|
321
|
-
Start Migration
|
|
322
|
-
</>
|
|
323
|
-
)}
|
|
324
|
-
</Button>
|
|
325
|
-
</div>
|
|
326
|
-
</div>
|
|
327
|
-
|
|
328
|
-
{/* Logs Terminal */}
|
|
329
|
-
<div className="rounded-lg border bg-black text-white font-mono text-xs p-4 h-64 overflow-y-auto">
|
|
330
|
-
{migrationLogs.length === 0 ? (
|
|
331
|
-
<div className="text-gray-500 italic">
|
|
332
|
-
Waiting to start...
|
|
333
|
-
</div>
|
|
334
|
-
) : (
|
|
335
|
-
migrationLogs.map((log, i) => (
|
|
336
|
-
<div key={i} className="mb-1 whitespace-pre-wrap">
|
|
337
|
-
{log}
|
|
338
|
-
</div>
|
|
339
|
-
))
|
|
340
|
-
)}
|
|
341
|
-
<div ref={logsEndRef} />
|
|
342
|
-
</div>
|
|
343
|
-
</div>
|
|
344
|
-
) : (
|
|
345
|
-
// Manual Instructions
|
|
346
|
-
<>
|
|
347
|
-
{/* Step 1: Prerequisites */}
|
|
348
|
-
<div>
|
|
349
|
-
<h4 className="mb-3 flex items-center gap-2 font-semibold">
|
|
350
|
-
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
|
|
351
|
-
1
|
|
352
|
-
</span>
|
|
353
|
-
Prerequisites
|
|
354
|
-
</h4>
|
|
355
|
-
<div className="ml-8 space-y-3">
|
|
356
|
-
<p className="text-sm text-muted-foreground">
|
|
357
|
-
You will need the following before proceeding:
|
|
358
|
-
</p>
|
|
359
|
-
<ul className="list-inside list-disc space-y-1 text-sm">
|
|
360
|
-
<li>
|
|
361
|
-
Supabase CLI installed
|
|
362
|
-
</li>
|
|
363
|
-
<li>
|
|
364
|
-
Project ID:{" "}
|
|
365
|
-
<code className="rounded bg-muted px-1 py-0.5 text-xs">
|
|
366
|
-
{projectId || "your-project-id"}
|
|
367
|
-
</code>
|
|
368
|
-
</li>
|
|
369
|
-
<li>
|
|
370
|
-
Database Password
|
|
371
|
-
</li>
|
|
372
|
-
</ul>
|
|
373
|
-
</div>
|
|
374
|
-
</div>
|
|
375
|
-
|
|
376
|
-
{/* Step 2: Install Supabase CLI */}
|
|
377
|
-
<div>
|
|
378
|
-
<h4 className="mb-3 flex items-center gap-2 font-semibold">
|
|
379
|
-
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
|
|
380
|
-
2
|
|
381
|
-
</span>
|
|
382
|
-
Install Supabase CLI
|
|
383
|
-
</h4>
|
|
384
|
-
<div className="ml-8 space-y-3">
|
|
385
|
-
<div className="space-y-2">
|
|
386
|
-
<p className="text-sm font-medium">
|
|
387
|
-
MacOS (Brew)
|
|
388
|
-
</p>
|
|
389
|
-
<CodeBlock code="brew install supabase/tap/supabase" />
|
|
390
|
-
</div>
|
|
391
|
-
|
|
392
|
-
<div className="space-y-2">
|
|
393
|
-
<p className="text-sm font-medium">
|
|
394
|
-
Windows (Scoop)
|
|
395
|
-
</p>
|
|
396
|
-
<CodeBlock
|
|
397
|
-
code={`scoop bucket add supabase https://github.com/supabase/scoop-bucket.git\nscoop install supabase`}
|
|
398
|
-
/>
|
|
399
|
-
</div>
|
|
400
|
-
|
|
401
|
-
<div className="space-y-2">
|
|
402
|
-
<p className="text-sm font-medium">
|
|
403
|
-
NPM (Universal)
|
|
404
|
-
</p>
|
|
405
|
-
<CodeBlock code="npm install -g supabase" />
|
|
406
|
-
</div>
|
|
407
|
-
</div>
|
|
408
|
-
</div>
|
|
409
|
-
|
|
410
|
-
{/* Step 3: Run Migration */}
|
|
411
|
-
<div>
|
|
412
|
-
<h4 className="mb-3 flex items-center gap-2 font-semibold">
|
|
413
|
-
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
|
|
414
|
-
3
|
|
415
|
-
</span>
|
|
416
|
-
Run Migration
|
|
417
|
-
</h4>
|
|
418
|
-
<div className="ml-8 space-y-3">
|
|
419
|
-
<p className="text-sm text-muted-foreground">
|
|
420
|
-
Run the built-in migration tool:
|
|
421
|
-
</p>
|
|
422
|
-
<CodeBlock code="npx email-automator migrate" />
|
|
423
|
-
</div>
|
|
424
|
-
</div>
|
|
425
|
-
</>
|
|
426
|
-
)}
|
|
427
|
-
|
|
428
|
-
{/* Troubleshooting */}
|
|
429
|
-
<Alert className="border-red-200 bg-red-50 dark:border-red-900/40 dark:bg-red-950/20">
|
|
430
|
-
<AlertTriangle className="h-4 w-4 text-red-700 dark:text-red-600" />
|
|
431
|
-
<AlertDescription>
|
|
432
|
-
<strong>Troubleshooting</strong>
|
|
433
|
-
<ul className="mt-2 list-inside list-disc space-y-1 text-sm">
|
|
434
|
-
<li>
|
|
435
|
-
Try logging out: <code>supabase logout</code>
|
|
436
|
-
</li>
|
|
437
|
-
<li>
|
|
438
|
-
Verify your database password is correct
|
|
439
|
-
</li>
|
|
440
|
-
</ul>
|
|
441
|
-
</AlertDescription>
|
|
442
|
-
</Alert>
|
|
443
|
-
</div>
|
|
444
|
-
|
|
445
|
-
<DialogFooter>
|
|
446
|
-
<Button
|
|
447
|
-
type="button"
|
|
448
|
-
variant="outline"
|
|
449
|
-
onClick={() => onOpenChange(false)}
|
|
450
|
-
disabled={isMigrating}
|
|
451
|
-
>
|
|
452
|
-
Close
|
|
453
|
-
</Button>
|
|
454
|
-
</DialogFooter>
|
|
455
|
-
</DialogContent>
|
|
456
|
-
</Dialog>
|
|
457
|
-
);
|
|
458
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MigrationPulseIndicator Component
|
|
3
|
-
*
|
|
4
|
-
* Shows a pulsing dot indicator when migration is needed but notification is dismissed.
|
|
5
|
-
* Provides a subtle, persistent reminder without being intrusive.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { AlertTriangle } from "lucide-react";
|
|
9
|
-
import { Button } from "../ui/button";
|
|
10
|
-
// Tooltip not yet imported/ported, so simplified for now to just a Button
|
|
11
|
-
// If we want tooltip we need to port it too, but omitting for simplicity as it wasn't in list
|
|
12
|
-
|
|
13
|
-
interface MigrationPulseIndicatorProps {
|
|
14
|
-
/** Callback when user clicks the indicator */
|
|
15
|
-
onClick: () => void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function MigrationPulseIndicator({
|
|
19
|
-
onClick,
|
|
20
|
-
}: MigrationPulseIndicatorProps) {
|
|
21
|
-
return (
|
|
22
|
-
<Button
|
|
23
|
-
variant="ghost"
|
|
24
|
-
size="icon"
|
|
25
|
-
className="relative"
|
|
26
|
-
onClick={onClick}
|
|
27
|
-
title="Database Update Available"
|
|
28
|
-
>
|
|
29
|
-
<AlertTriangle className="h-5 w-5 text-red-700 dark:text-red-600" />
|
|
30
|
-
{/* Pulsing dot */}
|
|
31
|
-
<span className="absolute right-0 top-0 flex h-2 w-2">
|
|
32
|
-
<span className="absolute inline-flex h-full w-full rounded-full bg-red-600/70 opacity-75 motion-safe:animate-ping motion-reduce:animate-none" />
|
|
33
|
-
<span className="relative inline-flex h-2 w-2 rounded-full bg-red-700 dark:bg-red-600" />
|
|
34
|
-
</span>
|
|
35
|
-
<span className="sr-only">Database migration pending</span>
|
|
36
|
-
</Button>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Moon, Sun } from "lucide-react"
|
|
2
|
-
import { useTheme } from "../components/theme-provider"
|
|
3
|
-
import { Button } from "../components/ui/button"
|
|
4
|
-
|
|
5
|
-
export function ModeToggle() {
|
|
6
|
-
const { setTheme, theme } = useTheme()
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<Button
|
|
10
|
-
variant="ghost"
|
|
11
|
-
size="icon"
|
|
12
|
-
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
13
|
-
className="text-muted-foreground hover:text-foreground"
|
|
14
|
-
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
|
|
15
|
-
>
|
|
16
|
-
{theme === 'dark' ? (
|
|
17
|
-
<Sun className="h-[1.2rem] w-[1.2rem]" />
|
|
18
|
-
) : (
|
|
19
|
-
<Moon className="h-[1.2rem] w-[1.2rem]" />
|
|
20
|
-
)}
|
|
21
|
-
<span className="sr-only">Toggle theme</span>
|
|
22
|
-
</Button>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useEffect, useState } from "react"
|
|
2
|
-
|
|
3
|
-
type Theme = "dark" | "light" | "system"
|
|
4
|
-
|
|
5
|
-
type ThemeProviderProps = {
|
|
6
|
-
children: React.ReactNode
|
|
7
|
-
defaultTheme?: Theme
|
|
8
|
-
storageKey?: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type ThemeProviderState = {
|
|
12
|
-
theme: Theme
|
|
13
|
-
setTheme: (theme: Theme) => void
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const initialState: ThemeProviderState = {
|
|
17
|
-
theme: "system",
|
|
18
|
-
setTheme: () => null,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
|
|
22
|
-
|
|
23
|
-
export function ThemeProvider({
|
|
24
|
-
children,
|
|
25
|
-
defaultTheme = "system",
|
|
26
|
-
storageKey = "vite-ui-theme",
|
|
27
|
-
}: ThemeProviderProps) {
|
|
28
|
-
const [theme, setTheme] = useState<Theme>(
|
|
29
|
-
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
const root = window.document.documentElement
|
|
34
|
-
|
|
35
|
-
root.classList.remove("light", "dark")
|
|
36
|
-
|
|
37
|
-
if (theme === "system") {
|
|
38
|
-
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
|
39
|
-
.matches
|
|
40
|
-
? "dark"
|
|
41
|
-
: "light"
|
|
42
|
-
|
|
43
|
-
root.classList.add(systemTheme)
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
root.classList.add(theme)
|
|
48
|
-
}, [theme])
|
|
49
|
-
|
|
50
|
-
const value = {
|
|
51
|
-
theme,
|
|
52
|
-
setTheme: (theme: Theme) => {
|
|
53
|
-
localStorage.setItem(storageKey, theme)
|
|
54
|
-
setTheme(theme)
|
|
55
|
-
},
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<ThemeProviderContext.Provider value={value}>
|
|
60
|
-
{children}
|
|
61
|
-
</ThemeProviderContext.Provider>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export const useTheme = () => {
|
|
66
|
-
const context = useContext(ThemeProviderContext)
|
|
67
|
-
|
|
68
|
-
if (context === undefined)
|
|
69
|
-
throw new Error("useTheme must be used within a ThemeProvider")
|
|
70
|
-
|
|
71
|
-
return context
|
|
72
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
-
|
|
4
|
-
import { cn } from "@/lib/utils"
|
|
5
|
-
|
|
6
|
-
const alertVariants = cva(
|
|
7
|
-
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
|
8
|
-
{
|
|
9
|
-
variants: {
|
|
10
|
-
variant: {
|
|
11
|
-
default: "bg-card text-card-foreground",
|
|
12
|
-
destructive:
|
|
13
|
-
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
defaultVariants: {
|
|
17
|
-
variant: "default",
|
|
18
|
-
},
|
|
19
|
-
}
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
function Alert({
|
|
23
|
-
className,
|
|
24
|
-
variant,
|
|
25
|
-
...props
|
|
26
|
-
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
27
|
-
return (
|
|
28
|
-
<div
|
|
29
|
-
data-slot="alert"
|
|
30
|
-
role="alert"
|
|
31
|
-
className={cn(alertVariants({ variant }), className)}
|
|
32
|
-
{...props}
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
38
|
-
return (
|
|
39
|
-
<div
|
|
40
|
-
data-slot="alert-title"
|
|
41
|
-
className={cn(
|
|
42
|
-
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
|
43
|
-
className
|
|
44
|
-
)}
|
|
45
|
-
{...props}
|
|
46
|
-
/>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function AlertDescription({
|
|
51
|
-
className,
|
|
52
|
-
...props
|
|
53
|
-
}: React.ComponentProps<"div">) {
|
|
54
|
-
return (
|
|
55
|
-
<div
|
|
56
|
-
data-slot="alert-description"
|
|
57
|
-
className={cn(
|
|
58
|
-
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
|
59
|
-
className
|
|
60
|
-
)}
|
|
61
|
-
{...props}
|
|
62
|
-
/>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export { Alert, AlertTitle, AlertDescription }
|