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.
| 59b6fb2 | 1 | /** |
| 2 | * Onboarding flow — guided setup for new users. | |
| 3 | */ | |
| 4 | ||
| 5 | import { Hono } from "hono"; | |
| 6 | import { Layout } from "../views/layout"; | |
| 7 | import { softAuth, requireAuth } from "../middleware/auth"; | |
| 8 | import type { AuthEnv } from "../middleware/auth"; | |
| 9 | import { eq, sql } from "drizzle-orm"; | |
| 10 | import { db } from "../db"; | |
| 11 | import { repositories, sshKeys, apiTokens, users } from "../db/schema"; | |
| 3e8f8e8 | 12 | import { |
| 13 | Container, | |
| 14 | WelcomeHero, | |
| 15 | StepIndicator, | |
| 16 | Card, | |
| 17 | Flex, | |
| 18 | Text, | |
| 19 | LinkButton, | |
| 20 | CopyBlock, | |
| 21 | Kbd, | |
| 22 | Spacer, | |
| 23 | } from "../views/ui"; | |
| 59b6fb2 | 24 | |
| 25 | const onboardingRoutes = new Hono<AuthEnv>(); | |
| 26 | ||
| 27 | onboardingRoutes.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}> | |
| 3e8f8e8 | 67 | <Container maxWidth={700}> |
| 68 | <WelcomeHero title="Welcome to gluecron" subtitle="Let's get you set up in a few steps." /> | |
| 59b6fb2 | 69 | |
| 3e8f8e8 | 70 | <div style="display:flex;justify-content:center;margin-bottom:40px"> |
| 71 | <StepIndicator steps={steps} /> | |
| 59b6fb2 | 72 | </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 | > | |
| 3e8f8e8 | 82 | <Spacer size={12} /> |
| 83 | <LinkButton href="/new" variant="primary">Create repository</LinkButton> | |
| 59b6fb2 | 84 | </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 | > | |
| 3e8f8e8 | 94 | <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" /> | |
| 59b6fb2 | 96 | </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 | <> | |
| 3e8f8e8 | 108 | <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> | |
| 59b6fb2 | 112 | </> |
| 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 | <> | |
| 3e8f8e8 | 125 | <Spacer size={12} /> |
| 126 | <Text size={14} muted> | |
| 59b6fb2 | 127 | Use tokens to authenticate with the gluecron API for scripting and automation. |
| 3e8f8e8 | 128 | </Text> |
| 129 | <Spacer size={12} /> | |
| 130 | <LinkButton href="/settings/tokens" variant="primary" size="sm">Create token</LinkButton> | |
| 59b6fb2 | 131 | </> |
| 132 | )} | |
| 133 | </StepCard> | |
| 134 | ||
| 135 | {/* All done */} | |
| 136 | {repoCount > 0 && hasKeys && hasTokens && ( | |
| 3e8f8e8 | 137 | <Card style="text-align:center;padding:40px 0;border-color:var(--green);margin-top:24px;background:rgba(63,185,80,0.05)"> |
| 59b6fb2 | 138 | <div style="font-size:48px;margin-bottom:12px">🎉</div> |
| 139 | <h2>You're all set!</h2> | |
| 3e8f8e8 | 140 | <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> | |
| 59b6fb2 | 147 | )} |
| 148 | ||
| 3e8f8e8 | 149 | <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> | |
| 59b6fb2 | 153 | </div> |
| 3e8f8e8 | 154 | </Container> |
| 59b6fb2 | 155 | </Layout> |
| 156 | ); | |
| 157 | }); | |
| 158 | ||
| 159 | const 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 | }) => ( | |
| 3e8f8e8 | 174 | <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"> | |
| 59b6fb2 | 176 | <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> | |
| 3e8f8e8 | 183 | <Text size={14} muted>{description}</Text> |
| 59b6fb2 | 184 | {children} |
| 185 | </div> | |
| 3e8f8e8 | 186 | </Flex> |
| 187 | </Card> | |
| 59b6fb2 | 188 | ); |
| 189 | ||
| 190 | export default onboardingRoutes; |