feat: initial scaffold - backend, frontend, Prisma schema, Docker

- Prisma schema: User, Session, RepoConfig, Preview, Job, WebhookToken, NoConfigComment, AdminSettings
- Backend: auth (login/logout/me/first-user setup), webhook handler with HMAC verification, EC2 service, SSH service, deploy pipeline, job queue worker, cron workers
- Frontend: Login with first-user detection, Dashboard, PreviewDetail with live log streaming, Settings, Repos config, Admin panel, SetupWizard, Privacy page
- Docker Compose and Dockerfile for self-hosted deployment
- Uses bcryptjs for Node 24 compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 00:18:08 +02:00
parent ca2efdadea
commit 40d484bede
108 changed files with 13393 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
import React from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
import { useTheme } from "../hooks/useTheme";
import { api } from "../services/api";
import { toast } from "react-toastify";
export function Layout({ children }: { children: React.ReactNode }) {
const { user, refresh } = useAuth();
const { dark, toggle } = useTheme();
const location = useLocation();
const navigate = useNavigate();
const handleLogout = async () => {
await api.auth.logout();
await refresh();
navigate("/login");
};
const navLink = (to: string, label: string) => (
<Link
to={to}
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
location.pathname === to || location.pathname.startsWith(to + "/")
? "bg-blue-600 text-white"
: "text-gray-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-slate-800"
}`}
>
{label}
</Link>
);
return (
<div className="min-h-screen flex flex-col">
<header className="border-b border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-900 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 h-14 flex items-center justify-between gap-4">
<div className="flex items-center gap-2">
<Link to="/" className="font-bold text-lg text-blue-600 dark:text-blue-400 hover:opacity-80">
🚀 PR Previews
</Link>
</div>
{user && (
<nav className="flex items-center gap-1 overflow-x-auto">
{navLink("/", "Previews")}
{navLink("/repos", "Repos")}
{navLink("/settings", "Settings")}
{user.isAdmin && navLink("/admin", "Admin")}
</nav>
)}
<div className="flex items-center gap-2 shrink-0">
<button
onClick={toggle}
className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-slate-800 text-lg"
title="Toggle theme"
>
{dark ? "☀️" : "🌙"}
</button>
{user && (
<button
onClick={handleLogout}
className="text-sm px-3 py-1.5 rounded-md bg-gray-100 dark:bg-slate-800 hover:bg-gray-200 dark:hover:bg-slate-700 text-gray-700 dark:text-slate-300 transition-colors"
>
Logout
</button>
)}
</div>
</div>
</header>
<main className="flex-1 max-w-7xl mx-auto w-full px-4 py-6">
{children}
</main>
<footer className="border-t border-gray-200 dark:border-slate-700 py-4 text-center text-xs text-gray-500 dark:text-slate-400">
PR Previews self-hosted preview environments for every pull request.{" "}
<Link to="/privacy" className="underline hover:text-gray-700 dark:hover:text-slate-200">Privacy Policy</Link>
</footer>
</div>
);
}