mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
6d8f3a07ee
B8.1/B8.2/B8.3. Replaces every process.env.NEXT_PUBLIC_* read in
shell-dashboard consumer code with the runtime-config readers, all
in a single commit so the tree never sits red across a partial
rename.
B8.1 — src/lib/pb.ts: replace the eager module-load read with a
lazy getPb() getter. The previous `const resolvedUrl =
resolvePbUrl()` at module top froze the URL at import time, which
defeated runtime injection. Now the PB client is constructed on
first getPb() call from runtimeConfig.pocketbaseUrl. The
PocketBase instance is returned directly (NOT wrapped in a Proxy)
so `instanceof PocketBase`, detached methods, and this-sensitive
chains all work without surprise. pbIsMisconfigured is converted
from a const boolean to a function — every runtime-config-derived
export in the module is now a function call.
B8.2 — src/lib/ops-api.ts: resolveBaseUrl() now reads
runtimeConfig.opsBaseUrl on the client (gated on typeof window so
SSR / server tests still fall through to the explicit-param or
/api/ops fallback). The runtime-config throw is caught and
treated as 'no override' so a missing __SHOWCASE_CONFIG__ wiring
bug degrades to the safe same-origin rewrite path.
B8.3 — src/components/feature-grid.tsx: resolveShellUrl() body
collapses to `return getRuntimeConfig().shellUrl`. The sentinel
about:blank#shell-url-missing now lives in runtime-config.ts (its
single source of truth) instead of being re-implemented here.
Consumers and test mocks updated in the same commit:
- hooks: useBaseline.ts, useLiveStatus.ts, useLastTransition.ts —
swap `pb` import for `getPb`, capture `const pb = getPb()`
at hook/effect entry, change pbIsMisconfigured reads to
pbIsMisconfigured() calls.
- pb-auth-prompt.tsx — capture getPb() once at the top of the
submit handler.
- test mocks: useLiveStatus.test.tsx, useLastTransition.test.tsx,
__tests__/useBaseline.test.ts, cell-pieces.test.tsx — vi.mock
stubs return { getPb: () => pb, pbIsMisconfigured: () => false }
to match the new function-form API.
- use-probes.integration.test.tsx + ops-api.test.ts — swap the
env-var snapshot/clear pattern for the window.__SHOWCASE_CONFIG__
pattern that mirrors the production code path.
129 lines
4.1 KiB
TypeScript
129 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState, type FormEvent } from "react";
|
|
import { getPb } from "@/lib/pb";
|
|
|
|
export interface PbAuthPromptProps {
|
|
onSuccess: () => void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
/**
|
|
* Modal login prompt for PocketBase admin authentication.
|
|
* Used when entering edit mode on the Baseline tab.
|
|
*
|
|
* Tries PB 0.22+ `_superusers` collection first, then falls back
|
|
* to the legacy `pb.admins` API (PB 0.21).
|
|
*/
|
|
export function PbAuthPrompt({ onSuccess, onCancel }: PbAuthPromptProps) {
|
|
const [email, setEmail] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const canSubmit = email.trim().length > 0 && password.length > 0 && !loading;
|
|
|
|
async function handleSubmit(e: FormEvent) {
|
|
e.preventDefault();
|
|
setError(null);
|
|
setLoading(true);
|
|
|
|
const pb = getPb();
|
|
|
|
try {
|
|
// PB 0.22+ uses a _superusers collection for admin auth
|
|
await pb
|
|
.collection("_superusers")
|
|
.authWithPassword(email.trim(), password);
|
|
onSuccess();
|
|
return;
|
|
} catch {
|
|
// Fall through to legacy API
|
|
}
|
|
|
|
try {
|
|
// PB 0.21 legacy admin auth
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
await (pb as any).admins.authWithPassword(email.trim(), password);
|
|
onSuccess();
|
|
} catch (err: unknown) {
|
|
const message =
|
|
err instanceof Error ? err.message : "Authentication failed.";
|
|
setError(message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
/* Full-screen backdrop */
|
|
<div
|
|
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/50"
|
|
onClick={onCancel}
|
|
role="presentation"
|
|
>
|
|
{/* Modal card — stop click propagation so clicking inside doesn't cancel */}
|
|
<form
|
|
onClick={(e) => e.stopPropagation()}
|
|
onSubmit={handleSubmit}
|
|
className="w-80 rounded-lg border border-[var(--border)] bg-[var(--bg-surface)] p-5 shadow"
|
|
>
|
|
{/* Title */}
|
|
<h2 className="text-sm font-semibold text-[var(--text)]">
|
|
PocketBase Login
|
|
</h2>
|
|
|
|
{/* Subtitle */}
|
|
<p className="mt-1 text-xs text-[var(--text-muted)]">
|
|
Admin credentials required to edit baseline data.
|
|
</p>
|
|
|
|
{/* Error display */}
|
|
{error && <p className="mt-3 text-xs text-[var(--danger)]">{error}</p>}
|
|
|
|
{/* Email input */}
|
|
<label className="mt-4 block">
|
|
<span className="text-xs text-[var(--text-muted)]">Email</span>
|
|
<input
|
|
type="email"
|
|
autoFocus
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
className="mt-1 block w-full rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2.5 py-1.5 text-sm text-[var(--text)] outline-none focus:border-[var(--accent)]"
|
|
placeholder="admin@example.com"
|
|
/>
|
|
</label>
|
|
|
|
{/* Password input */}
|
|
<label className="mt-3 block">
|
|
<span className="text-xs text-[var(--text-muted)]">Password</span>
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
className="mt-1 block w-full rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2.5 py-1.5 text-sm text-[var(--text)] outline-none focus:border-[var(--accent)]"
|
|
/>
|
|
</label>
|
|
|
|
{/* Action buttons */}
|
|
<div className="mt-5 flex items-center justify-end gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={onCancel}
|
|
className="cursor-pointer text-sm text-[var(--text-muted)] hover:text-[var(--text)]"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={!canSubmit}
|
|
className="cursor-pointer rounded bg-[var(--accent)] px-3 py-1.5 text-sm text-white disabled:cursor-not-allowed disabled:opacity-50"
|
|
>
|
|
{loading ? "Logging in…" : "Login"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|