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