@flame0510/project-aether 1.1.9
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/README.md +150 -0
- package/agent-templates/README.md +37 -0
- package/agent-templates/argus/AGENTS.md +52 -0
- package/agent-templates/argus/IDENTITY.md +7 -0
- package/agent-templates/argus/MEMORY.md +9 -0
- package/agent-templates/argus/SOUL.md +24 -0
- package/agent-templates/argus/TOOLS.md +8 -0
- package/agent-templates/atlas/AGENTS.md +47 -0
- package/agent-templates/atlas/HEARTBEAT.md +10 -0
- package/agent-templates/atlas/IDENTITY.md +6 -0
- package/agent-templates/atlas/MEMORY.md +9 -0
- package/agent-templates/atlas/SOUL.md +21 -0
- package/agent-templates/atlas/TOOLS.md +4 -0
- package/agent-templates/base-image/.trigger +0 -0
- package/agent-templates/base-image/Dockerfile +90 -0
- package/agent-templates/base-image/entrypoint.sh +37 -0
- package/agent-templates/custom/AGENTS.md +29 -0
- package/agent-templates/custom/BOOTSTRAP.md +56 -0
- package/agent-templates/custom/MEMORY.md +8 -0
- package/agent-templates/prometheus/AGENTS.md +37 -0
- package/agent-templates/prometheus/IDENTITY.md +6 -0
- package/agent-templates/prometheus/MEMORY.md +9 -0
- package/agent-templates/prometheus/SOUL.md +21 -0
- package/agent-templates/prometheus/TOOLS.md +4 -0
- package/app/agents/ChannelManager.tsx +465 -0
- package/app/agents/ImageDownloadBanner.tsx +159 -0
- package/app/agents/PageClient.tsx +1285 -0
- package/app/agents/create/PageClient.tsx +437 -0
- package/app/agents/create/page.tsx +126 -0
- package/app/agents/loading.tsx +14 -0
- package/app/agents/page.tsx +5 -0
- package/app/api/agents/[id]/backup/route.ts +169 -0
- package/app/api/agents/[id]/channels/pairing/route.ts +69 -0
- package/app/api/agents/[id]/channels/route.ts +14 -0
- package/app/api/agents/[id]/channels/telegram/route.ts +50 -0
- package/app/api/agents/[id]/lifecycle/route.ts +94 -0
- package/app/api/agents/[id]/recreate/route.ts +199 -0
- package/app/api/agents/[id]/restart/route.ts +33 -0
- package/app/api/agents/[id]/restore/route.ts +81 -0
- package/app/api/agents/[id]/route.ts +280 -0
- package/app/api/agents/channels-summary/route.ts +39 -0
- package/app/api/agents/config-json/route.ts +58 -0
- package/app/api/agents/create/route.ts +488 -0
- package/app/api/agents/download-image/route.ts +32 -0
- package/app/api/agents/image-status/route.ts +126 -0
- package/app/api/agents/route.ts +213 -0
- package/app/api/agents/token/route.ts +94 -0
- package/app/api/agents-active/route.ts +190 -0
- package/app/api/agents-config/route.ts +629 -0
- package/app/api/alerts/smoke/route.ts +26 -0
- package/app/api/aliases/add/route.ts +28 -0
- package/app/api/aliases/remove/route.ts +27 -0
- package/app/api/assistant/route.ts +204 -0
- package/app/api/auth/check/route.ts +38 -0
- package/app/api/auth/login/route.ts +85 -0
- package/app/api/auth/logout/route.ts +7 -0
- package/app/api/auth/status/route.ts +33 -0
- package/app/api/config/env/route.ts +132 -0
- package/app/api/config/restart/route.ts +26 -0
- package/app/api/containers/route.ts +75 -0
- package/app/api/cost-override/route.ts +46 -0
- package/app/api/costs/route.ts +84 -0
- package/app/api/credentials/[id]/reveal/route.ts +21 -0
- package/app/api/credentials/[id]/route.ts +52 -0
- package/app/api/credentials/[id]/sync/route.ts +58 -0
- package/app/api/credentials/detect/route.ts +246 -0
- package/app/api/credentials/route.ts +33 -0
- package/app/api/crons/[id]/route.ts +69 -0
- package/app/api/crons/route.ts +12 -0
- package/app/api/crons/runs/route.ts +31 -0
- package/app/api/debug/route.ts +11 -0
- package/app/api/envcheck/route.ts +18 -0
- package/app/api/events/route.ts +24 -0
- package/app/api/gateway/agent/route.ts +154 -0
- package/app/api/gateway/provider/balance/route.ts +38 -0
- package/app/api/gateway/provider/keys.ts +38 -0
- package/app/api/gateway/provider/oauth/route.ts +304 -0
- package/app/api/gateway/provider/route.ts +209 -0
- package/app/api/gateway/route.ts +223 -0
- package/app/api/gateway/sync.ts +189 -0
- package/app/api/lineage/route.ts +54 -0
- package/app/api/memory-context/route.ts +15 -0
- package/app/api/metrics/route.ts +35 -0
- package/app/api/models/route.ts +77 -0
- package/app/api/plugins/route.js +37 -0
- package/app/api/provider/upstream.ts +142 -0
- package/app/api/provider/v1/chat/completions/route.ts +146 -0
- package/app/api/provider/v1/models/route.ts +167 -0
- package/app/api/provider-usage/route.ts +629 -0
- package/app/api/session/route.ts +39 -0
- package/app/api/sessions/[id]/route.ts +38 -0
- package/app/api/sessions/route.ts +23 -0
- package/app/api/setup/agent-image/route.ts +102 -0
- package/app/api/setup/password/route.ts +111 -0
- package/app/api/setup/restart/route.ts +76 -0
- package/app/api/skills/delete/route.js +105 -0
- package/app/api/skills/promote/route.js +129 -0
- package/app/api/skills/route.js +203 -0
- package/app/api/skills/save/route.js +142 -0
- package/app/api/stats/route.ts +34 -0
- package/app/api/stats-since/route.ts +20 -0
- package/app/api/stream/route.ts +70 -0
- package/app/api/system-health/route.ts +233 -0
- package/app/api/tool-calls/route.ts +24 -0
- package/app/api/tools-config/route.ts +163 -0
- package/app/api/update-check/route.ts +127 -0
- package/app/api/version/route.ts +25 -0
- package/app/api/wizard/complete/route.ts +32 -0
- package/app/api/wizard/reset/route.ts +28 -0
- package/app/api/wizard/status/route.ts +28 -0
- package/app/api/workspace/route.ts +392 -0
- package/app/api/workspace/stream/route.ts +136 -0
- package/app/components/AppShell.tsx +33 -0
- package/app/components/AuthGuard.tsx +91 -0
- package/app/components/CostBreakdown.tsx +37 -0
- package/app/components/DashboardHeader.tsx +73 -0
- package/app/components/DashboardLayout.tsx +76 -0
- package/app/components/DashboardToolbar.tsx +61 -0
- package/app/components/FloatingActions.tsx +33 -0
- package/app/components/Icons.tsx +78 -0
- package/app/components/LineageGraphPage.tsx +142 -0
- package/app/components/LiveFeed.tsx +48 -0
- package/app/components/MobileBottomNav.tsx +84 -0
- package/app/components/ModelPickerModal.tsx +94 -0
- package/app/components/PasswordInput.tsx +86 -0
- package/app/components/PulseChat.tsx +362 -0
- package/app/components/Rev4aLoader.tsx +48 -0
- package/app/components/SessionDrawer.tsx +263 -0
- package/app/components/SessionTopology.tsx +289 -0
- package/app/components/Sidebar.tsx +294 -0
- package/app/components/Skeleton.tsx +133 -0
- package/app/components/SystemCockpit.tsx +169 -0
- package/app/components/VersionBanner.tsx +105 -0
- package/app/components/WizardButton.tsx +30 -0
- package/app/components/ui/Badge.tsx +30 -0
- package/app/components/ui/Button.tsx +59 -0
- package/app/components/ui/ConfirmModal.tsx +46 -0
- package/app/components/ui/FilterBar.tsx +33 -0
- package/app/components/ui/Input.tsx +25 -0
- package/app/components/ui/ItemList.tsx +83 -0
- package/app/components/ui/LoadingSpinner.tsx +30 -0
- package/app/components/ui/Metric.tsx +22 -0
- package/app/components/ui/Modal.tsx +42 -0
- package/app/components/ui/Page.tsx +18 -0
- package/app/components/ui/Pill.tsx +6 -0
- package/app/components/ui/PropertyList.tsx +24 -0
- package/app/components/ui/Select.tsx +26 -0
- package/app/components/ui/StatusCard.tsx +32 -0
- package/app/components/ui/Surface.tsx +14 -0
- package/app/components/ui/Tabs.tsx +55 -0
- package/app/components/ui/TemplateOption.tsx +59 -0
- package/app/components/ui/Toast.tsx +44 -0
- package/app/components/ui/index.ts +19 -0
- package/app/components/ui/tokens.ts +18 -0
- package/app/config/PageClient.tsx +293 -0
- package/app/config/loading.tsx +10 -0
- package/app/config/page.tsx +7 -0
- package/app/containers/ContainersClient.tsx +129 -0
- package/app/containers/loading.tsx +21 -0
- package/app/containers/page.tsx +5 -0
- package/app/containers/terminal/[id]/TerminalClient.tsx +313 -0
- package/app/containers/terminal/[id]/page.tsx +6 -0
- package/app/credentials/PageClient.tsx +598 -0
- package/app/credentials/loading.tsx +10 -0
- package/app/credentials/page.tsx +7 -0
- package/app/crons/PageClient.tsx +538 -0
- package/app/crons/loading.tsx +10 -0
- package/app/crons/page.tsx +5 -0
- package/app/design-system.ts +57 -0
- package/app/gateway/PageClient.tsx +1048 -0
- package/app/gateway/loading.tsx +24 -0
- package/app/gateway/page.tsx +5 -0
- package/app/globals.css +1474 -0
- package/app/layout.tsx +45 -0
- package/app/lib/models-context.tsx +41 -0
- package/app/lineage/loading.tsx +10 -0
- package/app/lineage/page.tsx +13 -0
- package/app/loading.tsx +16 -0
- package/app/login/page.tsx +160 -0
- package/app/memory/MemoryContextPageClient.tsx +196 -0
- package/app/memory/loading.tsx +10 -0
- package/app/memory/page.tsx +8 -0
- package/app/page.tsx +31 -0
- package/app/plugins/PageClient.tsx +296 -0
- package/app/plugins/loading.tsx +10 -0
- package/app/plugins/page.tsx +5 -0
- package/app/setup/PageClient.tsx +227 -0
- package/app/setup/loading.tsx +21 -0
- package/app/setup/page.tsx +7 -0
- package/app/skills/PageClient.tsx +499 -0
- package/app/skills/loading.tsx +10 -0
- package/app/skills/page.tsx +5 -0
- package/app/tools/ToolsPageClient.tsx +308 -0
- package/app/tools/loading.tsx +10 -0
- package/app/tools/page.tsx +62 -0
- package/app/tools/tools-catalog.ts +141 -0
- package/app/wizard/PageClient.tsx +485 -0
- package/app/wizard/icons.tsx +89 -0
- package/app/wizard/loading.tsx +24 -0
- package/app/wizard/page.tsx +7 -0
- package/app/wizard/storage.ts +33 -0
- package/app/wizard/useWizard.ts +101 -0
- package/app/workspace/WorkspaceClient.tsx +633 -0
- package/app/workspace/loading.tsx +36 -0
- package/app/workspace/page.tsx +8 -0
- package/bin/docker-entrypoint.sh +21 -0
- package/bin/postinstall.js +20 -0
- package/bin/rev4a.js +696 -0
- package/daemon.js +783 -0
- package/docs/ARCHITECTURE.md +557 -0
- package/docs/CONTAINER-TERMINAL.md +280 -0
- package/docs/DESIGN-SYSTEM.md +99 -0
- package/docs/FRONTEND-ARCHITECTURE.md +119 -0
- package/docs/REV4A.md +504 -0
- package/docs/dev/API-REFERENCE.md +1835 -0
- package/docs/dev/DATABASE.md +224 -0
- package/docs/dev/GATEWAY.md +300 -0
- package/docs/dev/PROVIDERS.md +229 -0
- package/docs/dev/SESSION-MAINTENANCE-PLAN.md +607 -0
- package/docs/dev/WORKSPACE.md +199 -0
- package/docs/rag/DATA-FRESHNESS.md +109 -0
- package/docs/rag/GLOSSARY.md +125 -0
- package/docs/rag/REV4A-OVERVIEW.md +135 -0
- package/docs/rag/WHAT-I-CAN-ANSWER.md +124 -0
- package/lib/agent-setup.ts +130 -0
- package/lib/alerts.ts +115 -0
- package/lib/apiFetch.ts +7 -0
- package/lib/auth.ts +28 -0
- package/lib/billing.ts +100 -0
- package/lib/buildAgentImage.ts +346 -0
- package/lib/channelManager.ts +386 -0
- package/lib/container.ts +60 -0
- package/lib/credentials/db.ts +86 -0
- package/lib/credentials/delivery.ts +448 -0
- package/lib/credentials/detect.ts +149 -0
- package/lib/credentials/hash.ts +9 -0
- package/lib/credentials/providers.ts +110 -0
- package/lib/credentials/vault.ts +195 -0
- package/lib/db-bootstrap.d.ts +3 -0
- package/lib/db-bootstrap.mjs +144 -0
- package/lib/db.ts +25 -0
- package/lib/docker-utils.ts +60 -0
- package/lib/hooks/useDashboard.ts +264 -0
- package/lib/hooks/useRev4aTimezone.ts +28 -0
- package/lib/hooks/useScreenshot.ts +30 -0
- package/lib/memory-context.ts +385 -0
- package/lib/model-catalogue.ts +131 -0
- package/lib/model-pricing.ts +63 -0
- package/lib/openclaw-cron.ts +350 -0
- package/lib/patterns/ApiAdapter.ts +73 -0
- package/lib/patterns/ApiClient.ts +65 -0
- package/lib/patterns/EventBus.ts +113 -0
- package/lib/patterns/FilterStrategy.ts +79 -0
- package/lib/patterns/SessionFactory.ts +117 -0
- package/lib/patterns/sessionPresentation.ts +117 -0
- package/lib/provider-balance.ts +163 -0
- package/lib/requireAuth.tsx +38 -0
- package/lib/rev4a-auth.d.ts +3 -0
- package/lib/rev4a-auth.js +36 -0
- package/lib/rev4a-paths.ts +145 -0
- package/lib/timezone.ts +46 -0
- package/lib/types/index.ts +96 -0
- package/lib/utils/format.ts +82 -0
- package/lineage.js +56 -0
- package/model-pricing.json +482 -0
- package/models.config.json +844 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +7 -0
- package/package.json +82 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-64.png +0 -0
- package/public/favicon.svg +58 -0
- package/public/icon-192-maskable.png +0 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512-maskable.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/manifest.json +38 -0
- package/public/rev4a-logo.png +0 -0
- package/public/rev4a-logo.svg +58 -0
- package/rev4a-rules/AGENTS.md +37 -0
- package/scripts/add-auth.cjs +185 -0
- package/scripts/backup.sh +66 -0
- package/scripts/check-providers.mjs +143 -0
- package/scripts/docker/traefik/docker-compose.yml +32 -0
- package/scripts/docker/traefik/dynamic/.gitkeep +18 -0
- package/scripts/docker/traefik/traefik.yml +37 -0
- package/scripts/remove-auth-profile.js +56 -0
- package/scripts/restore.sh +82 -0
- package/terminal-ws-server.js +139 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { Button } from '../components/ui';
|
|
6
|
+
import { useWizard, type ProviderKeys } from './useWizard';
|
|
7
|
+
import {
|
|
8
|
+
ArrowRightIcon, CheckCircleIcon, CheckIcon,
|
|
9
|
+
DockerIcon, GatewayIcon, AgentIcon,
|
|
10
|
+
TemplateIcon, LinkIcon, ConfigIcon,
|
|
11
|
+
} from './icons';
|
|
12
|
+
|
|
13
|
+
// ─── Step order ─────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const STEPS = [
|
|
16
|
+
{ key: 'welcome' as const, labelMobile: '1', label: 'Welcome' },
|
|
17
|
+
{ key: 'providers' as const, labelMobile: '2', label: 'Providers' },
|
|
18
|
+
{ key: 'agents' as const, labelMobile: '3', label: 'Agent' },
|
|
19
|
+
{ key: 'done' as const, labelMobile: '✓', label: 'Ready' },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
// ─── Main page ──────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
export default function WizardPageClient() {
|
|
25
|
+
const router = useRouter();
|
|
26
|
+
const {
|
|
27
|
+
step, stepIndex, saving, complete, providerKeys,
|
|
28
|
+
setProviderKeys, goNext, skip, goToStep, completeAll, restart, hydrated,
|
|
29
|
+
} = useWizard();
|
|
30
|
+
|
|
31
|
+
const handleComplete = async () => {
|
|
32
|
+
await completeAll();
|
|
33
|
+
router.push('/');
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (!hydrated) return null;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<WizardFrame stepIndex={stepIndex} steps={STEPS} onStepClick={goToStep}>
|
|
40
|
+
{step === 'welcome' && <WelcomeStep onNext={goNext} />}
|
|
41
|
+
{step === 'providers' && <ProvidersStep keys={providerKeys} onChange={setProviderKeys} onNext={goNext} onSkip={skip} saving={saving} />}
|
|
42
|
+
{step === 'agents' && <AgentsStep onNext={goNext} onSkip={skip} />}
|
|
43
|
+
{step === 'done' && <DoneStep onComplete={handleComplete} saving={saving} onRestart={restart} />}
|
|
44
|
+
</WizardFrame>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Step components ────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function WelcomeStep({ onNext }: { onNext: () => void }) {
|
|
51
|
+
return (
|
|
52
|
+
<div style={{ display: 'grid', gap: 14 }}>
|
|
53
|
+
<h2 className="wizard-title">Welcome to Rev4a</h2>
|
|
54
|
+
<p className="wizard-text">
|
|
55
|
+
Rev4a is the control center for your OpenClaw agent ecosystem.
|
|
56
|
+
Create, monitor, and manage AI agents running as Docker containers
|
|
57
|
+
on your VPS — all from one dashboard.
|
|
58
|
+
</p>
|
|
59
|
+
<div className="wizard-feature-box">
|
|
60
|
+
<Feature icon={<DockerIcon />} title="Agent Dashboard" desc="View all running agents, their status, ports, and resource usage at a glance." />
|
|
61
|
+
<Feature icon={<GatewayIcon />} title="Provider Gateway" desc="Configure LLM providers once — every agent gets automatic access through the Rev4a proxy." />
|
|
62
|
+
<Feature icon={<AgentIcon />} title="One-Click Deploy" desc="Create new agents from templates with a single click. No Docker expertise needed." />
|
|
63
|
+
</div>
|
|
64
|
+
<div className="wizard-actions">
|
|
65
|
+
<span className="wizard-meta">3 steps · ~2 minutes</span>
|
|
66
|
+
<Button variant="primary" size="md" onClick={onNext}>
|
|
67
|
+
Get Started <ArrowRightIcon />
|
|
68
|
+
</Button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function ProvidersStep({
|
|
75
|
+
keys, onChange, onNext, onSkip, saving,
|
|
76
|
+
}: {
|
|
77
|
+
keys: ProviderKeys;
|
|
78
|
+
onChange: (fn: (prev: ProviderKeys) => ProviderKeys) => void;
|
|
79
|
+
onNext: () => void;
|
|
80
|
+
onSkip: () => void;
|
|
81
|
+
saving: boolean;
|
|
82
|
+
}) {
|
|
83
|
+
const providers = [
|
|
84
|
+
{ key: 'deepseek' as const, label: 'DeepSeek', placeholder: 'sk-...', recommended: true },
|
|
85
|
+
{ key: 'openai' as const, label: 'OpenAI', placeholder: 'sk-...', recommended: false },
|
|
86
|
+
{ key: 'openrouter' as const, label: 'OpenRouter', placeholder: 'sk-or-...', recommended: false },
|
|
87
|
+
{ key: 'groq' as const, label: 'Groq', placeholder: 'gsk_...', recommended: false },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div style={{ display: 'grid', gap: 14 }}>
|
|
92
|
+
<h2 className="wizard-title">Configure Providers</h2>
|
|
93
|
+
<p className="wizard-text">
|
|
94
|
+
Add API keys for the LLM providers you want to use. At least one is required
|
|
95
|
+
to create working agents. Keys are stored securely and never logged.
|
|
96
|
+
</p>
|
|
97
|
+
<div style={{ display: 'grid', gap: 10 }}>
|
|
98
|
+
{providers.map((p) => (
|
|
99
|
+
<div key={p.key} style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
100
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
101
|
+
<span style={{ fontSize: 12, fontWeight: 700, color: 'var(--text)' }}>{p.label}</span>
|
|
102
|
+
{p.recommended && <span className="wizard-badge">RECOMMENDED</span>}
|
|
103
|
+
</div>
|
|
104
|
+
<input
|
|
105
|
+
type="password"
|
|
106
|
+
placeholder={p.placeholder}
|
|
107
|
+
value={keys[p.key]}
|
|
108
|
+
onChange={(e) => onChange((prev) => ({ ...prev, [p.key]: e.target.value }))}
|
|
109
|
+
className="wizard-input"
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
<div className="wizard-actions">
|
|
115
|
+
<Button variant="ghost" size="sm" onClick={onSkip}>Skip for now</Button>
|
|
116
|
+
<Button variant="primary" size="md" onClick={onNext} loading={saving}>
|
|
117
|
+
Save & Continue <ArrowRightIcon />
|
|
118
|
+
</Button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function AgentsStep({ onNext, onSkip }: { onNext: () => void; onSkip: () => void }) {
|
|
125
|
+
return (
|
|
126
|
+
<div style={{ display: 'grid', gap: 14 }}>
|
|
127
|
+
<h2 className="wizard-title">Your First Agent</h2>
|
|
128
|
+
<p className="wizard-text">
|
|
129
|
+
Agents are independent Docker containers, each with their own workspace,
|
|
130
|
+
memory, and personality. You can create them from the Agents page.
|
|
131
|
+
Mark this step as done when you're ready to move on.
|
|
132
|
+
</p>
|
|
133
|
+
<div className="wizard-feature-box" style={{ borderColor: 'rgba(96,165,250,0.15)', background: 'rgba(96,165,250,0.06)' }}>
|
|
134
|
+
<Feature icon={<TemplateIcon />} title="Pick a template" desc="Each template gives your agent a different role and skill set (Prometheus, Argus, Atlas…)" />
|
|
135
|
+
<Feature icon={<LinkIcon />} title="Get a URL" desc="Your agent gets its own port — access its Control UI from the dashboard." />
|
|
136
|
+
<Feature icon={<ConfigIcon />} title="Automatic config" desc="Provider keys, gateway sync, and model selection are handled for you." />
|
|
137
|
+
</div>
|
|
138
|
+
<div className="wizard-actions">
|
|
139
|
+
<Button variant="ghost" size="sm" onClick={onSkip}>Skip for now</Button>
|
|
140
|
+
<Button variant="primary" size="md" onClick={onNext}>
|
|
141
|
+
Done <ArrowRightIcon />
|
|
142
|
+
</Button>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function DoneStep({ onComplete, saving, onRestart }: { onComplete: () => void; saving: boolean; onRestart: () => void }) {
|
|
149
|
+
const routes = [
|
|
150
|
+
{ route: '/agents', desc: 'Create and manage agents' },
|
|
151
|
+
{ route: '/gateway', desc: 'Configure providers and sync agent models' },
|
|
152
|
+
{ route: '/credentials', desc: 'Third-party credentials (GitHub, Trello, Vercel…)' },
|
|
153
|
+
{ route: '/config', desc: 'Environment variables and server restart' },
|
|
154
|
+
{ route: '/workspace', desc: 'Browse agent filesystems' },
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div style={{ display: 'grid', gap: 14, textAlign: 'center' }}>
|
|
159
|
+
<div style={{ margin: '0 auto', display: 'flex', justifyContent: 'center' }}>
|
|
160
|
+
<CheckCircleIcon size={48} color="var(--violet)" />
|
|
161
|
+
</div>
|
|
162
|
+
<h2 className="wizard-title">You're All Set</h2>
|
|
163
|
+
<p className="wizard-text">
|
|
164
|
+
Rev4a is ready. Here's a quick map of the key sections:
|
|
165
|
+
</p>
|
|
166
|
+
<div className="wizard-feature-box" style={{ borderColor: 'rgba(52,211,153,0.15)', background: 'rgba(52,211,153,0.06)', textAlign: 'left' }}>
|
|
167
|
+
{routes.map((r) => (
|
|
168
|
+
<div key={r.route} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }}>
|
|
169
|
+
<CheckIcon size={14} />
|
|
170
|
+
<Link href={r.route} className="wizard-link">{r.route}</Link>
|
|
171
|
+
<span style={{ color: 'var(--text-dim)' }}>– {r.desc}</span>
|
|
172
|
+
</div>
|
|
173
|
+
))}
|
|
174
|
+
</div>
|
|
175
|
+
<div className="wizard-actions" style={{ justifyContent: 'center', gap: 12 }}>
|
|
176
|
+
<div className="wizard-done-actions">
|
|
177
|
+
<Button variant="secondary" size="md" onClick={onRestart}>
|
|
178
|
+
Restart Wizard
|
|
179
|
+
</Button>
|
|
180
|
+
<Button variant="primary" size="md" onClick={onComplete} loading={saving}>
|
|
181
|
+
Start Using Rev4a <ArrowRightIcon />
|
|
182
|
+
</Button>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ─── Shared sub-components ──────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
function Feature({ icon, title, desc }: { icon: React.ReactNode; title: string; desc: string }) {
|
|
192
|
+
return (
|
|
193
|
+
<div className="wizard-feature">
|
|
194
|
+
<span className="wizard-feature-icon">{icon}</span>
|
|
195
|
+
<div>
|
|
196
|
+
<div className="wizard-feature-title">{title}</div>
|
|
197
|
+
<div className="wizard-feature-desc">{desc}</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── Wizard shell ───────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
function WizardFrame({
|
|
206
|
+
stepIndex, steps, children, onStepClick,
|
|
207
|
+
}: {
|
|
208
|
+
stepIndex: number;
|
|
209
|
+
steps: { key: string; label: string; labelMobile: string }[];
|
|
210
|
+
children: React.ReactNode;
|
|
211
|
+
onStepClick: (index: number) => void;
|
|
212
|
+
}) {
|
|
213
|
+
return (
|
|
214
|
+
<div className="wizard-outer">
|
|
215
|
+
<div className="wizard-card">
|
|
216
|
+
{/* Header */}
|
|
217
|
+
<div className="wizard-header">
|
|
218
|
+
<div className="wizard-logo">REV4A</div>
|
|
219
|
+
|
|
220
|
+
{/* Step indicators — compact on mobile, full labels on desktop */}
|
|
221
|
+
<div className="wizard-stepper">
|
|
222
|
+
{steps.map((s, i) => {
|
|
223
|
+
const isPast = i < stepIndex;
|
|
224
|
+
const isCurrent = i === stepIndex;
|
|
225
|
+
const clickable = isPast || isCurrent;
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div key={s.key} className="wizard-step">
|
|
229
|
+
<button
|
|
230
|
+
onClick={() => clickable && onStepClick(i)}
|
|
231
|
+
disabled={!clickable}
|
|
232
|
+
title={s.label}
|
|
233
|
+
className={`wizard-step-dot${isPast ? ' done' : ''}${isCurrent ? ' active' : ''}${clickable ? ' clickable' : ''}`}
|
|
234
|
+
/>
|
|
235
|
+
{/* Full label — hidden on mobile */}
|
|
236
|
+
<span className={`wizard-step-label${isCurrent ? ' current' : ''}`}>
|
|
237
|
+
{s.label}
|
|
238
|
+
</span>
|
|
239
|
+
{/* Mobile-only compact label */}
|
|
240
|
+
<span className={`wizard-step-mobile${isCurrent ? ' current' : ''}`}>
|
|
241
|
+
{s.labelMobile}
|
|
242
|
+
</span>
|
|
243
|
+
{i < steps.length - 1 && <div className={`wizard-step-line${isPast ? ' done' : ''}`} />}
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
})}
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
{/* Body */}
|
|
251
|
+
<div className="wizard-body">{children}</div>
|
|
252
|
+
|
|
253
|
+
{/* Footer */}
|
|
254
|
+
<div className="wizard-footer">
|
|
255
|
+
You can return to this wizard anytime from the sidebar.
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
{/* Mobile-first CSS */}
|
|
260
|
+
<style>{`
|
|
261
|
+
/* ── Base (mobile-first) ── */
|
|
262
|
+
.wizard-outer {
|
|
263
|
+
min-height: 100vh; min-height: 100dvh;
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: flex-start;
|
|
266
|
+
justify-content: center;
|
|
267
|
+
background: radial-gradient(circle at top, rgba(146,91,252,0.10), transparent 36%), var(--bg);
|
|
268
|
+
font-family: var(--font-mono-stack);
|
|
269
|
+
padding: 0;
|
|
270
|
+
}
|
|
271
|
+
.wizard-card {
|
|
272
|
+
width: 100%;
|
|
273
|
+
background: var(--bg);
|
|
274
|
+
overflow: hidden;
|
|
275
|
+
}
|
|
276
|
+
.wizard-header {
|
|
277
|
+
padding: 12px 14px 8px;
|
|
278
|
+
border-bottom: 1px solid var(--border);
|
|
279
|
+
display: flex;
|
|
280
|
+
flex-direction: column;
|
|
281
|
+
align-items: center;
|
|
282
|
+
gap: 6px;
|
|
283
|
+
}
|
|
284
|
+
.wizard-logo {
|
|
285
|
+
font-family: var(--font-serif-stack);
|
|
286
|
+
font-size: 18px;
|
|
287
|
+
letter-spacing: 2px;
|
|
288
|
+
color: var(--violet);
|
|
289
|
+
}
|
|
290
|
+
.wizard-body {
|
|
291
|
+
padding: 12px 14px 14px;
|
|
292
|
+
}
|
|
293
|
+
.wizard-footer {
|
|
294
|
+
padding: 0 14px 12px;
|
|
295
|
+
text-align: center;
|
|
296
|
+
font-size: 9px;
|
|
297
|
+
color: var(--text-dim);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* ── Stepper: compact mobile, full desktop ── */
|
|
301
|
+
.wizard-stepper {
|
|
302
|
+
display: flex;
|
|
303
|
+
gap: 2px;
|
|
304
|
+
align-items: center;
|
|
305
|
+
max-width: 100%;
|
|
306
|
+
overflow-x: auto;
|
|
307
|
+
padding-bottom: 2px;
|
|
308
|
+
}
|
|
309
|
+
.wizard-step {
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
gap: 3px;
|
|
313
|
+
flex-shrink: 0;
|
|
314
|
+
}
|
|
315
|
+
.wizard-step-dot {
|
|
316
|
+
width: 8px; min-width: 8px; height: 8px;
|
|
317
|
+
border-radius: 50%;
|
|
318
|
+
background: var(--border);
|
|
319
|
+
opacity: 0.35;
|
|
320
|
+
border: none;
|
|
321
|
+
padding: 0;
|
|
322
|
+
cursor: default;
|
|
323
|
+
transition: all 0.2s;
|
|
324
|
+
}
|
|
325
|
+
.wizard-step-dot.done { background: var(--violet); opacity: 0.7; }
|
|
326
|
+
.wizard-step-dot.active { background: var(--violet); opacity: 1; box-shadow: 0 0 5px rgba(146,91,252,0.4); }
|
|
327
|
+
.wizard-step-dot.clickable { cursor: pointer; }
|
|
328
|
+
.wizard-step-label {
|
|
329
|
+
display: none; /* hidden on mobile */
|
|
330
|
+
font-size: 10px;
|
|
331
|
+
color: var(--text-dim);
|
|
332
|
+
letter-spacing: 0.05em;
|
|
333
|
+
white-space: nowrap;
|
|
334
|
+
}
|
|
335
|
+
.wizard-step-label.current { color: var(--violet); font-weight: 700; }
|
|
336
|
+
.wizard-step-mobile {
|
|
337
|
+
font-size: 10px;
|
|
338
|
+
color: var(--text-dim);
|
|
339
|
+
white-space: nowrap;
|
|
340
|
+
font-weight: 500;
|
|
341
|
+
}
|
|
342
|
+
.wizard-step-mobile.current { color: var(--violet); font-weight: 700; }
|
|
343
|
+
.wizard-step-line {
|
|
344
|
+
width: 10px; min-width: 10px; height: 1px;
|
|
345
|
+
background: var(--border);
|
|
346
|
+
opacity: 0.2;
|
|
347
|
+
}
|
|
348
|
+
.wizard-step-line.done { opacity: 0.5; }
|
|
349
|
+
|
|
350
|
+
/* Restart icon */
|
|
351
|
+
.wizard-restart {
|
|
352
|
+
margin-left: 4px;
|
|
353
|
+
background: none;
|
|
354
|
+
border: none;
|
|
355
|
+
color: var(--text-dim);
|
|
356
|
+
cursor: pointer;
|
|
357
|
+
padding: 2px;
|
|
358
|
+
display: flex;
|
|
359
|
+
align-items: center;
|
|
360
|
+
opacity: 0.5;
|
|
361
|
+
transition: opacity 0.2s;
|
|
362
|
+
flex-shrink: 0;
|
|
363
|
+
}
|
|
364
|
+
.wizard-restart:hover { opacity: 1; color: var(--violet); }
|
|
365
|
+
|
|
366
|
+
/* ── Shared element styles ── */
|
|
367
|
+
.wizard-title {
|
|
368
|
+
font-family: var(--font-serif-stack);
|
|
369
|
+
font-size: 22px;
|
|
370
|
+
letter-spacing: 1.5px;
|
|
371
|
+
color: var(--violet);
|
|
372
|
+
margin: 0;
|
|
373
|
+
}
|
|
374
|
+
.wizard-text {
|
|
375
|
+
font-size: 12px;
|
|
376
|
+
color: var(--text-dim);
|
|
377
|
+
line-height: 1.6;
|
|
378
|
+
margin: 0;
|
|
379
|
+
}
|
|
380
|
+
.wizard-actions {
|
|
381
|
+
display: flex;
|
|
382
|
+
justify-content: space-between;
|
|
383
|
+
align-items: center;
|
|
384
|
+
margin-top: 6px;
|
|
385
|
+
}
|
|
386
|
+
.wizard-meta {
|
|
387
|
+
font-size: 10px;
|
|
388
|
+
color: var(--text-dim);
|
|
389
|
+
}
|
|
390
|
+
.wizard-badge {
|
|
391
|
+
font-size: 8px;
|
|
392
|
+
background: rgba(146,91,252,0.15);
|
|
393
|
+
color: var(--violet);
|
|
394
|
+
padding: 1px 5px;
|
|
395
|
+
}
|
|
396
|
+
.wizard-input {
|
|
397
|
+
background: var(--bg);
|
|
398
|
+
border: 1px solid var(--border);
|
|
399
|
+
color: var(--text);
|
|
400
|
+
padding: 7px 10px;
|
|
401
|
+
font-size: 11px;
|
|
402
|
+
font-family: var(--font-mono-stack);
|
|
403
|
+
outline: none;
|
|
404
|
+
width: 100%;
|
|
405
|
+
box-sizing: border-box;
|
|
406
|
+
}
|
|
407
|
+
.wizard-input:focus { border-color: var(--violet); }
|
|
408
|
+
.wizard-feature-box {
|
|
409
|
+
display: grid;
|
|
410
|
+
gap: 8px;
|
|
411
|
+
padding: 10px 12px;
|
|
412
|
+
background: rgba(146,91,252,0.06);
|
|
413
|
+
border: 1px solid rgba(146,91,252,0.15);
|
|
414
|
+
}
|
|
415
|
+
.wizard-feature {
|
|
416
|
+
display: flex;
|
|
417
|
+
gap: 8px;
|
|
418
|
+
align-items: flex-start;
|
|
419
|
+
}
|
|
420
|
+
.wizard-feature-icon {
|
|
421
|
+
flex-shrink: 0;
|
|
422
|
+
color: var(--violet);
|
|
423
|
+
margin-top: 1px;
|
|
424
|
+
}
|
|
425
|
+
.wizard-feature-title {
|
|
426
|
+
font-size: 11px;
|
|
427
|
+
font-weight: 700;
|
|
428
|
+
color: var(--text);
|
|
429
|
+
}
|
|
430
|
+
.wizard-feature-desc {
|
|
431
|
+
font-size: 10px;
|
|
432
|
+
color: var(--text-dim);
|
|
433
|
+
line-height: 1.5;
|
|
434
|
+
}
|
|
435
|
+
.wizard-link {
|
|
436
|
+
color: #60a5fa;
|
|
437
|
+
text-decoration: underline;
|
|
438
|
+
font-weight: 700;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.wizard-done-actions {
|
|
442
|
+
display: flex;
|
|
443
|
+
flex-direction: column;
|
|
444
|
+
gap: 8px;
|
|
445
|
+
width: 100%;
|
|
446
|
+
align-items: center;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/* ── Desktop breakpoint ── */
|
|
450
|
+
@media (min-width: 481px) {
|
|
451
|
+
.wizard-outer {
|
|
452
|
+
align-items: center;
|
|
453
|
+
padding: 24px;
|
|
454
|
+
}
|
|
455
|
+
.wizard-card {
|
|
456
|
+
width: min(92vw, 480px);
|
|
457
|
+
background: linear-gradient(180deg, rgba(18,18,18,0.98), rgba(12,12,12,0.98));
|
|
458
|
+
border: 1px solid var(--border);
|
|
459
|
+
box-shadow: 0 24px 64px rgba(0,0,0,0.42);
|
|
460
|
+
}
|
|
461
|
+
.wizard-header { padding: 18px 24px 14px; gap: 10px; }
|
|
462
|
+
.wizard-logo { font-size: 20px; letter-spacing: 3px; }
|
|
463
|
+
.wizard-body { padding: 20px 24px 24px; }
|
|
464
|
+
.wizard-footer { padding: 0 24px 16px; font-size: 10px; }
|
|
465
|
+
|
|
466
|
+
.wizard-stepper { gap: 6px; }
|
|
467
|
+
.wizard-step { gap: 6px; }
|
|
468
|
+
.wizard-step-dot { width: 10px; min-width: 10px; height: 10px; }
|
|
469
|
+
.wizard-step-label { display: inline; } /* show full labels */
|
|
470
|
+
.wizard-step-mobile { display: none; } /* hide compact labels */
|
|
471
|
+
.wizard-step-line { width: 18px; }
|
|
472
|
+
.wizard-restart { margin-left: 8px; }
|
|
473
|
+
|
|
474
|
+
.wizard-title { font-size: 22px; }
|
|
475
|
+
.wizard-text { font-size: 13px; }
|
|
476
|
+
|
|
477
|
+
.wizard-done-actions {
|
|
478
|
+
flex-direction: row;
|
|
479
|
+
width: auto;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
`}</style>
|
|
483
|
+
</div>
|
|
484
|
+
);
|
|
485
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/** Shared SVG icons for the onboarding wizard. Gang-of-Four flyweight pattern — single component per icon, reused everywhere. */
|
|
4
|
+
|
|
5
|
+
export function ArrowRightIcon({ size = 14 }: { size?: number }) {
|
|
6
|
+
return (
|
|
7
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
8
|
+
<line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/>
|
|
9
|
+
</svg>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function CheckIcon({ size = 14, color = '#34d399' }: { size?: number; color?: string }) {
|
|
14
|
+
return (
|
|
15
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
16
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function CheckCircleIcon({ size = 48, color = '#34d399' }: { size?: number; color?: string }) {
|
|
22
|
+
return (
|
|
23
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
24
|
+
<circle cx="12" cy="12" r="10"/><polyline points="8 12 11 15 16 9"/>
|
|
25
|
+
</svg>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function DockerIcon({ size = 18 }: { size?: number }) {
|
|
30
|
+
return (
|
|
31
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
32
|
+
<rect x="3" y="5" width="18" height="14" rx="2"/>
|
|
33
|
+
<rect x="5" y="8" width="3" height="3" rx="0.5"/><rect x="10" y="8" width="3" height="3" rx="0.5"/><rect x="15" y="8" width="3" height="3" rx="0.5"/>
|
|
34
|
+
<rect x="5" y="13" width="3" height="3" rx="0.5"/><rect x="10" y="13" width="3" height="3" rx="0.5"/><rect x="15" y="13" width="3" height="3" rx="0.5"/>
|
|
35
|
+
</svg>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function GatewayIcon({ size = 18 }: { size?: number }) {
|
|
40
|
+
return (
|
|
41
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
42
|
+
<circle cx="12" cy="12" r="3"/>
|
|
43
|
+
<path d="M12 2v4M12 18v4M2 12h4M18 12h4M5.64 5.64l2.83 2.83M15.54 15.54l2.83 2.83M5.64 18.36l2.83-2.83M15.54 8.46l2.83-2.83"/>
|
|
44
|
+
</svg>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function AgentIcon({ size = 18 }: { size?: number }) {
|
|
49
|
+
return (
|
|
50
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
51
|
+
<circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/>
|
|
52
|
+
</svg>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function TemplateIcon({ size = 18 }: { size?: number }) {
|
|
57
|
+
return (
|
|
58
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
59
|
+
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
|
|
60
|
+
<rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
|
|
61
|
+
</svg>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function LinkIcon({ size = 18 }: { size?: number }) {
|
|
66
|
+
return (
|
|
67
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
68
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
|
69
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
|
70
|
+
</svg>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function ConfigIcon({ size = 18 }: { size?: number }) {
|
|
75
|
+
return (
|
|
76
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
77
|
+
<circle cx="12" cy="12" r="3"/>
|
|
78
|
+
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
|
|
79
|
+
</svg>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function InformationIcon({ size = 14 }: { size?: number }) {
|
|
84
|
+
return (
|
|
85
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
86
|
+
<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/>
|
|
87
|
+
</svg>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Skeleton } from '../components/Skeleton';
|
|
2
|
+
|
|
3
|
+
export default function WizardLoading() {
|
|
4
|
+
return (
|
|
5
|
+
<div style={{
|
|
6
|
+
display: 'flex',
|
|
7
|
+
alignItems: 'center',
|
|
8
|
+
justifyContent: 'center',
|
|
9
|
+
minHeight: '100vh',
|
|
10
|
+
background: 'var(--bg)',
|
|
11
|
+
}}>
|
|
12
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 20, maxWidth: 560, width: '100%', padding: 20 }}>
|
|
13
|
+
<Skeleton width={80} height={80} accent variant="circle" />
|
|
14
|
+
<Skeleton width={240} height={32} accent />
|
|
15
|
+
<Skeleton width={480} height={16} />
|
|
16
|
+
<div style={{ display: 'grid', gap: 10, width: '100%', marginTop: 12 }}>
|
|
17
|
+
{[1, 2, 3].map(i => (
|
|
18
|
+
<Skeleton key={i} width="100%" height={60} style={{ border: '1px solid rgba(255,255,255,0.06)' }} />
|
|
19
|
+
))}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const WIZARD_STATUS_URL = '/api/wizard/status';
|
|
2
|
+
|
|
3
|
+
/** Check whether wizard is fully complete (via API, works with httpOnly cookies). */
|
|
4
|
+
export async function isWizardComplete(): Promise<boolean> {
|
|
5
|
+
try {
|
|
6
|
+
const res = await fetch(WIZARD_STATUS_URL);
|
|
7
|
+
if (!res.ok) return false;
|
|
8
|
+
const data: { done: boolean } = await res.json();
|
|
9
|
+
return data.done === true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Mark wizard as done — calls API which sets the cookie server-side. */
|
|
16
|
+
export async function markWizardComplete(): Promise<boolean> {
|
|
17
|
+
try {
|
|
18
|
+
const res = await fetch('/api/wizard/complete', { method: 'POST' });
|
|
19
|
+
return res.ok;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Reset wizard — calls API which removes the cookie. */
|
|
26
|
+
export async function resetWizard(): Promise<boolean> {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch('/api/wizard/reset', { method: 'POST' });
|
|
29
|
+
return res.ok;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|