Some SSG magic

This commit is contained in:
Konstantin Wohlwend 2025-10-20 23:14:34 -07:00
parent 86942380b1
commit 57ab0c55ac
6 changed files with 40 additions and 17 deletions

View File

@ -1,6 +1,12 @@
import { AppId } from "@stackframe/stack-shared/dist/apps/apps-config";
import { ALL_APPS, AppId } from "@stackframe/stack-shared/dist/apps/apps-config";
import AppDetailsModalPageClient from "./page-client";
export const generateStaticParams = async () => {
return Object.keys(ALL_APPS).map(appId => ({ appId }));
};
export const dynamicParams = false;
export default async function AppDetailsModalPage({ params }: { params: Promise<{ appId: AppId }> }) {
const appId = (await params).appId;

View File

@ -1,6 +1,12 @@
import { AppId } from "@stackframe/stack-shared/dist/apps/apps-config";
import { ALL_APPS, AppId } from "@stackframe/stack-shared/dist/apps/apps-config";
import AppDetailsPageClient from "./page-client";
export const generateStaticParams = async () => {
return Object.keys(ALL_APPS).map(appId => ({ appId }));
};
export const dynamicParams = false;
export default async function AppDetailsPage({ params }: { params: Promise<{ appId: AppId }> }) {
const appId = (await params).appId;

View File

@ -8,7 +8,7 @@ type Params = {
projectId: string,
};
export default async function Page({ params }: { params: Promise<Params> }) {
export default async function Page() {
return (
<PageClient />
);

View File

@ -8,12 +8,12 @@ export default async function Layout(
props: { children: React.ReactNode, modal?: React.ReactNode, params: Promise<{ projectId: string }> }
) {
return (
<AdminAppProvider projectId={(await props.params).projectId}>
<AdminAppProvider>
{/* Pre-fetch the current URL to prevent request waterfalls */}
<UrlPrefetcher href="" />
<SidebarLayout projectId={(await props.params).projectId}>
<SidebarLayout>
<Suspense fallback={<SiteLoadingIndicator />}>
{props.children}
{props.modal}

View File

@ -11,6 +11,7 @@ import { cn } from "@/lib/utils";
import { UserButton, useUser } from "@stackframe/stack";
import { ALL_APPS, type AppId } from "@stackframe/stack-shared/dist/apps/apps-config";
import { typedEntries } from "@stackframe/stack-shared/dist/utils/objects";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { getRelativePart } from "@stackframe/stack-shared/dist/utils/urls";
import {
Breadcrumb,
@ -34,8 +35,7 @@ import {
import { useTheme } from "next-themes";
import { usePathname } from "next/navigation";
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import { useAdminApp } from "./use-admin-app";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { useAdminApp, useProjectId } from "./use-admin-app";
type BreadcrumbItem = { item: React.ReactNode, href: string };
@ -463,10 +463,11 @@ function HeaderBreadcrumb({
}
}
export default function SidebarLayout(props: { projectId: string, children?: React.ReactNode }) {
export default function SidebarLayout(props: { children?: React.ReactNode }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [companionExpanded, setCompanionExpanded] = useState(false);
const { resolvedTheme, setTheme } = useTheme();
const projectId = useProjectId();
return (
<div className="w-full flex">
@ -482,7 +483,7 @@ export default function SidebarLayout(props: { projectId: string, children?: Rea
*/}
<div className="absolute inset-0 backdrop-blur-md z-[-1]"></div>
<SidebarContent projectId={props.projectId} />
<SidebarContent projectId={projectId} />
</div>
{/* Main Content Area */}
@ -490,7 +491,7 @@ export default function SidebarLayout(props: { projectId: string, children?: Rea
{/* Header */}
<div className="h-14 border-b flex items-center justify-between sticky top-0 backdrop-blur-md bg-slate-200/20 dark:bg-black/20 z-10 px-4 lg:px-6">
<div className="hidden lg:flex">
<HeaderBreadcrumb projectId={props.projectId} />
<HeaderBreadcrumb projectId={projectId} />
</div>
<div className="flex lg:hidden items-center">
@ -504,18 +505,18 @@ export default function SidebarLayout(props: { projectId: string, children?: Rea
<SheetContent
aria-describedby={undefined}
side='left' className="w-[240px] p-0" hasCloseButton={false}>
<SidebarContent projectId={props.projectId} onNavigate={() => setSidebarOpen(false)} />
<SidebarContent projectId={projectId} onNavigate={() => setSidebarOpen(false)} />
</SheetContent>
</Sheet>
<div className="ml-4 flex lg:hidden">
<HeaderBreadcrumb projectId={props.projectId} mobile />
<HeaderBreadcrumb projectId={projectId} mobile />
</div>
</div>
<div className="flex gap-2 relative items-center">
<Button asChild variant="ghost" size="icon" className="hidden lg:flex">
<Link href={`/projects/${props.projectId}/project-settings`}>
<Link href={`/projects/${projectId}/project-settings`}>
<Settings className="w-4 h-4" />
</Link>
</Button>

View File

@ -1,14 +1,15 @@
"use client";
import { StackAdminApp, useUser } from "@stackframe/stack";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { notFound } from "next/navigation";
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { notFound, usePathname } from "next/navigation";
import React from "react";
const StackAdminAppContext = React.createContext<StackAdminApp<false> | null>(null);
export function AdminAppProvider(props: { projectId: string, children: React.ReactNode }) {
const app = useAdminApp(props.projectId);
export function AdminAppProvider(props: { children: React.ReactNode }) {
const projectId = useProjectId();
const app = useAdminApp(projectId);
return (
<StackAdminAppContext.Provider value={app}>
{props.children}
@ -41,3 +42,12 @@ export function useAdminApp(projectId?: string) {
return providedApp ?? throwErr("useAdminApp must be used within an AdminInterfaceProvider");
}
}
export function useProjectId() {
const pathname = usePathname();
if (!pathname.startsWith("/projects/")) {
throw new StackAssertionError("useProjectId must be used within a project route");
}
const projectId = pathname.split("/")[2];
return projectId;
}