Files
vibn-frontend/components/project-creation/setup-shared.tsx
Mark Henderson 651ddf1e11 Rip out Theia, ship P5.1 attach E2E + Justine UI work-in-progress
Theia rip-out:
- Delete app/api/theia-auth/route.ts (Traefik ForwardAuth shim)
- Delete app/api/projects/[projectId]/workspace/route.ts and
  app/api/projects/prewarm/route.ts (Cloud Run Theia provisioning)
- Delete lib/cloud-run-workspace.ts and lib/coolify-workspace.ts
- Strip provisionTheiaWorkspace + theiaWorkspaceUrl/theiaAppUuid/
  theiaError from app/api/projects/create/route.ts response
- Remove Theia callbackUrl branch in app/auth/page.tsx
- Drop "Open in Theia" button + xterm/Theia PTY copy in build/page.tsx
- Drop theiaWorkspaceUrl from deployment/page.tsx Project type
- Strip Theia IDE line + theia-code-os from advisor + agent-chat
  context strings
- Scrub Theia mention from lib/auth/workspace-auth.ts comment

P5.1 (custom apex domains + DNS):
- lib/coolify.ts + lib/opensrs.ts: nameserver normalization, OpenSRS
  XML auth, Cloud DNS plumbing
- scripts/smoke-attach-e2e.ts: full prod GCP + sandbox OpenSRS +
  prod Coolify smoke covering register/zone/A/NS/PATCH/cleanup

In-progress (Justine onboarding/build, MVP setup, agent telemetry):
- New (justine)/stories, project (home) layouts, mvp-setup, run, tasks
  routes + supporting components
- Project shell + sidebar + nav refactor for the Stackless palette
- Agent session API hardening (sessions, events, stream, approve,
  retry, stop) + atlas-chat, advisor, design-surfaces refresh
- New scripts/sync-db-url-from-coolify.mjs +
  scripts/prisma-db-push.mjs + docker-compose.local-db.yml for
  local Prisma workflows
- lib/dev-bypass.ts, lib/chat-context-refs.ts, lib/prd-sections.ts
- Misc: stories CSS, debug/prisma route, modal-theme, BuildLivePlanPanel

Made-with: Cursor
2026-04-22 18:05:01 -07:00

231 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { ReactNode, CSSProperties } from "react";
import { JM } from "./modal-theme";
export interface SetupProps {
workspace: string;
onClose: () => void;
onBack: () => void;
}
export function FieldLabel({ children }: { children: ReactNode }) {
return (
<label style={{
display: "block", fontSize: 12, fontWeight: 600, color: JM.mid,
marginBottom: 6, fontFamily: JM.fontSans,
}}>
{children}
</label>
);
}
export function ForWhomSelector({
value,
onChange,
}: {
value: "personal" | "client";
onChange: (v: "personal" | "client") => void;
}) {
const cardBase: CSSProperties = {
flex: 1,
border: `1px solid ${JM.border}`,
borderRadius: 9,
padding: 14,
cursor: "pointer",
textAlign: "center" as const,
background: JM.inputBg,
transition: "all 0.15s",
fontFamily: JM.fontSans,
};
const row = (key: "personal" | "client", emoji: string, title: string, sub: string) => {
const sel = value === key;
return (
<button
type="button"
key={key}
onClick={() => onChange(key)}
style={{
...cardBase,
borderColor: sel ? JM.indigo : JM.border,
background: sel ? JM.cream : JM.inputBg,
boxShadow: sel ? "0 0 0 1px rgba(99,102,241,0.2)" : undefined,
}}
>
<div style={{ fontSize: 20, marginBottom: 5 }}>{emoji}</div>
<div style={{ fontSize: 12.5, fontWeight: 600, color: JM.ink }}>{title}</div>
<div style={{ fontSize: 11, color: JM.muted, marginTop: 2 }}>{sub}</div>
</button>
);
};
return (
<div style={{ marginBottom: 22 }}>
<FieldLabel>This project is for</FieldLabel>
<div style={{ display: "flex", gap: 8 }}>
{row("personal", "🧑‍💻", "Myself", "My own product")}
{row("client", "🤝", "A client", "Client project")}
</div>
</div>
);
}
export function SetupHeader({
icon,
label,
tagline,
accent,
onBack,
onClose,
}: {
icon: string;
label: string;
tagline: string;
accent: string;
onBack: () => void;
onClose: () => void;
}) {
return (
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 22 }}>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<button
type="button"
onClick={onBack}
style={{
background: "none", border: "none", cursor: "pointer",
color: JM.muted, fontSize: "1rem", padding: "3px 5px",
borderRadius: 4, lineHeight: 1, flexShrink: 0,
fontFamily: JM.fontSans,
}}
onMouseEnter={e => (e.currentTarget.style.color = JM.ink)}
onMouseLeave={e => (e.currentTarget.style.color = JM.muted)}
>
</button>
<div>
<h2 style={{
fontFamily: JM.fontDisplay, fontSize: 18, fontWeight: 700,
color: JM.ink, margin: 0, marginBottom: 3, letterSpacing: "-0.02em",
}}>
{label}
</h2>
<p style={{
fontSize: 10.5, fontWeight: 600, color: accent, textTransform: "uppercase",
letterSpacing: "0.07em", margin: 0, fontFamily: JM.fontSans,
}}>
{tagline}
</p>
</div>
</div>
<button
type="button"
onClick={onClose}
style={{
background: "none", border: "none", cursor: "pointer",
color: JM.muted, fontSize: 20, lineHeight: 1,
padding: 4, flexShrink: 0, fontFamily: JM.fontSans,
}}
onMouseEnter={e => (e.currentTarget.style.color = JM.mid)}
onMouseLeave={e => (e.currentTarget.style.color = JM.muted)}
>
×
</button>
</div>
);
}
export function TextInput({
value,
onChange,
placeholder,
onKeyDown,
autoFocus,
inputRef,
}: {
value: string;
onChange: (v: string) => void;
placeholder?: string;
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
autoFocus?: boolean;
inputRef?: React.RefObject<HTMLInputElement | null> | React.RefObject<HTMLInputElement>;
}) {
const base: CSSProperties = {
width: "100%", padding: "10px 13px", marginBottom: 16,
borderRadius: 8, border: `1px solid ${JM.border}`,
background: JM.inputBg, fontSize: 14,
fontFamily: JM.fontSans, color: JM.ink,
outline: "none", boxSizing: "border-box",
};
return (
<input
ref={inputRef}
type="text"
value={value}
onChange={e => onChange(e.target.value)}
onKeyDown={onKeyDown}
placeholder={placeholder}
autoFocus={autoFocus}
style={base}
onFocus={e => (e.currentTarget.style.borderColor = JM.indigo)}
onBlur={e => (e.currentTarget.style.borderColor = JM.border)}
/>
);
}
export function PrimaryButton({
onClick,
disabled,
loading,
children,
}: {
onClick: () => void;
disabled?: boolean;
loading?: boolean;
children: ReactNode;
}) {
const active = !disabled && !loading;
return (
<button
type="button"
onClick={onClick}
disabled={!active}
style={{
width: "100%", padding: "12px",
borderRadius: 8, border: "none",
background: active ? JM.primaryGradient : "#E5E7EB",
color: active ? "#fff" : JM.muted,
fontSize: 14, fontWeight: 600,
fontFamily: JM.fontSans,
cursor: active ? "pointer" : "not-allowed",
display: "flex", alignItems: "center", justifyContent: "center", gap: 6,
boxShadow: active ? JM.primaryShadow : "none",
transition: "box-shadow 0.2s, transform 0.15s, opacity 0.15s",
}}
onMouseEnter={e => {
if (active) {
e.currentTarget.style.boxShadow = JM.primaryShadowHover;
e.currentTarget.style.transform = "translateY(-1px)";
}
}}
onMouseLeave={e => {
e.currentTarget.style.boxShadow = active ? JM.primaryShadow : "none";
e.currentTarget.style.transform = "none";
}}
>
{loading ? (
<>
<span style={{
width: 14, height: 14, borderRadius: "50%",
border: "2px solid rgba(255,255,255,0.35)", borderTopColor: "#fff",
animation: "vibn-spin 0.7s linear infinite", display: "inline-block",
}} />
Creating
</>
) : (
children
)}
</button>
);
}