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.tsxBlame205 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
c63b860Claude31// P3 — `/onboarding` is the canonical post-register landing. Alias the
32// existing `/getting-started` handler so both URLs work; new users hit
33// /onboarding?welcome=1 and see a celebration banner.
34const gettingStartedHandler = async (c: any) => {
59b6fb2Claude35 const user = c.get("user")!;
c63b860Claude36 const welcome = c.req.query("welcome") === "1";
59b6fb2Claude37
38 // Check what the user has done
39 let repoCount = 0;
40 let hasKeys = false;
41 let hasTokens = false;
42
43 try {
44 const [repos] = await db
45 .select({ count: sql<number>`count(*)` })
46 .from(repositories)
47 .where(eq(repositories.ownerId, user.id));
48 repoCount = repos?.count ?? 0;
49
50 const [keys] = await db
51 .select({ count: sql<number>`count(*)` })
52 .from(sshKeys)
53 .where(eq(sshKeys.userId, user.id));
54 hasKeys = (keys?.count ?? 0) > 0;
55
56 const [tokens] = await db
57 .select({ count: sql<number>`count(*)` })
58 .from(apiTokens)
59 .where(eq(apiTokens.userId, user.id));
60 hasTokens = (tokens?.count ?? 0) > 0;
61 } catch { /* DB may not be ready */ }
62
80bed05Claude63 const firstRun = repoCount === 0;
59b6fb2Claude64
65 return c.html(
66 <Layout title="Getting Started" user={user}>
80bed05Claude67 <Container maxWidth={760}>
c63b860Claude68 {welcome && (
69 <div
70 data-onboarding-welcome="1"
71 style="margin: 12px 0 20px; padding: 14px 18px; border-radius: 12px; background: linear-gradient(135deg, rgba(140,109,255,0.18) 0%, rgba(54,197,214,0.18) 100%); border: 1px solid rgba(140,109,255,0.45); font-size: 14px; color: var(--text-strong)"
72 >
73 🎉 Welcome to Gluecron! Let's get you set up.
74 </div>
75 )}
80bed05Claude76 {/* ─── Welcome headline + 1-line value prop ─── */}
77 <WelcomeHero
78 title={firstRun ? `Welcome, ${user.username}` : "Finish setting up"}
79 subtitle="Ship safer code with AI-native hosting, automated CI, and push-time gates."
80 />
81
82 {/* ─── Three concrete next-step CTAs — the 60-second path ─── */}
83 {firstRun && (
84 <div class="panel" style="margin-bottom:20px">
85 <div class="panel-item" style="flex-direction:column;align-items:stretch;gap:4px;padding:16px">
86 <div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
87 <div style="flex:1">
88 <div style="font-size:15px;font-weight:600">Create a new repository</div>
89 <div style="font-size:13px;color:var(--text-muted);margin-top:2px">
90 Start from scratch. Green-ecosystem defaults, branch protection, labels, CODEOWNERS — all wired on day one.
91 </div>
92 </div>
93 <a href="/new" class="btn btn-primary">Create repo</a>
94 </div>
95 </div>
96 <div class="panel-item" style="flex-direction:column;align-items:stretch;gap:4px;padding:16px">
97 <div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
98 <div style="flex:1">
99 <div style="font-size:15px;font-weight:600">Import from GitHub</div>
100 <div style="font-size:13px;color:var(--text-muted);margin-top:2px">
101 Mirror an existing repo by URL. History, branches, and tags come across on the first sync.
102 </div>
103 </div>
104 <a href="/import" class="btn">Import repo</a>
105 </div>
106 </div>
107 <div class="panel-item" style="flex-direction:column;align-items:stretch;gap:4px;padding:16px">
108 <div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
109 <div style="flex:1">
110 <div style="font-size:15px;font-weight:600">Browse public repos</div>
111 <div style="font-size:13px;color:var(--text-muted);margin-top:2px">
112 See what others are building. Fork or star without leaving the platform.
113 </div>
114 </div>
115 <a href="/explore" class="btn">Browse</a>
116 </div>
117 </div>
118 </div>
59b6fb2Claude119 )}
120
80bed05Claude121 {/* ─── Existing users: show remaining setup as a compact checklist ─── */}
122 {!firstRun && (
123 <div class="panel" style="margin-bottom:20px">
124 <div class="panel-item" style="justify-content:space-between;padding:14px 16px">
125 <div>
126 <div style="font-size:14px;font-weight:600">
127 {"✓"} You have {repoCount} repositor{repoCount === 1 ? "y" : "ies"}
128 </div>
129 <div style="font-size:12px;color:var(--text-muted);margin-top:2px">
130 Push code, open issues, review PRs.
131 </div>
132 </div>
133 <a href="/dashboard" class="btn btn-sm">Open dashboard</a>
134 </div>
135 <div class="panel-item" style="justify-content:space-between;padding:14px 16px">
136 <div>
137 <div style="font-size:14px;font-weight:600">
138 {hasKeys ? "✓ SSH key added" : "Add an SSH key"}
139 </div>
140 <div style="font-size:12px;color:var(--text-muted);margin-top:2px">
141 {hasKeys ? "Push without passwords." : "Push without entering a password every time."}
142 </div>
143 </div>
144 {!hasKeys && <a href="/settings/keys" class="btn btn-sm">Add key</a>}
145 </div>
146 <div class="panel-item" style="justify-content:space-between;padding:14px 16px">
147 <div>
148 <div style="font-size:14px;font-weight:600">
149 {hasTokens ? "✓ API token ready" : "Create an API token"}
150 </div>
151 <div style="font-size:12px;color:var(--text-muted);margin-top:2px">
152 {hasTokens ? "Use it for CI, CLI, and automation." : "Authenticate scripts, CI, and the CLI."}
153 </div>
154 </div>
155 {!hasTokens && <a href="/settings/tokens" class="btn btn-sm">Create token</a>}
156 </div>
157 </div>
59b6fb2Claude158 )}
159
80bed05Claude160 {/* ─── Push snippet (only once the user has at least one repo) ─── */}
161 {!firstRun && (
162 <Card style="padding:16px;margin-bottom:20px">
163 <h3 style="font-size:14px;margin:0 0 8px 0">Push an existing project</h3>
164 <CopyBlock
ea52715copilot-swe-agent[bot]165 text={`git remote add gluecron ${config.appBaseUrl}/${user.username}/your-repo.git\ngit push -u gluecron main`}
80bed05Claude166 label="Commands"
167 />
168 </Card>
169 )}
59b6fb2Claude170
80bed05Claude171 {/* ─── All done celebration ─── */}
59b6fb2Claude172 {repoCount > 0 && hasKeys && hasTokens && (
80bed05Claude173 <Card style="text-align:center;padding:32px 0;border-color:var(--green);margin-bottom:20px;background:rgba(63,185,80,0.05)">
174 <div style="font-size:40px;margin-bottom:8px">&#127881;</div>
175 <h2 style="margin:0">You're all set.</h2>
176 <Text size={13} muted style="display:block;margin-top:6px">
177 Setup complete. Start building.
178 </Text>
179 <Flex gap={12} justify="center" style="margin-top:16px">
180 <LinkButton href="/dashboard" variant="primary">Open dashboard</LinkButton>
3e8f8e8Claude181 <LinkButton href="/explore">Discover repos</LinkButton>
182 </Flex>
183 </Card>
59b6fb2Claude184 )}
185
80bed05Claude186 {/* ─── Skip-to-dashboard + help ─── */}
187 <div style="text-align:center;padding:16px 0 32px 0">
188 <a href="/dashboard" style="font-size:13px;color:var(--text-muted);text-decoration:underline">
189 Skip to dashboard {"→"}
190 </a>
191 <div style="margin-top:12px">
192 <Text size={12} muted>
193 Need help? See the <a href="/api/docs">API docs</a> or press <Kbd>?</Kbd> for shortcuts.
194 </Text>
195 </div>
59b6fb2Claude196 </div>
3e8f8e8Claude197 </Container>
59b6fb2Claude198 </Layout>
199 );
c63b860Claude200};
201
202onboardingRoutes.get("/getting-started", softAuth, requireAuth, gettingStartedHandler);
203onboardingRoutes.get("/onboarding", softAuth, requireAuth, gettingStartedHandler);
59b6fb2Claude204
205export default onboardingRoutes;