From 8fa9aa516e61fa7d6464db7f75b55319e2960776 Mon Sep 17 00:00:00 2001
From: space
Date: Sun, 26 Jul 2026 15:00:08 +0200
Subject: [PATCH 1/2] fix(ui): use app icon in header
Co-Authored-By: Codex
---
TODO.md | 2 +-
frontend/src/components/Layout.tsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/TODO.md b/TODO.md
index f2db8051..fcd0eada 100644
--- a/TODO.md
+++ b/TODO.md
@@ -3,7 +3,7 @@
## UI Related
- [ ] Fix Theming to be more professional (use frontend themeing skill & make it look like a professional product)
-
+- [x] replace emoji in the top left with the actual icon
## Future BS
- [ ] CPU, MEM, NET, DISK sentinal first-installed on EC2s to expose a backend for the user's ui to hit, THROUGH A BACKEND PROXY ROUTE so we can cache and rate limit the requests. This will allow for a better dashboard and better stats for the user to see. We'll use an obscure port for the sentinal so that we dont hit any other services the user may have. Sentinal is our own little C program exposing the stats we need, seperate repo for that thing tho!
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index 57e63331..306b405a 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -3,7 +3,7 @@ import { Link, useLocation, useNavigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
import { ThemePreference, useTheme } from "../hooks/useTheme";
import { api } from "../services/api";
-import { Rocket, Sun, Moon, Monitor } from "lucide-react";
+import { Sun, Moon, Monitor } from "lucide-react";
const themeOptions: { value: ThemePreference; label: string; Icon: typeof Sun }[] = [
{ value: "light", label: "Light", Icon: Sun },
@@ -42,7 +42,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
-
PR Previews
+
PR Previews
From a1c7ad71202a0e521ebca8412820ca5fd99dfeda Mon Sep 17 00:00:00 2001
From: space
Date: Sun, 26 Jul 2026 17:06:05 +0200
Subject: [PATCH 2/2] feat(setup): add ability to skip setup wizard and update
user model
---
backend/src/index.ts | 3 +-
backend/src/routes/api/user.ts | 12 ++++++
backend/src/routes/auth.ts | 14 ++++++-
frontend/src/hooks/useAuth.ts | 1 +
frontend/src/pages/SetupWizard.tsx | 41 +++++++++++++++----
frontend/src/services/api.ts | 1 +
.../migration.sql | 1 +
prisma/schema.prisma | 1 +
8 files changed, 64 insertions(+), 10 deletions(-)
create mode 100644 prisma/migrations/20260726170000_setup_skipped/migration.sql
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() {
Get Started
-
- Skip wizard
+
+ {saving ? "Skipping..." : "Skip wizard"}
@@ -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.
- navigate("/")} className="inline-flex items-center gap-1 px-6 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors">
- Go to Dashboard
+ leaveWizard("/")} disabled={saving} className="inline-flex items-center gap-1 px-6 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors disabled:opacity-50">
+ {saving ? "Finishing..." : Go to Dashboard }
)}
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