feat: add URL-driven workspace state and streamed Codex research

This commit is contained in:
2026-09-28 20:08:28 +09:00
parent 85d23a328b
commit 8085ad2f90
207 changed files with 15287 additions and 16026 deletions
+51 -64
View File
@@ -1,10 +1,10 @@
import { ArrowRight, LockKeyhole } from 'lucide-react'
import { type FormEvent, type ReactNode, useState } from 'react'
import { Button } from '#/components/ui/button'
import { Input } from '#/components/ui/input'
import { Label } from '#/components/ui/label'
import { authRequest } from './auth-client'
import './auth.css'
import { ArrowRight, LockKeyhole } from "lucide-react";
import { type FormEvent, type ReactNode, useState } from "react";
import { Button } from "#/components/ui/button";
import { Input } from "#/components/ui/input";
import { Label } from "#/components/ui/label";
import { authRequest } from "./auth-client";
import { safeReturnTo } from "./return-to";
export function AuthFrame({ children }: { children: ReactNode }) {
return (
@@ -17,78 +17,70 @@ export function AuthFrame({ children }: { children: ReactNode }) {
{children}
</div>
</main>
)
);
}
export function LoginPage() {
return <CredentialsForm setup={false} />
return <CredentialsForm setup={false} />;
}
export function SetupPage() {
return <CredentialsForm setup />
return <CredentialsForm setup />;
}
function formText(data: FormData, name: string) {
const value = data.get(name);
return typeof value === "string" ? value : "";
}
function CredentialsForm({ setup }: { setup: boolean }) {
const [pending, setPending] = useState(false)
const [error, setError] = useState('')
const [pending, setPending] = useState(false);
const [error, setError] = useState("");
async function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
if (pending) return
const data = new FormData(event.currentTarget)
const email = String(data.get('email') ?? '').trim()
const password = String(data.get('password') ?? '')
if (setup && password !== data.get('confirmPassword')) {
setError('Passwords do not match.')
return
event.preventDefault();
if (pending) return;
const data = new FormData(event.currentTarget);
const email = formText(data, "email").trim();
const password = formText(data, "password");
if (setup && password !== data.get("confirmPassword")) {
setError("Passwords do not match.");
return;
}
setPending(true)
setError('')
setPending(true);
setError("");
try {
const state = await authRequest(
setup
? {
action: 'setup',
action: "setup",
email,
password,
name: String(data.get('name') ?? '').trim(),
setupToken: String(data.get('setupToken') ?? '').trim(),
name: formText(data, "name").trim(),
setupToken: formText(data, "setupToken").trim(),
}
: { action: 'login', email, password },
)
: { action: "login", email, password },
);
if (!state.owner?.onboardingCompletedAt) {
window.location.assign('/onboarding')
return
window.location.assign("/onboarding");
return;
}
const returnTo = new URLSearchParams(window.location.search).get(
'returnTo',
)
const destination =
returnTo &&
['/', '/deck', '/support', '/journal', '/inbox', '/vitals'].includes(
returnTo,
)
? returnTo
: '/'
window.location.assign(destination)
const returnTo = new URLSearchParams(window.location.search).get("returnTo");
window.location.assign(safeReturnTo(returnTo));
} catch (cause) {
setError(
cause instanceof Error
? cause.message
: 'Unable to sign in. Please try again.',
)
setPending(false)
setError(cause instanceof Error ? cause.message : "Unable to sign in. Please try again.");
setPending(false);
}
}
return (
<AuthFrame>
<header className="auth-heading">
<h1>{setup ? 'Make yourself at home.' : 'Welcome back.'}</h1>
<h1>{setup ? "Make yourself at home." : "Welcome back."}</h1>
<p>
{setup
? 'Create the owner account for your personal workspace.'
: 'Sign in to your personal workspace.'}
? "Create the owner account for your personal workspace."
: "Sign in to your personal workspace."}
</p>
</header>
<form onSubmit={submit} className="auth-form" aria-busy={pending}>
@@ -123,11 +115,11 @@ function CredentialsForm({ setup }: { setup: boolean }) {
id="password"
name="password"
type="password"
autoComplete={setup ? 'new-password' : 'current-password'}
autoComplete={setup ? "new-password" : "current-password"}
required
minLength={setup ? 15 : undefined}
maxLength={128}
aria-describedby={setup ? 'password-hint' : undefined}
aria-describedby={setup ? "password-hint" : undefined}
disabled={pending}
/>
{setup && (
@@ -173,27 +165,22 @@ function CredentialsForm({ setup }: { setup: boolean }) {
{error}
</p>
)}
<Button
type="submit"
size="lg"
disabled={pending}
className="auth-submit"
>
<Button type="submit" size="lg" disabled={pending} className="auth-submit">
{pending
? setup
? 'Creating account…'
: 'Signing in…'
? "Creating account…"
: "Signing in…"
: setup
? 'Create account'
: 'Sign in'}
? "Create account"
: "Sign in"}
<ArrowRight aria-hidden="true" />
</Button>
</form>
<p className="auth-footnote">
{setup
? 'This workspace has one owner. Connections can be added later.'
: 'A private workspace. Access is limited to its owner.'}
? "This workspace has one owner. Connections can be added later."
: "A private workspace. Access is limited to its owner."}
</p>
</AuthFrame>
)
);
}