Skip to content

Commit e843a2b

Browse files
BilalG1N2D4
andauthored
svix embedded portal (#1007)
https://www.loom.com/share/ade557d34b674ecb9ae1d703b5332c9d <!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for inline webhook configuration portal rendering when available * Enhanced webhooks page with improved theming support * **Refactor** * Updated webhook token API to return structured data including optional server URL alongside token <!-- end of auto-generated comment: release notes by coderabbit.ai --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Enables embedded Svix portal on the Webhooks page when available, updating the token API and shared types to return an optional portal URL and wiring it through the admin app. > > - **Frontend (Dashboard Webhooks page)**: > - Conditionally render Svix `AppPortal` when `svixToken.url` is provided; otherwise fall back to `SvixProvider` with token. > - Integrate theme support (`next-themes`) for portal `darkMode`; import `svix-react` styles. > - **Backend (API)**: > - Update `POST /api/latest/webhooks/svix-token` to return `{ token, url? }`, deriving `url` only when no `STACK_SVIX_SERVER_URL` is set. > - **Shared Types/SDK**: > - Extend `svixTokenAdminReadSchema` to include optional `url`. > - Change admin app `useSvixToken()` to return `{ token, url }` and propagate through implementation. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9f5dc52. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
1 parent 6763f1a commit e843a2b

5 files changed

Lines changed: 61 additions & 44 deletions

File tree

apps/backend/src/app/api/latest/webhooks/svix-token/route.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ import { getSvixClient } from "@/lib/webhooks";
22
import { createCrudHandlers } from "@/route-handlers/crud-handler";
33
import { svixTokenCrud } from "@stackframe/stack-shared/dist/interface/crud/svix-token";
44
import { yupObject } from "@stackframe/stack-shared/dist/schema-fields";
5+
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
56
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
67

8+
const svixServerUrl = getEnvVariable("STACK_SVIX_SERVER_URL", "");
9+
710
const appPortalCrudHandlers = createLazyProxy(() => createCrudHandlers(svixTokenCrud, {
811
paramsSchema: yupObject({}),
912
onCreate: async ({ auth }) => {
1013
const svix = getSvixClient();
1114
await svix.application.getOrCreate({ uid: auth.project.id, name: auth.project.id });
1215
const result = await svix.authentication.appPortalAccess(auth.project.id, {});
13-
return { token: result.token };
16+
// svix embedded app portal is only available on hosted svix.
17+
const url = svixServerUrl ? undefined : result.url;
18+
return { token: result.token, url };
1419
},
1520
}));
1621

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,56 @@ import { PageLayout } from "../page-layout";
1717
import { useAdminApp } from "../use-admin-app";
1818
import { getSvixResult } from "./utils";
1919
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
20+
import { useTheme } from "next-themes";
21+
import { AppPortal } from "svix-react";
22+
import "svix-react/style.css";
23+
24+
25+
export default function PageClient() {
26+
const stackAdminApp = useAdminApp();
27+
const svixToken = stackAdminApp.useSvixToken();
28+
const { resolvedTheme } = useTheme();
29+
const [updateCounter, setUpdateCounter] = useState(0);
30+
const [testDialogEndpoint, setTestDialogEndpoint] = useState<Endpoint | null>(null);
31+
32+
return (
33+
<AppEnabledGuard appId="webhooks">
34+
<PageLayout
35+
title="Webhooks"
36+
description="Webhooks are used to sync users and teams events from Stack to your own server."
37+
>
38+
{svixToken.url ? (
39+
<div>
40+
<AppPortal url={svixToken.url} darkMode={resolvedTheme === "dark"} fullSize />
41+
</div>
42+
) : (
43+
<SvixProvider
44+
key={updateCounter}
45+
token={svixToken.token}
46+
appId={stackAdminApp.projectId}
47+
options={{ serverUrl: getPublicEnvVar('NEXT_PUBLIC_STACK_SVIX_SERVER_URL') }}
48+
>
49+
<Endpoints
50+
updateFn={() => setUpdateCounter(x => x + 1)}
51+
onTestRequested={(endpoint) => setTestDialogEndpoint(endpoint)}
52+
/>
53+
{testDialogEndpoint && (
54+
<TestEndpointDialog
55+
endpoint={testDialogEndpoint}
56+
open
57+
onOpenChange={(open) => {
58+
if (!open) {
59+
setTestDialogEndpoint(null);
60+
}
61+
}}
62+
/>
63+
)}
64+
</SvixProvider>
65+
)}
66+
</PageLayout>
67+
</AppEnabledGuard>
68+
);
69+
}
2070

2171
type Endpoint = {
2272
id: string,
@@ -157,7 +207,7 @@ function CreateDialog(props: {
157207
);
158208
}
159209

160-
export function EndpointEditDialog(props: {
210+
function EndpointEditDialog(props: {
161211
open: boolean,
162212
onClose: () => void,
163213
endpoint: Endpoint,
@@ -369,42 +419,3 @@ function Endpoints(props: { updateFn: () => void, onTestRequested: (endpoint: En
369419
);
370420
}
371421
}
372-
373-
export default function PageClient() {
374-
const stackAdminApp = useAdminApp();
375-
const svixToken = stackAdminApp.useSvixToken();
376-
const [updateCounter, setUpdateCounter] = useState(0);
377-
const [testDialogEndpoint, setTestDialogEndpoint] = useState<Endpoint | null>(null);
378-
379-
return (
380-
<AppEnabledGuard appId="webhooks">
381-
<PageLayout
382-
title="Webhooks"
383-
description="Webhooks are used to sync users and teams events from Stack to your own server."
384-
>
385-
<SvixProvider
386-
key={updateCounter}
387-
token={svixToken}
388-
appId={stackAdminApp.projectId}
389-
options={{ serverUrl: getPublicEnvVar('NEXT_PUBLIC_STACK_SVIX_SERVER_URL') }}
390-
>
391-
<Endpoints
392-
updateFn={() => setUpdateCounter(x => x + 1)}
393-
onTestRequested={(endpoint) => setTestDialogEndpoint(endpoint)}
394-
/>
395-
{testDialogEndpoint && (
396-
<TestEndpointDialog
397-
endpoint={testDialogEndpoint}
398-
open
399-
onOpenChange={(open) => {
400-
if (!open) {
401-
setTestDialogEndpoint(null);
402-
}
403-
}}
404-
/>
405-
)}
406-
</SvixProvider>
407-
</PageLayout>
408-
</AppEnabledGuard>
409-
);
410-
}

packages/stack-shared/src/interface/crud/svix-token.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { yupObject, yupString } from "../../schema-fields";
33

44
export const svixTokenAdminReadSchema = yupObject({
55
token: yupString().defined(),
6+
url: yupString().optional(),
67
}).defined();
78

89
export const svixTokenAdminCreateSchema = yupObject({}).defined();

packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,9 +416,9 @@ export class _StackAdminAppImplIncomplete<HasTokenStore extends boolean, Project
416416
}
417417
// END_PLATFORM
418418
// IF_PLATFORM react-like
419-
useSvixToken(): string {
419+
useSvixToken(): { token: string, url: string | undefined } {
420420
const crud = useAsyncCache(this._svixTokenCache, [], "adminApp.useSvixToken()");
421-
return crud.token;
421+
return { token: crud.token, url: crud.url };
422422
}
423423
// END_PLATFORM
424424

packages/template/src/lib/stack-app/apps/interfaces/admin-app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export type StackAdminApp<HasTokenStore extends boolean = boolean, ProjectId ext
4949
updateProjectPermissionDefinition(permissionId: string, data: AdminProjectPermissionDefinitionUpdateOptions): Promise<void>,
5050
deleteProjectPermissionDefinition(permissionId: string): Promise<void>,
5151

52-
useSvixToken(): string, // THIS_LINE_PLATFORM react-like
52+
useSvixToken(): { token: string, url: string | undefined }, // THIS_LINE_PLATFORM react-like
5353

5454
sendTestEmail(options: {
5555
recipientEmail: string,

0 commit comments

Comments
 (0)