import { useCallback, useEffect, useMemo, useState } from "react"; import { type FoundryBillingPlanId, type FoundryOrganization, type FoundryOrganizationMember, type FoundryUser } from "@sandbox-agent/foundry-shared"; import { useNavigate } from "@tanstack/react-router"; import { ArrowLeft, Clock, CreditCard, FileText, Github, LogOut, Moon, Settings, Sun, Users } from "lucide-react"; import { activeMockUser, eligibleOrganizations, useMockAppClient, useMockAppSnapshot } from "../lib/mock-app"; import { isMockFrontendClient } from "../lib/env"; import { useColorMode, useFoundryTokens } from "../app/theme"; import type { FoundryTokens } from "../styles/tokens"; import { appSurfaceStyle, primaryButtonStyle, secondaryButtonStyle, subtleButtonStyle, cardStyle, badgeStyle, inputStyle } from "../styles/shared-styles"; const dateFormatter = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", }); const planCatalog: Record< FoundryBillingPlanId, { label: string; price: string; pricePerMonth: number; seats: string; taskHours: number; summary: string; } > = { free: { label: "Free", price: "$0", pricePerMonth: 0, seats: "1 seat included", taskHours: 8, summary: "Get started with up to 8 task hours per month.", }, team: { label: "Pro", price: "$25/mo", pricePerMonth: 25, seats: "per seat", taskHours: 200, summary: "200 task hours per seat, with the ability to purchase additional hours.", }, }; const taskHourPackages = [ { hours: 50, price: 6 }, { hours: 100, price: 12 }, { hours: 200, price: 24 }, { hours: 400, price: 48 }, { hours: 600, price: 72 }, { hours: 1000, price: 120 }, ]; function DesktopDragRegion() { const isDesktop = !!import.meta.env.VITE_DESKTOP; const onDragMouseDown = useCallback((event: React.PointerEvent) => { if (event.button !== 0) return; const ipc = (window as unknown as Record).__TAURI_INTERNALS__ as | { invoke: (cmd: string, args?: unknown) => Promise; } | undefined; if (ipc?.invoke) { ipc.invoke("plugin:window|start_dragging").catch(() => {}); } }, []); if (!isDesktop) return null; return (
); } function formatDate(value: string | null): string { if (!value) { return "N/A"; } return dateFormatter.format(new Date(value)); } function organizationPath(organization: FoundryOrganization): string { return `/organizations/${organization.organizationId}`; } function settingsPath(organization: FoundryOrganization): string { return `/organizations/${organization.id}/settings`; } function billingPath(organization: FoundryOrganization): string { return `/organizations/${organization.id}/billing`; } function checkoutPath(organization: FoundryOrganization, planId: FoundryBillingPlanId): string { return `/organizations/${organization.id}/checkout/${planId}`; } function statusBadge(t: FoundryTokens, organization: FoundryOrganization) { if (organization.kind === "personal") { return Personal organization; } return GitHub organization; } function githubBadge(t: FoundryTokens, organization: FoundryOrganization) { if (organization.github.installationStatus === "connected") { return GitHub connected; } if (organization.github.installationStatus === "reconnect_required") { return Reconnect required; } return Install GitHub App; } function StatCard({ label, value, caption }: { label: string; value: string; caption: string }) { const t = useFoundryTokens(); return (
{label}
{value}
{caption}
); } function MemberRow({ member }: { member: FoundryOrganizationMember }) { const t = useFoundryTokens(); return (
{member.name}
{member.email}
{member.role}
{member.state}
); } const AUTH_ERROR_MESSAGES: Record = { please_restart_the_process: "Sign-in failed. Please try again.", state_mismatch: "Sign-in session expired. Please try again.", }; export function MockSignInPage({ error }: { error?: string }) { const client = useMockAppClient(); const navigate = useNavigate(); const t = useFoundryTokens(); const errorMessage = error ? (AUTH_ERROR_MESSAGES[error] ?? `Sign-in error: ${error}`) : undefined; return (
{/* Foundry icon */}

Sign in to Sandbox Agent Foundry

Connect your GitHub account to get started.

{errorMessage && (

{errorMessage}

)} {/* GitHub sign-in button */} {/* Footer */} Learn more
); } export function MockOrganizationSelectorPage() { const client = useMockAppClient(); const snapshot = useMockAppSnapshot(); const organizations: FoundryOrganization[] = eligibleOrganizations(snapshot); const navigate = useNavigate(); const t = useFoundryTokens(); return (
{/* Header */}

Select a organization

Choose where you want to work.

{/* Organization list */}
{organizations.map((organization, index) => ( ))}
{/* Footer */}
); } type SettingsSection = "settings" | "members" | "billing" | "docs"; function SettingsNavItem({ icon, label, active, onClick }: { icon: React.ReactNode; label: string; active: boolean; onClick: () => void }) { const t = useFoundryTokens(); return ( ); } function SettingsContentSection({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) { const t = useFoundryTokens(); return (

{title}

{description ?

{description}

: null}
{children}
); } function SettingsRow({ label, description, action }: { label: string; description?: string; action?: React.ReactNode }) { const t = useFoundryTokens(); return (
{label}
{description ?
{description}
: null}
{action ?? null}
); } function SettingsLayout({ organization, activeSection, onSectionChange, children, }: { organization: FoundryOrganization; activeSection: SettingsSection; onSectionChange?: (section: SettingsSection) => void; children: React.ReactNode; }) { const client = useMockAppClient(); const snapshot = useMockAppSnapshot(); const user = activeMockUser(snapshot); const navigate = useNavigate(); const t = useFoundryTokens(); const navSections: Array<{ section: SettingsSection; icon: React.ReactNode; label: string }> = [ { section: "settings", icon: , label: "Settings" }, { section: "members", icon: , label: "Members" }, { section: "billing", icon: , label: "Billing & Invoices" }, { section: "docs", icon: , label: "Docs" }, ]; return (
{/* Left nav */}
{/* Back to organization */} {/* User header */}
{user?.name ?? "User"} {planCatalog[organization.billing.planId]?.label ?? "Free"} Plan · {user?.email ?? ""}
{navSections.map((item) => ( { if (item.section === "billing") { void navigate({ to: billingPath(organization) }); } else if (onSectionChange) { onSectionChange(item.section); } else { void navigate({ to: settingsPath(organization) }); } }} /> ))}
{/* Content */}
{children}
); } export function MockOrganizationSettingsPage({ organization }: { organization: FoundryOrganization }) { const client = useMockAppClient(); const navigate = useNavigate(); const t = useFoundryTokens(); const [section, setSection] = useState("settings"); const [displayName, setDisplayName] = useState(organization.settings.displayName); const [slug, setSlug] = useState(organization.settings.slug); const [primaryDomain, setPrimaryDomain] = useState(organization.settings.primaryDomain); useEffect(() => { setDisplayName(organization.settings.displayName); setSlug(organization.settings.slug); setPrimaryDomain(organization.settings.primaryDomain); }, [organization.id, organization.settings.displayName, organization.settings.slug, organization.settings.primaryDomain]); return ( {section === "settings" ? (

Settings

window.open("https://sandbox-agent.dev", "_blank", "noopener,noreferrer")} style={secondaryButtonStyle(t)}> Configure } /> Delete } />
) : null} {section === "members" ? (

Members

{organization.members.length} member{organization.members.length !== 1 ? "s" : ""}

{organization.members.map((member) => ( ))}
{/* Upgrade CTA for free plan */} {!organization.billing.stripeCustomerId.trim() ? (
Invite your team
Upgrade to Pro to add team members and unlock collaboration features:
{[ "Hand off tasks to teammates for review or continuation", "Shared organization with unified billing across your org", "200 task hours per seat, with bulk hour purchases available", "Collaborative task history and audit trail", ].map((feature) => (
+ {feature}
))}
) : null}
) : null} {section === "docs" ? (

Docs

Documentation and resources.

window.open("https://sandbox-agent.dev", "_blank", "noopener,noreferrer")} style={secondaryButtonStyle(t)}> Open docs } />
) : null}
); } export function MockOrganizationBillingPage({ organization }: { organization: FoundryOrganization }) { const client = useMockAppClient(); const navigate = useNavigate(); const t = useFoundryTokens(); const hasStripeCustomer = organization.billing.stripeCustomerId.trim().length > 0; const effectivePlanId: FoundryBillingPlanId = hasStripeCustomer ? organization.billing.planId : "free"; const currentPlan = planCatalog[effectivePlanId]!; // Mock usage data const taskHoursUsed = effectivePlanId === "free" ? 5.2 : 147.3; const taskHoursIncluded = currentPlan.taskHours; const taskHoursRemaining = Math.max(0, taskHoursIncluded - taskHoursUsed); const usagePercent = Math.min(100, (taskHoursUsed / taskHoursIncluded) * 100); const isOverage = taskHoursUsed > taskHoursIncluded; const isFree = effectivePlanId === "free"; return (

Billing & Invoices

Manage your plan, task hours, and invoices.

{/* Overview stats */}
{/* Task hours usage bar */}
Task Hours
{taskHoursUsed.toFixed(1)} / {taskHoursIncluded}h used
90 ? "#ef4444" : usagePercent > 70 ? "#f59e0b" : "#22c55e", transition: "width 500ms ease", }} />
Metered by the minute $0.12 / task hour overage
{/* Upgrade to Pro (only shown on Free plan) */} {isFree ? (
Upgrade to Pro
Get 200 task hours per month, plus the ability to purchase additional hours in bulk. Currently limited to {currentPlan.taskHours} hours on the Free plan.
) : null} {/* Buy more task hours (only shown on Pro plan) */} {!isFree ? (
{taskHourPackages.map((pkg) => (
{ (event.currentTarget as HTMLDivElement).style.borderColor = t.borderMedium; }} onMouseLeave={(event) => { (event.currentTarget as HTMLDivElement).style.borderColor = t.borderSubtle; }} >
{pkg.hours}h
${((pkg.price / pkg.hours) * 60).toFixed(1)}¢/min
))}
) : null} {/* Payment method */} {hasStripeCustomer ? (
{organization.billing.status === "scheduled_cancel" ? ( ) : ( )}
) : null} {/* Invoices */} {organization.billing.invoices.length === 0 ? (
No invoices yet.
) : (
{organization.billing.invoices.map((invoice) => (
{invoice.label}
{invoice.issuedAt}
${invoice.amountUsd}
{invoice.status}
))}
)}
); } export function MockHostedCheckoutPage({ organization, planId }: { organization: FoundryOrganization; planId: FoundryBillingPlanId }) { const client = useMockAppClient(); const navigate = useNavigate(); const t = useFoundryTokens(); const plan = planCatalog[planId]!; return (

Checkout {plan.label}

Complete payment to activate the {plan.label} plan.

); } function CheckoutLine({ label, value }: { label: string; value: string }) { const t = useFoundryTokens(); return (
{label}
{value}
); } export function MockAccountSettingsPage() { const client = useMockAppClient(); const snapshot = useMockAppSnapshot(); const user = activeMockUser(snapshot); const navigate = useNavigate(); const t = useFoundryTokens(); const [name, setName] = useState(user?.name ?? ""); const [email, setEmail] = useState(user?.email ?? ""); useEffect(() => { setName(user?.name ?? ""); setEmail(user?.email ?? ""); }, [user?.name, user?.email]); return (
{/* Left nav */}
{user?.name ?? "User"} {user?.email ?? ""}
} label="General" active onClick={() => {}} />
{/* Content */}

Account

Manage your personal account settings.

Delete } />
); } function ProviderCredentialsSection() { const snapshot = useMockAppSnapshot(); const t = useFoundryTokens(); const providerCredentials = snapshot.providerCredentials ?? { anthropic: false, openai: false }; const providers = [ { key: "anthropic" as const, label: "Claude", description: "Anthropic's Claude AI assistant.", signedIn: providerCredentials.anthropic, }, { key: "openai" as const, label: "Codex", description: "OpenAI's Codex coding agent.", signedIn: providerCredentials.openai, }, ]; return ( {providers.map((provider) => ( Connected ) : ( ) } /> ))} ); } function AppearanceSection() { const { colorMode, setColorMode } = useColorMode(); const t = useFoundryTokens(); const isDark = colorMode === "dark"; return ( setColorMode(isDark ? "light" : "dark")} style={{ position: "relative", width: "36px", height: "20px", borderRadius: "10px", border: "1px solid rgba(128, 128, 128, 0.3)", background: isDark ? t.borderDefault : t.accent, cursor: "pointer", padding: 0, transition: "background 0.2s", flexShrink: 0, }} >
{isDark ? : }
} />
); }