Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

onboarding.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

onboarding.tsxBlame189 lines · 1 contributor
59b6fb2Claude1/**
2 * Onboarding flow — guided setup for new users.
80bed05Claude3 *
4 * Goal: get a fresh user from 0 to first repo in <60 seconds.
5 * Headline + 1-line value prop + 3 concrete next-step CTAs + skip-to-dashboard.
59b6fb2Claude6 */
7
8import { Hono } from "hono";
9import { Layout } from "../views/layout";
10import { softAuth, requireAuth } from "../middleware/auth";
11import type { AuthEnv } from "../middleware/auth";
12import { eq, sql } from "drizzle-orm";
13import { db } from "../db";
14import { repositories, sshKeys, apiTokens, users } from "../db/schema";
3e8f8e8Claude15import {
16 Container,
17 WelcomeHero,
18 StepIndicator,
19 Card,
20 Flex,
21 Text,
22 LinkButton,
23 CopyBlock,
24 Kbd,
25 Spacer,
26} from "../views/ui";
59b6fb2Claude27
28const onboardingRoutes = new Hono<AuthEnv>();
29
30onboardingRoutes.get("/getting-started", softAuth, requireAuth, async (c) => {
31 const user = c.get("user")!;
32
33 // Check what the user has done
34 let repoCount = 0;
35 let hasKeys = false;
36 let hasTokens = false;
37
38 try {
39 const [repos] = await db
40 .select({ count: sql<number>`count(*)` })
41 .from(repositories)
42 .where(eq(repositories.ownerId, user.id));
43 repoCount = repos?.count ?? 0;
44
45 const [keys] = await db
46 .select({ count: sql<number>`count(*)` })
47 .from(sshKeys)
48 .where(eq(sshKeys.userId, user.id));
49 hasKeys = (keys?.count ?? 0) > 0;
50
51 const [tokens] = await db
52 .select({ count: sql<number>`count(*)` })
53 .from(apiTokens)
54 .where(eq(apiTokens.userId, user.id));
55 hasTokens = (tokens?.count ?? 0) > 0;
56 } catch { /* DB may not be ready */ }
57
80bed05Claude58 const firstRun = repoCount === 0;
59b6fb2Claude59
60 return c.html(
61 <Layout title="Getting Started" user={user}>
80bed05Claude62 <Container maxWidth={760}>
63 {/* ─── Welcome headline + 1-line value prop ─── */}
64 <WelcomeHero
65 title={firstRun ? `Welcome, ${user.username}` : "Finish setting up"}
66 subtitle="Ship safer code with AI-native hosting, automated CI, and push-time gates."
67 />
68
69 {/* ─── Three concrete next-step CTAs — the 60-second path ─── */}
70 {firstRun && (
71 <div class="panel" style="margin-bottom:20px">
72 <div class="panel-item" style="flex-direction:column;align-items:stretch;gap:4px;padding:16px">
73 <div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
74 <div style="flex:1">
75 <div style="font-size:15px;font-weight:600">Create a new repository</div>
76 <div style="font-size:13px;color:var(--text-muted);margin-top:2px">
77 Start from scratch. Green-ecosystem defaults, branch protection, labels, CODEOWNERS — all wired on day one.
78 </div>
79 </div>
80 <a href="/new" class="btn btn-primary">Create repo</a>
81 </div>
82 </div>
83 <div class="panel-item" style="flex-direction:column;align-items:stretch;gap:4px;padding:16px">
84 <div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
85 <div style="flex:1">
86 <div style="font-size:15px;font-weight:600">Import from GitHub</div>
87 <div style="font-size:13px;color:var(--text-muted);margin-top:2px">
88 Mirror an existing repo by URL. History, branches, and tags come across on the first sync.
89 </div>
90 </div>
91 <a href="/import" class="btn">Import repo</a>
92 </div>
93 </div>
94 <div class="panel-item" style="flex-direction:column;align-items:stretch;gap:4px;padding:16px">
95 <div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
96 <div style="flex:1">
97 <div style="font-size:15px;font-weight:600">Browse public repos</div>
98 <div style="font-size:13px;color:var(--text-muted);margin-top:2px">
99 See what others are building. Fork or star without leaving the platform.
100 </div>
101 </div>
102 <a href="/explore" class="btn">Browse</a>
103 </div>
104 </div>
105 </div>
59b6fb2Claude106 )}
107
80bed05Claude108 {/* ─── Existing users: show remaining setup as a compact checklist ─── */}
109 {!firstRun && (
110 <div class="panel" style="margin-bottom:20px">
111 <div class="panel-item" style="justify-content:space-between;padding:14px 16px">
112 <div>
113 <div style="font-size:14px;font-weight:600">
114 {"✓"} You have {repoCount} repositor{repoCount === 1 ? "y" : "ies"}
115 </div>
116 <div style="font-size:12px;color:var(--text-muted);margin-top:2px">
117 Push code, open issues, review PRs.
118 </div>
119 </div>
120 <a href="/dashboard" class="btn btn-sm">Open dashboard</a>
121 </div>
122 <div class="panel-item" style="justify-content:space-between;padding:14px 16px">
123 <div>
124 <div style="font-size:14px;font-weight:600">
125 {hasKeys ? "✓ SSH key added" : "Add an SSH key"}
126 </div>
127 <div style="font-size:12px;color:var(--text-muted);margin-top:2px">
128 {hasKeys ? "Push without passwords." : "Push without entering a password every time."}
129 </div>
130 </div>
131 {!hasKeys && <a href="/settings/keys" class="btn btn-sm">Add key</a>}
132 </div>
133 <div class="panel-item" style="justify-content:space-between;padding:14px 16px">
134 <div>
135 <div style="font-size:14px;font-weight:600">
136 {hasTokens ? "✓ API token ready" : "Create an API token"}
137 </div>
138 <div style="font-size:12px;color:var(--text-muted);margin-top:2px">
139 {hasTokens ? "Use it for CI, CLI, and automation." : "Authenticate scripts, CI, and the CLI."}
140 </div>
141 </div>
142 {!hasTokens && <a href="/settings/tokens" class="btn btn-sm">Create token</a>}
143 </div>
144 </div>
59b6fb2Claude145 )}
146
80bed05Claude147 {/* ─── Push snippet (only once the user has at least one repo) ─── */}
148 {!firstRun && (
149 <Card style="padding:16px;margin-bottom:20px">
150 <h3 style="font-size:14px;margin:0 0 8px 0">Push an existing project</h3>
151 <CopyBlock
152 text={`git remote add gluecron http://localhost:3000/${user.username}/your-repo.git\ngit push -u gluecron main`}
153 label="Commands"
154 />
155 </Card>
156 )}
59b6fb2Claude157
80bed05Claude158 {/* ─── All done celebration ─── */}
59b6fb2Claude159 {repoCount > 0 && hasKeys && hasTokens && (
80bed05Claude160 <Card style="text-align:center;padding:32px 0;border-color:var(--green);margin-bottom:20px;background:rgba(63,185,80,0.05)">
161 <div style="font-size:40px;margin-bottom:8px">&#127881;</div>
162 <h2 style="margin:0">You're all set.</h2>
163 <Text size={13} muted style="display:block;margin-top:6px">
164 Setup complete. Start building.
165 </Text>
166 <Flex gap={12} justify="center" style="margin-top:16px">
167 <LinkButton href="/dashboard" variant="primary">Open dashboard</LinkButton>
3e8f8e8Claude168 <LinkButton href="/explore">Discover repos</LinkButton>
169 </Flex>
170 </Card>
59b6fb2Claude171 )}
172
80bed05Claude173 {/* ─── Skip-to-dashboard + help ─── */}
174 <div style="text-align:center;padding:16px 0 32px 0">
175 <a href="/dashboard" style="font-size:13px;color:var(--text-muted);text-decoration:underline">
176 Skip to dashboard {"→"}
177 </a>
178 <div style="margin-top:12px">
179 <Text size={12} muted>
180 Need help? See the <a href="/api/docs">API docs</a> or press <Kbd>?</Kbd> for shortcuts.
181 </Text>
182 </div>
59b6fb2Claude183 </div>
3e8f8e8Claude184 </Container>
59b6fb2Claude185 </Layout>
186 );
187});
188
189export default onboardingRoutes;