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 · 1 contributor
59b6fb2Claude1/**
2 * Onboarding flow — guided setup for new users.
3 */
4
5import { Hono } from "hono";
6import { Layout } from "../views/layout";
7import { softAuth, requireAuth } from "../middleware/auth";
8import type { AuthEnv } from "../middleware/auth";
9import { eq, sql } from "drizzle-orm";
10import { db } from "../db";
11import { repositories, sshKeys, apiTokens, users } from "../db/schema";
3e8f8e8Claude12import {
13 Container,
14 WelcomeHero,
15 StepIndicator,
16 Card,
17 Flex,
18 Text,
19 LinkButton,
20 CopyBlock,
21 Kbd,
22 Spacer,
23} from "../views/ui";
59b6fb2Claude24
25const onboardingRoutes = new Hono<AuthEnv>();
26
27onboardingRoutes.get("/getting-started", softAuth, requireAuth, async (c) => {
28 const user = c.get("user")!;
29
30 // Check what the user has done
31 let repoCount = 0;
32 let hasKeys = false;
33 let hasTokens = false;
34
35 try {
36 const [repos] = await db
37 .select({ count: sql<number>`count(*)` })
38 .from(repositories)
39 .where(eq(repositories.ownerId, user.id));
40 repoCount = repos?.count ?? 0;
41
42 const [keys] = await db
43 .select({ count: sql<number>`count(*)` })
44 .from(sshKeys)
45 .where(eq(sshKeys.userId, user.id));
46 hasKeys = (keys?.count ?? 0) > 0;
47
48 const [tokens] = await db
49 .select({ count: sql<number>`count(*)` })
50 .from(apiTokens)
51 .where(eq(apiTokens.userId, user.id));
52 hasTokens = (tokens?.count ?? 0) > 0;
53 } catch { /* DB may not be ready */ }
54
55 const steps = [
56 { label: "Create account", completed: true, active: false },
57 { label: "Create a repository", completed: repoCount > 0, active: repoCount === 0 },
58 { label: "Push your code", completed: false, active: repoCount > 0 },
59 { label: "Set up SSH key", completed: hasKeys, active: !hasKeys && repoCount > 0 },
60 { label: "Create API token", completed: hasTokens, active: !hasTokens && hasKeys },
61 ];
62
63 const activeStep = steps.findIndex((s) => s.active);
64
65 return c.html(
66 <Layout title="Getting Started" user={user}>
3e8f8e8Claude67 <Container maxWidth={700}>
68 <WelcomeHero title="Welcome to gluecron" subtitle="Let's get you set up in a few steps." />
59b6fb2Claude69
3e8f8e8Claude70 <div style="display:flex;justify-content:center;margin-bottom:40px">
71 <StepIndicator steps={steps} />
59b6fb2Claude72 </div>
73
74 {/* Step 1: Create repository */}
75 {activeStep <= 1 && repoCount === 0 && (
76 <StepCard
77 number={1}
78 title="Create your first repository"
79 description="A repository contains all your project files, including the revision history."
80 active
81 >
3e8f8e8Claude82 <Spacer size={12} />
83 <LinkButton href="/new" variant="primary">Create repository</LinkButton>
59b6fb2Claude84 </StepCard>
85 )}
86
87 {/* Step 2: Push code */}
88 {repoCount > 0 && (
89 <StepCard
90 number={2}
91 title="Push your code"
92 description="Connect your local repository and push your first commit."
93 >
3e8f8e8Claude94 <Spacer size={12} />
95 <CopyBlock text={`git remote add gluecron http://localhost:3000/${user.username}/your-repo.git\ngit push -u gluecron main`} label="Commands" />
59b6fb2Claude96 </StepCard>
97 )}
98
99 {/* Step 3: SSH key */}
100 <StepCard
101 number={3}
102 title={hasKeys ? "SSH key added \u2713" : "Add an SSH key"}
103 description={hasKeys ? "Your SSH key is configured." : "SSH keys let you push code securely without entering your password."}
104 completed={hasKeys}
105 >
106 {!hasKeys && (
107 <>
3e8f8e8Claude108 <Spacer size={12} />
109 <CopyBlock text={`ssh-keygen -t ed25519 -C "your@email.com"\ncat ~/.ssh/id_ed25519.pub`} label="Generate & copy key" />
110 <Spacer size={12} />
111 <LinkButton href="/settings/keys" variant="primary" size="sm">Add SSH key</LinkButton>
59b6fb2Claude112 </>
113 )}
114 </StepCard>
115
116 {/* Step 4: API token */}
117 <StepCard
118 number={4}
119 title={hasTokens ? "API token created \u2713" : "Create an API token"}
120 description={hasTokens ? "You have an API token configured." : "API tokens let you automate workflows and integrate with CI/CD."}
121 completed={hasTokens}
122 >
123 {!hasTokens && (
124 <>
3e8f8e8Claude125 <Spacer size={12} />
126 <Text size={14} muted>
59b6fb2Claude127 Use tokens to authenticate with the gluecron API for scripting and automation.
3e8f8e8Claude128 </Text>
129 <Spacer size={12} />
130 <LinkButton href="/settings/tokens" variant="primary" size="sm">Create token</LinkButton>
59b6fb2Claude131 </>
132 )}
133 </StepCard>
134
135 {/* All done */}
136 {repoCount > 0 && hasKeys && hasTokens && (
3e8f8e8Claude137 <Card style="text-align:center;padding:40px 0;border-color:var(--green);margin-top:24px;background:rgba(63,185,80,0.05)">
59b6fb2Claude138 <div style="font-size:48px;margin-bottom:12px">&#127881;</div>
139 <h2>You're all set!</h2>
3e8f8e8Claude140 <Text size={14} muted style="display:block;margin-top:8px">You've completed the setup. Start building something great.</Text>
141 <Flex gap={12} justify="center" style="margin-top:20px">
142 <LinkButton href="/" variant="primary">Go to dashboard</LinkButton>
143 <LinkButton href="/api/docs">Explore the API</LinkButton>
144 <LinkButton href="/explore">Discover repos</LinkButton>
145 </Flex>
146 </Card>
59b6fb2Claude147 )}
148
3e8f8e8Claude149 <div style="text-align:center;padding:32px 0">
150 <Text size={13} muted>
151 Need help? Check the <a href="/api/docs">API documentation</a> or press <Kbd>?</Kbd> for keyboard shortcuts.
152 </Text>
59b6fb2Claude153 </div>
3e8f8e8Claude154 </Container>
59b6fb2Claude155 </Layout>
156 );
157});
158
159const StepCard = ({
160 number,
161 title,
162 description,
163 active,
164 completed,
165 children,
166}: {
167 number: number;
168 title: string;
169 description: string;
170 active?: boolean;
171 completed?: boolean;
172 children?: any;
173}) => (
3e8f8e8Claude174 <Card style={`border-color:${completed ? "var(--green)" : active ? "var(--accent)" : "var(--border)"};padding:20px;margin-bottom:16px;${completed ? "opacity:0.7;" : ""}`}>
175 <Flex gap={12} align="flex-start">
59b6fb2Claude176 <div
177 style={`width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:600;flex-shrink:0;${completed ? "background:var(--green);color:#fff" : "background:var(--bg-tertiary);color:var(--text-muted)"}`}
178 >
179 {completed ? "\u2713" : number}
180 </div>
181 <div style="flex:1">
182 <h3 style="font-size:16px;margin-bottom:4px">{title}</h3>
3e8f8e8Claude183 <Text size={14} muted>{description}</Text>
59b6fb2Claude184 {children}
185 </div>
3e8f8e8Claude186 </Flex>
187 </Card>
59b6fb2Claude188);
189
190export default onboardingRoutes;