diff --git a/backend/src/index.ts b/backend/src/index.ts index dd26f24d..6346aa88 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -18,7 +18,7 @@ import { runWebhookReconciliation } from "./services/webhookReconcile"; import { loginHandler, logoutHandler, meHandler, setupStatusHandler, firstUserHandler } from "./routes/auth"; import { webhookHandler } from "./routes/webhook"; -import { getUserSettings, updateUsername, updatePassword, updateGitea, updateAws, getWebhookSecret, regenerateWebhookSecret } from "./routes/api/user"; +import { getUserSettings, updateUsername, updatePassword, updateGitea, updateAws, skipSetupWizard, getWebhookSecret, regenerateWebhookSecret } from "./routes/api/user"; import { listRepos, saveRepoConfig, toggleRepoEnabled, getRepoConfig } from "./routes/api/repos"; import { listPreviews, getPreview, stopPreviewRoute, rebuildPreviewRoute, previewLogsWs, previewAppLogsWs } from "./routes/api/previews"; import { getStats } from "./routes/api/stats"; @@ -66,6 +66,7 @@ server.path("/", (path) => path .http("PATCH", "/api/user/password", (http) => http.onRequest(updatePassword)) .http("PUT", "/api/user/gitea", (http) => http.onRequest(updateGitea)) .http("PUT", "/api/user/aws", (http) => http.onRequest(updateAws)) + .http("POST", "/api/user/setup/skip", (http) => http.onRequest(skipSetupWizard)) .http("GET", "/api/user/webhook-secret", (http) => http.onRequest(getWebhookSecret)) .http("POST", "/api/user/webhook-secret/regenerate", (http) => http.onRequest(regenerateWebhookSecret)) ); diff --git a/backend/src/routes/api/user.ts b/backend/src/routes/api/user.ts index 469bbc57..117f6f75 100644 --- a/backend/src/routes/api/user.ts +++ b/backend/src/routes/api/user.ts @@ -181,6 +181,18 @@ export async function updateAws(ctr: any) { return makeResponse({ ctr, content: { code: 200, message: `Connected as ${validation.arn}`, data: { arn: validation.arn } } }); } +export async function skipSetupWizard(ctr: any) { + const user = requireAuth(ctr); + if (!user) return makeResponse({ ctr, content: { code: 401, message: ERROR_MESSAGES.UNAUTHORIZED.message } }); + + await prisma.user.update({ + where: { id: user.id }, + data: { setupSkipped: true }, + }); + + return makeResponse({ ctr, content: { code: 200, message: "Setup wizard skipped" } }); +} + export async function getWebhookSecret(ctr: any) { const user = requireAuth(ctr); if (!user) return makeResponse({ ctr, content: { code: 401, message: ERROR_MESSAGES.UNAUTHORIZED.message } }); diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 7ad9a7c3..410ce96a 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -10,6 +10,17 @@ const log = createLogger("AUTH_ROUTE"); const COOKIE_NAME = "pp_session"; const COOKIE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000; +function isSetupComplete(user: { + setupSkipped: boolean; + giteaInstanceUrl: string | null; + giteaPAT: string | null; + awsAccessKeyId: string | null; + awsSecretAccessKey: string | null; + awsRegion: string | null; +}) { + return user.setupSkipped || !!(user.giteaInstanceUrl && user.giteaPAT && user.awsAccessKeyId && user.awsSecretAccessKey && user.awsRegion); +} + export async function loginHandler(ctr: any) { let body: any; try { @@ -82,7 +93,8 @@ export async function meHandler(ctr: any) { awsAccessKeyId: user.awsAccessKeyId ? "****" : null, awsRegion: user.awsRegion, awsConfigured: !!(user.awsAccessKeyId && user.awsSecretAccessKey && user.awsRegion), - setupComplete: !!(user.giteaInstanceUrl && user.giteaPAT && user.awsAccessKeyId && user.awsSecretAccessKey && user.awsRegion), + setupSkipped: user.setupSkipped, + setupComplete: isSetupComplete(user), }, }, }); diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 2db1583b..eac4af1a 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -6,6 +6,7 @@ export interface AuthUser { username: string; isAdmin: boolean; isFounder: boolean; + setupSkipped: boolean; setupComplete: boolean; giteaUsername: string | null; giteaInstanceUrl: string | null; diff --git a/frontend/src/pages/SetupWizard.tsx b/frontend/src/pages/SetupWizard.tsx index af7a144e..97b4619e 100644 --- a/frontend/src/pages/SetupWizard.tsx +++ b/frontend/src/pages/SetupWizard.tsx @@ -44,7 +44,32 @@ export function SetupWizard() { }, [step, user?.id]); const next = () => setStep(s => Math.min(s + 1, STEPS.length - 1)); - const skip = () => navigate("/"); + const skip = async () => { + setSaving(true); + const res = await api.user.skipSetup(); + setSaving(false); + if (res.ok) { + await refresh(); + navigate("/"); + } else { + toast.error(res.message || "Failed to skip setup"); + } + }; + const leaveWizard = async (path: string) => { + if (user?.setupComplete) { + navigate(path); + return; + } + setSaving(true); + const res = await api.user.skipSetup(); + setSaving(false); + if (res.ok) { + await refresh(); + navigate(path); + } else { + toast.error(res.message || "Failed to finish setup"); + } + }; const handleGiteaNext = async () => { setSaving(true); @@ -112,8 +137,8 @@ export function SetupWizard() { - @@ -211,10 +236,10 @@ export function SetupWizard() { Go to the Repos page to enable previews for a repository. PP will automatically register the webhook.

- +
@@ -227,8 +252,8 @@ export function SetupWizard() {

Open a pull request on an enabled repo and PP will provision a preview automatically.

- )} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index a20fb55d..b947a2cc 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -37,6 +37,7 @@ export const api = { request("PUT", "/user/gitea", data), updateAws: (data: { awsAccessKeyId: string; awsSecretAccessKey: string; awsRegion: string }) => request("PUT", "/user/aws", data), + skipSetup: () => request("POST", "/user/setup/skip"), getWebhookSecret: () => request("GET", "/user/webhook-secret"), regenerateWebhookSecret: () => request("POST", "/user/webhook-secret/regenerate"), }, diff --git a/prisma/migrations/20260726170000_setup_skipped/migration.sql b/prisma/migrations/20260726170000_setup_skipped/migration.sql new file mode 100644 index 00000000..55bf12d8 --- /dev/null +++ b/prisma/migrations/20260726170000_setup_skipped/migration.sql @@ -0,0 +1 @@ +ALTER TABLE "User" ADD COLUMN "setupSkipped" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 25bec2d3..888e80e9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -36,6 +36,7 @@ model User { passwordHash String @db.VarChar(256) isAdmin Boolean @default(false) isFounder Boolean @default(false) + setupSkipped Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt