jettypod 4.4.118 → 4.4.121
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/.env +4 -3
- package/Cargo.lock +6450 -0
- package/Cargo.toml +35 -0
- package/README.md +5 -1
- package/TAURI-MIGRATION-PLAN.md +840 -0
- package/apps/dashboard/app/connect-claude/page.tsx +5 -6
- package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
- package/apps/dashboard/app/demo/gates/page.tsx +43 -45
- package/apps/dashboard/app/design-system/page.tsx +868 -0
- package/apps/dashboard/app/globals.css +80 -4
- package/apps/dashboard/app/install-claude/page.tsx +4 -6
- package/apps/dashboard/app/login/page.tsx +72 -54
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +61 -13
- package/apps/dashboard/app/signup/page.tsx +242 -0
- package/apps/dashboard/app/subscribe/page.tsx +0 -2
- package/apps/dashboard/app/tests/page.tsx +37 -4
- package/apps/dashboard/app/welcome/page.tsx +13 -16
- package/apps/dashboard/app/work/[id]/page.tsx +117 -118
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +92 -85
- package/apps/dashboard/components/CardMenu.tsx +45 -12
- package/apps/dashboard/components/ClaudePanel.tsx +771 -850
- package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
- package/apps/dashboard/components/CopyableId.tsx +3 -4
- package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
- package/apps/dashboard/components/DragContext.tsx +134 -63
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +6 -7
- package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +26 -7
- package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
- package/apps/dashboard/components/EpicGroup.tsx +359 -0
- package/apps/dashboard/components/GateCard.tsx +79 -17
- package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
- package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
- package/apps/dashboard/components/JettyLoader.tsx +37 -0
- package/apps/dashboard/components/KanbanBoard.tsx +368 -958
- package/apps/dashboard/components/KanbanCard.tsx +740 -0
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
- package/apps/dashboard/components/MainNav.tsx +38 -73
- package/apps/dashboard/components/MessageBlock.tsx +468 -0
- package/apps/dashboard/components/ModeStartCard.tsx +15 -16
- package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
- package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
- package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
- package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
- package/apps/dashboard/components/ReviewFooter.tsx +139 -0
- package/apps/dashboard/components/SessionList.tsx +19 -19
- package/apps/dashboard/components/SubscribeContent.tsx +91 -47
- package/apps/dashboard/components/TestTree.tsx +16 -16
- package/apps/dashboard/components/TipCard.tsx +16 -17
- package/apps/dashboard/components/Toast.tsx +5 -6
- package/apps/dashboard/components/TypeIcon.tsx +55 -0
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
- package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
- package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
- package/apps/dashboard/components/WorkItemTree.tsx +11 -32
- package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
- package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
- package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
- package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
- package/apps/dashboard/components/ui/Button.tsx +104 -0
- package/apps/dashboard/components/ui/Input.tsx +78 -0
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
- package/apps/dashboard/contexts/UsageContext.tsx +87 -32
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
- package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/constants.ts +43 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1265
- package/apps/dashboard/lib/environment-config.ts +173 -0
- package/apps/dashboard/lib/environment-verification.ts +119 -0
- package/apps/dashboard/lib/kanban-utils.ts +270 -0
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- package/apps/dashboard/lib/run-migrations.js +27 -2
- package/apps/dashboard/lib/service-recovery.ts +326 -0
- package/apps/dashboard/lib/session-state-machine.ts +1 -0
- package/apps/dashboard/lib/session-state-utils.ts +0 -164
- package/apps/dashboard/lib/session-stream-manager.ts +308 -134
- package/apps/dashboard/lib/shadows.ts +7 -0
- package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
- package/apps/dashboard/lib/tauri-bridge.ts +102 -0
- package/apps/dashboard/lib/tauri.ts +106 -0
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -32
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
- package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
- package/apps/dashboard/public/in-flight-seagull.png +0 -0
- package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
- package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
- package/apps/dashboard/public/jettypod_logo.png +0 -0
- package/apps/dashboard/public/pier-icon.png +0 -0
- package/apps/dashboard/public/star-icon.png +0 -0
- package/apps/dashboard/public/wrench-icon.png +0 -0
- package/apps/dashboard/scripts/tauri-build.js +228 -0
- package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
- package/apps/dashboard/scripts/ws-server.js +191 -0
- package/apps/dashboard/src/main.tsx +12 -0
- package/apps/dashboard/src/router.tsx +107 -0
- package/apps/dashboard/src/vite-env.d.ts +1 -0
- package/apps/dashboard/tsconfig.json +7 -12
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
- package/apps/dashboard/vite.config.ts +33 -0
- package/apps/update-server/src/index.ts +228 -80
- package/claude-hooks/global-guardrails.js +14 -13
- package/crates/jettypod-cli/Cargo.toml +19 -0
- package/crates/jettypod-cli/src/commands.rs +1249 -0
- package/crates/jettypod-cli/src/main.rs +595 -0
- package/crates/jettypod-core/Cargo.toml +26 -0
- package/crates/jettypod-core/build.rs +98 -0
- package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
- package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
- package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
- package/crates/jettypod-core/src/auth.rs +294 -0
- package/crates/jettypod-core/src/config.rs +397 -0
- package/crates/jettypod-core/src/db/mod.rs +507 -0
- package/crates/jettypod-core/src/db/recovery.rs +114 -0
- package/crates/jettypod-core/src/db/startup.rs +101 -0
- package/crates/jettypod-core/src/db/validate.rs +149 -0
- package/crates/jettypod-core/src/error.rs +76 -0
- package/crates/jettypod-core/src/git.rs +458 -0
- package/crates/jettypod-core/src/lib.rs +20 -0
- package/crates/jettypod-core/src/sessions.rs +625 -0
- package/crates/jettypod-core/src/skills.rs +556 -0
- package/crates/jettypod-core/src/work.rs +1086 -0
- package/crates/jettypod-core/src/worktree.rs +628 -0
- package/crates/jettypod-core/src/ws.rs +767 -0
- package/cucumber-test.cjs +6 -0
- package/cucumber.js +9 -3
- package/docs/COMMAND_REFERENCE.md +34 -0
- package/hooks/post-checkout +32 -75
- package/hooks/post-merge +111 -10
- package/jest.setup.js +1 -0
- package/jettypod.js +145 -116
- package/lib/bdd-preflight.js +96 -0
- package/lib/chore-taxonomy.js +33 -10
- package/lib/database.js +36 -16
- package/lib/db-watcher.js +1 -1
- package/lib/git-hooks/pre-commit +1 -1
- package/lib/jettypod-backup.js +27 -4
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/027-plan-at-creation-column.js +3 -1
- package/lib/migrations/029-remove-autoincrement.js +307 -0
- package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/migrations/index.js +47 -4
- package/lib/schema.js +10 -5
- package/lib/seed-onboarding.js +1 -1
- package/lib/update-command/index.js +9 -175
- package/lib/work-commands/index.js +144 -19
- package/lib/work-tracking/index.js +148 -27
- package/lib/worktree-diagnostics.js +16 -16
- package/lib/worktree-facade.js +1 -1
- package/lib/worktree-manager.js +8 -8
- package/lib/worktree-reconciler.js +5 -5
- package/package.json +9 -2
- package/scripts/ndjson-to-cucumber-json.js +152 -0
- package/scripts/postinstall.js +25 -0
- package/skills-templates/bug-mode/SKILL.md +79 -20
- package/skills-templates/bug-planning/SKILL.md +25 -29
- package/skills-templates/chore-mode/SKILL.md +171 -69
- package/skills-templates/chore-mode/verification.js +51 -10
- package/skills-templates/chore-planning/SKILL.md +47 -18
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +82 -48
- package/skills-templates/external-transition/SKILL.md +47 -47
- package/skills-templates/feature-planning/SKILL.md +173 -74
- package/skills-templates/production-mode/SKILL.md +69 -49
- package/skills-templates/request-routing/SKILL.md +4 -4
- package/skills-templates/simple-improvement/SKILL.md +74 -29
- package/skills-templates/speed-mode/SKILL.md +217 -141
- package/skills-templates/stable-mode/SKILL.md +148 -89
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
- package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
- package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
- package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
- package/apps/dashboard/app/api/kanban/route.ts +0 -15
- package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
- package/apps/dashboard/app/api/settings/general/route.ts +0 -21
- package/apps/dashboard/app/api/tests/route.ts +0 -9
- package/apps/dashboard/app/api/tests/run/route.ts +0 -82
- package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
- package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
- package/apps/dashboard/app/api/usage/route.ts +0 -17
- package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -43
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
- package/apps/dashboard/electron/ipc-handlers.js +0 -1028
- package/apps/dashboard/electron/main.js +0 -2124
- package/apps/dashboard/electron/preload.js +0 -123
- package/apps/dashboard/electron/session-manager.js +0 -141
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/claude-process-manager.ts +0 -492
- package/apps/dashboard/lib/db-bridge.ts +0 -282
- package/apps/dashboard/lib/prototypes.ts +0 -202
- package/apps/dashboard/lib/test-results-db.ts +0 -307
- package/apps/dashboard/lib/tests.ts +0 -282
- package/apps/dashboard/next.config.js +0 -50
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
- package/docs/bdd-guidance.md +0 -390
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
import { Button } from '@/components/ui/Button';
|
|
4
|
+
import { Input } from '@/components/ui/Input';
|
|
5
|
+
import { isTauri, auth } from '@/lib/tauri-bridge';
|
|
6
|
+
|
|
7
|
+
const API_BASE = 'https://jettypod-update-server.spangbaryn2.workers.dev';
|
|
8
|
+
|
|
9
|
+
export default function SignupPage() {
|
|
10
|
+
const [email, setEmail] = useState('');
|
|
11
|
+
const [otpCode, setOtpCode] = useState('');
|
|
12
|
+
const [otpSent, setOtpSent] = useState(false);
|
|
13
|
+
const [isSending, setIsSending] = useState(false);
|
|
14
|
+
const [isVerifying, setIsVerifying] = useState(false);
|
|
15
|
+
const [error, setError] = useState<string | null>(null);
|
|
16
|
+
|
|
17
|
+
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
18
|
+
|
|
19
|
+
// Redirect already-authenticated users to dashboard
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
async function checkIfAlreadyAuthenticated() {
|
|
22
|
+
if (isTauri()) {
|
|
23
|
+
try {
|
|
24
|
+
const status = await auth.getStatus();
|
|
25
|
+
if (status.authenticated) {
|
|
26
|
+
const path = await auth.getPostLoginPath() || '/';
|
|
27
|
+
window.location.href = path;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// Ignore — stay on signup page
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
checkIfAlreadyAuthenticated();
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
return () => {
|
|
39
|
+
if (pollRef.current) clearInterval(pollRef.current);
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const handleGoogleSignUp = () => {
|
|
44
|
+
if (!isTauri()) return;
|
|
45
|
+
auth.loginWithGoogle();
|
|
46
|
+
|
|
47
|
+
pollRef.current = setInterval(async () => {
|
|
48
|
+
try {
|
|
49
|
+
const status = await auth.getStatus();
|
|
50
|
+
if (status.authenticated) {
|
|
51
|
+
if (pollRef.current) clearInterval(pollRef.current);
|
|
52
|
+
const path = await auth.getPostLoginPath() || '/';
|
|
53
|
+
window.location.href = path;
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Ignore — keep polling
|
|
57
|
+
}
|
|
58
|
+
}, 1000);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleSendOTP = async (e: React.FormEvent) => {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
setError(null);
|
|
64
|
+
|
|
65
|
+
if (!email.trim() || !email.includes('@')) {
|
|
66
|
+
setError('Please enter a valid email address.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setIsSending(true);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const res = await fetch(`${API_BASE}/auth/otp/send`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: { 'Content-Type': 'application/json' },
|
|
76
|
+
body: JSON.stringify({ email: email.trim().toLowerCase() }),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
const data = await res.json() as { error?: string };
|
|
81
|
+
setError(data.error || 'Failed to send code.');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setOtpSent(true);
|
|
86
|
+
} catch {
|
|
87
|
+
setError('Failed to send code. Check your connection.');
|
|
88
|
+
} finally {
|
|
89
|
+
setIsSending(false);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleVerifyOTP = async (e: React.FormEvent) => {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
setError(null);
|
|
96
|
+
|
|
97
|
+
if (!otpCode.trim()) {
|
|
98
|
+
setError('Please enter the code from your email.');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setIsVerifying(true);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const res = await fetch(`${API_BASE}/auth/otp/verify`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
body: JSON.stringify({ email: email.trim().toLowerCase(), code: otpCode.trim() }),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
const data = await res.json() as { error?: string };
|
|
113
|
+
setError(data.error || 'Invalid or expired code.');
|
|
114
|
+
setIsVerifying(false);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const data = await res.json() as { token: string; user: { id: string; email: string; plan: string } };
|
|
119
|
+
|
|
120
|
+
if (isTauri()) {
|
|
121
|
+
await auth.saveToken(data.token, data.user);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const path = await auth.getPostLoginPath() || '/';
|
|
125
|
+
window.location.href = path;
|
|
126
|
+
} catch {
|
|
127
|
+
setError('Failed to verify code. Check your connection.');
|
|
128
|
+
setIsVerifying(false);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className="flex flex-col items-center justify-center min-h-screen bg-white dark:bg-zinc-900 p-8">
|
|
134
|
+
<div className="max-w-md w-full space-y-10">
|
|
135
|
+
{/* Logo */}
|
|
136
|
+
<div className="flex flex-col items-center space-y-6">
|
|
137
|
+
<img
|
|
138
|
+
src="/jettypod_wordmark.png"
|
|
139
|
+
alt="JettyPod"
|
|
140
|
+
width={160}
|
|
141
|
+
height={40}
|
|
142
|
+
/>
|
|
143
|
+
<h1 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-100 text-center">
|
|
144
|
+
Create your account
|
|
145
|
+
</h1>
|
|
146
|
+
<p className="text-zinc-500 dark:text-zinc-400 text-center">
|
|
147
|
+
Get started free. No credit card required.
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Error */}
|
|
152
|
+
{error && (
|
|
153
|
+
<div className="bg-red-50 dark:bg-red-900/20 border-2 border-red-200 dark:border-red-800 text-red-700 dark:text-red-400 px-5 py-4 rounded-xl text-base">
|
|
154
|
+
{error}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
{/* Google Sign-Up */}
|
|
159
|
+
<div className="pt-4">
|
|
160
|
+
<Button
|
|
161
|
+
onClick={handleGoogleSignUp}
|
|
162
|
+
size="lg"
|
|
163
|
+
fullWidth
|
|
164
|
+
>
|
|
165
|
+
Sign up with Google
|
|
166
|
+
</Button>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
{/* Divider */}
|
|
170
|
+
<div className="flex items-center gap-4">
|
|
171
|
+
<div className="flex-1 h-px bg-zinc-200 dark:bg-zinc-700" />
|
|
172
|
+
<span className="text-base text-zinc-400 dark:text-zinc-500">or</span>
|
|
173
|
+
<div className="flex-1 h-px bg-zinc-200 dark:bg-zinc-700" />
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
{/* Email OTP */}
|
|
177
|
+
{!otpSent ? (
|
|
178
|
+
<form onSubmit={handleSendOTP} className="space-y-6">
|
|
179
|
+
<Input
|
|
180
|
+
type="email"
|
|
181
|
+
value={email}
|
|
182
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
183
|
+
placeholder="Enter your email"
|
|
184
|
+
disabled={isSending}
|
|
185
|
+
/>
|
|
186
|
+
<Button
|
|
187
|
+
type="submit"
|
|
188
|
+
variant="secondary"
|
|
189
|
+
size="lg"
|
|
190
|
+
fullWidth
|
|
191
|
+
disabled={isSending || !email.trim()}
|
|
192
|
+
>
|
|
193
|
+
{isSending ? 'Sending code...' : 'Sign up with email'}
|
|
194
|
+
</Button>
|
|
195
|
+
</form>
|
|
196
|
+
) : (
|
|
197
|
+
<form onSubmit={handleVerifyOTP} className="space-y-6">
|
|
198
|
+
<p className="text-base text-zinc-500 dark:text-zinc-400">
|
|
199
|
+
We sent a 6-digit code to <span className="font-medium text-zinc-700 dark:text-zinc-300">{email}</span>
|
|
200
|
+
</p>
|
|
201
|
+
<Input
|
|
202
|
+
type="text"
|
|
203
|
+
value={otpCode}
|
|
204
|
+
onChange={(e) => setOtpCode(e.target.value)}
|
|
205
|
+
placeholder="Enter 6-digit code"
|
|
206
|
+
maxLength={6}
|
|
207
|
+
autoFocus
|
|
208
|
+
disabled={isVerifying}
|
|
209
|
+
className="text-center text-xl tracking-widest font-mono"
|
|
210
|
+
/>
|
|
211
|
+
<Button
|
|
212
|
+
type="submit"
|
|
213
|
+
variant="secondary"
|
|
214
|
+
size="lg"
|
|
215
|
+
fullWidth
|
|
216
|
+
disabled={isVerifying || !otpCode.trim()}
|
|
217
|
+
>
|
|
218
|
+
{isVerifying ? 'Verifying...' : 'Verify code'}
|
|
219
|
+
</Button>
|
|
220
|
+
<Button
|
|
221
|
+
type="button"
|
|
222
|
+
variant="ghost"
|
|
223
|
+
size="sm"
|
|
224
|
+
fullWidth
|
|
225
|
+
onClick={() => { setOtpSent(false); setOtpCode(''); setError(null); }}
|
|
226
|
+
>
|
|
227
|
+
Use a different email
|
|
228
|
+
</Button>
|
|
229
|
+
</form>
|
|
230
|
+
)}
|
|
231
|
+
|
|
232
|
+
{/* Switch to login */}
|
|
233
|
+
<p className="text-center text-base text-zinc-500 dark:text-zinc-400">
|
|
234
|
+
Already have an account?{' '}
|
|
235
|
+
<Link to="/login" className="font-medium hover:underline" style={{ color: '#819D9F' }}>
|
|
236
|
+
Sign in
|
|
237
|
+
</Link>
|
|
238
|
+
</p>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -1,10 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
2
|
import { RealTimeTestsWrapper } from '@/components/RealTimeTestsWrapper';
|
|
3
|
-
|
|
4
|
-
export const dynamic = 'force-dynamic';
|
|
3
|
+
import { prefetch } from '@/lib/data-bridge';
|
|
5
4
|
|
|
6
5
|
export default function TestsPage() {
|
|
7
|
-
const initialData =
|
|
6
|
+
const [initialData, setInitialData] = useState<any>(null);
|
|
7
|
+
const [loading, setLoading] = useState(true);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
async function loadData() {
|
|
11
|
+
try {
|
|
12
|
+
const data = await prefetch.tests();
|
|
13
|
+
setInitialData(data);
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.error('Failed to load test data:', err);
|
|
16
|
+
setInitialData({ suites: [], summary: { total: 0, passing: 0, failing: 0, pending: 0 } });
|
|
17
|
+
} finally {
|
|
18
|
+
setLoading(false);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
loadData();
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
if (loading || !initialData) return (
|
|
25
|
+
<div className="flex-1 max-w-7xl w-full mx-auto px-4 py-4">
|
|
26
|
+
<div className="flex justify-between items-center mb-6">
|
|
27
|
+
<div className="h-8 w-32 bg-zinc-200 dark:bg-zinc-800 rounded" style={{ animation: 'skeleton-pulse 1.5s ease-in-out infinite' }} />
|
|
28
|
+
<div className="flex gap-3">
|
|
29
|
+
{[1, 2, 3, 4].map(i => (
|
|
30
|
+
<div key={i} className="h-10 w-20 bg-zinc-200 dark:bg-zinc-800 rounded-lg" style={{ animation: 'skeleton-pulse 1.5s ease-in-out infinite' }} />
|
|
31
|
+
))}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div className="space-y-3">
|
|
35
|
+
{[1, 2, 3, 4, 5].map(i => (
|
|
36
|
+
<div key={i} className="h-16 bg-zinc-200 dark:bg-zinc-800 rounded-xl" style={{ animation: 'skeleton-pulse 1.5s ease-in-out infinite' }} />
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
8
41
|
|
|
9
42
|
return <RealTimeTestsWrapper initialData={initialData} />;
|
|
10
43
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
1
|
import { useState, useEffect } from 'react';
|
|
4
2
|
import { WelcomeScreen } from '@/components/WelcomeScreen';
|
|
5
|
-
import
|
|
3
|
+
import { isTauri, project } from '@/lib/tauri-bridge';
|
|
4
|
+
import type { RecentProject } from '@/lib/tauri-bridge';
|
|
6
5
|
|
|
7
6
|
export default function WelcomePage() {
|
|
8
7
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -11,11 +10,11 @@ export default function WelcomePage() {
|
|
|
11
10
|
// Load recent projects on mount
|
|
12
11
|
useEffect(() => {
|
|
13
12
|
async function loadRecentProjects() {
|
|
14
|
-
if (!
|
|
13
|
+
if (!isTauri()) {
|
|
15
14
|
return;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
const projects = await
|
|
17
|
+
const projects = await project.getRecent();
|
|
19
18
|
setRecentProjects(projects);
|
|
20
19
|
}
|
|
21
20
|
|
|
@@ -25,12 +24,12 @@ export default function WelcomePage() {
|
|
|
25
24
|
const handleNewProject = async () => {
|
|
26
25
|
setError(null);
|
|
27
26
|
|
|
28
|
-
if (!
|
|
27
|
+
if (!isTauri()) {
|
|
29
28
|
setError('Project creation is only available in the desktop app.');
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
const result = await
|
|
32
|
+
const result = await project.newProject();
|
|
34
33
|
|
|
35
34
|
if (result.canceled) {
|
|
36
35
|
return;
|
|
@@ -47,13 +46,12 @@ export default function WelcomePage() {
|
|
|
47
46
|
const handleOpenProject = async () => {
|
|
48
47
|
setError(null);
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
if (!window.electronAPI?.isElectron) {
|
|
49
|
+
if (!isTauri()) {
|
|
52
50
|
setError('Project selection is only available in the desktop app.');
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
const result = await
|
|
54
|
+
const result = await project.openDialog();
|
|
57
55
|
|
|
58
56
|
if (result.canceled) {
|
|
59
57
|
// User canceled - do nothing
|
|
@@ -69,19 +67,18 @@ export default function WelcomePage() {
|
|
|
69
67
|
window.location.href = '/';
|
|
70
68
|
};
|
|
71
69
|
|
|
72
|
-
const handleSelectRecentProject = async (
|
|
70
|
+
const handleSelectRecentProject = async (p: RecentProject) => {
|
|
73
71
|
setError(null);
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
if (!window.electronAPI?.isElectron) {
|
|
73
|
+
if (!isTauri()) {
|
|
77
74
|
setError('Project selection is only available in the desktop app.');
|
|
78
75
|
return;
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
const result = await
|
|
78
|
+
const result = await project.openRecent(p.path);
|
|
82
79
|
|
|
83
80
|
if (!result.success) {
|
|
84
|
-
setError(
|
|
81
|
+
setError('Failed to open project');
|
|
85
82
|
return;
|
|
86
83
|
}
|
|
87
84
|
|
|
@@ -92,7 +89,7 @@ export default function WelcomePage() {
|
|
|
92
89
|
return (
|
|
93
90
|
<>
|
|
94
91
|
{error && (
|
|
95
|
-
<div className="fixed top-4 left-1/2 transform -translate-x-1/2 bg-red-
|
|
92
|
+
<div className="fixed top-4 left-1/2 transform -translate-x-1/2 bg-red-50 dark:bg-red-900/20 border-2 border-red-200 dark:border-red-800 text-red-700 dark:text-red-400 px-4 py-3 rounded-xl text-base z-50">
|
|
96
93
|
{error}
|
|
97
94
|
</div>
|
|
98
95
|
)}
|