mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
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
9f5dc52ecf. 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>
This commit is contained in:
parent
6763f1ac03
commit
e843a2b637
@ -2,15 +2,20 @@ import { getSvixClient } from "@/lib/webhooks";
|
||||
import { createCrudHandlers } from "@/route-handlers/crud-handler";
|
||||
import { svixTokenCrud } from "@stackframe/stack-shared/dist/interface/crud/svix-token";
|
||||
import { yupObject } from "@stackframe/stack-shared/dist/schema-fields";
|
||||
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies";
|
||||
|
||||
const svixServerUrl = getEnvVariable("STACK_SVIX_SERVER_URL", "");
|
||||
|
||||
const appPortalCrudHandlers = createLazyProxy(() => createCrudHandlers(svixTokenCrud, {
|
||||
paramsSchema: yupObject({}),
|
||||
onCreate: async ({ auth }) => {
|
||||
const svix = getSvixClient();
|
||||
await svix.application.getOrCreate({ uid: auth.project.id, name: auth.project.id });
|
||||
const result = await svix.authentication.appPortalAccess(auth.project.id, {});
|
||||
return { token: result.token };
|
||||
// svix embedded app portal is only available on hosted svix.
|
||||
const url = svixServerUrl ? undefined : result.url;
|
||||
return { token: result.token, url };
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@ -17,6 +17,56 @@ import { PageLayout } from "../page-layout";
|
||||
import { useAdminApp } from "../use-admin-app";
|
||||
import { getSvixResult } from "./utils";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { useTheme } from "next-themes";
|
||||
import { AppPortal } from "svix-react";
|
||||
import "svix-react/style.css";
|
||||
|
||||
|
||||
export default function PageClient() {
|
||||
const stackAdminApp = useAdminApp();
|
||||
const svixToken = stackAdminApp.useSvixToken();
|
||||
const { resolvedTheme } = useTheme();
|
||||
const [updateCounter, setUpdateCounter] = useState(0);
|
||||
const [testDialogEndpoint, setTestDialogEndpoint] = useState<Endpoint | null>(null);
|
||||
|
||||
return (
|
||||
<AppEnabledGuard appId="webhooks">
|
||||
<PageLayout
|
||||
title="Webhooks"
|
||||
description="Webhooks are used to sync users and teams events from Stack to your own server."
|
||||
>
|
||||
{svixToken.url ? (
|
||||
<div>
|
||||
<AppPortal url={svixToken.url} darkMode={resolvedTheme === "dark"} fullSize />
|
||||
</div>
|
||||
) : (
|
||||
<SvixProvider
|
||||
key={updateCounter}
|
||||
token={svixToken.token}
|
||||
appId={stackAdminApp.projectId}
|
||||
options={{ serverUrl: getPublicEnvVar('NEXT_PUBLIC_STACK_SVIX_SERVER_URL') }}
|
||||
>
|
||||
<Endpoints
|
||||
updateFn={() => setUpdateCounter(x => x + 1)}
|
||||
onTestRequested={(endpoint) => setTestDialogEndpoint(endpoint)}
|
||||
/>
|
||||
{testDialogEndpoint && (
|
||||
<TestEndpointDialog
|
||||
endpoint={testDialogEndpoint}
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setTestDialogEndpoint(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</SvixProvider>
|
||||
)}
|
||||
</PageLayout>
|
||||
</AppEnabledGuard>
|
||||
);
|
||||
}
|
||||
|
||||
type Endpoint = {
|
||||
id: string,
|
||||
@ -157,7 +207,7 @@ function CreateDialog(props: {
|
||||
);
|
||||
}
|
||||
|
||||
export function EndpointEditDialog(props: {
|
||||
function EndpointEditDialog(props: {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
endpoint: Endpoint,
|
||||
@ -369,42 +419,3 @@ function Endpoints(props: { updateFn: () => void, onTestRequested: (endpoint: En
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default function PageClient() {
|
||||
const stackAdminApp = useAdminApp();
|
||||
const svixToken = stackAdminApp.useSvixToken();
|
||||
const [updateCounter, setUpdateCounter] = useState(0);
|
||||
const [testDialogEndpoint, setTestDialogEndpoint] = useState<Endpoint | null>(null);
|
||||
|
||||
return (
|
||||
<AppEnabledGuard appId="webhooks">
|
||||
<PageLayout
|
||||
title="Webhooks"
|
||||
description="Webhooks are used to sync users and teams events from Stack to your own server."
|
||||
>
|
||||
<SvixProvider
|
||||
key={updateCounter}
|
||||
token={svixToken}
|
||||
appId={stackAdminApp.projectId}
|
||||
options={{ serverUrl: getPublicEnvVar('NEXT_PUBLIC_STACK_SVIX_SERVER_URL') }}
|
||||
>
|
||||
<Endpoints
|
||||
updateFn={() => setUpdateCounter(x => x + 1)}
|
||||
onTestRequested={(endpoint) => setTestDialogEndpoint(endpoint)}
|
||||
/>
|
||||
{testDialogEndpoint && (
|
||||
<TestEndpointDialog
|
||||
endpoint={testDialogEndpoint}
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setTestDialogEndpoint(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</SvixProvider>
|
||||
</PageLayout>
|
||||
</AppEnabledGuard>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { yupObject, yupString } from "../../schema-fields";
|
||||
|
||||
export const svixTokenAdminReadSchema = yupObject({
|
||||
token: yupString().defined(),
|
||||
url: yupString().optional(),
|
||||
}).defined();
|
||||
|
||||
export const svixTokenAdminCreateSchema = yupObject({}).defined();
|
||||
|
||||
@ -416,9 +416,9 @@ export class _StackAdminAppImplIncomplete<HasTokenStore extends boolean, Project
|
||||
}
|
||||
// END_PLATFORM
|
||||
// IF_PLATFORM react-like
|
||||
useSvixToken(): string {
|
||||
useSvixToken(): { token: string, url: string | undefined } {
|
||||
const crud = useAsyncCache(this._svixTokenCache, [], "adminApp.useSvixToken()");
|
||||
return crud.token;
|
||||
return { token: crud.token, url: crud.url };
|
||||
}
|
||||
// END_PLATFORM
|
||||
|
||||
|
||||
@ -49,7 +49,7 @@ export type StackAdminApp<HasTokenStore extends boolean = boolean, ProjectId ext
|
||||
updateProjectPermissionDefinition(permissionId: string, data: AdminProjectPermissionDefinitionUpdateOptions): Promise<void>,
|
||||
deleteProjectPermissionDefinition(permissionId: string): Promise<void>,
|
||||
|
||||
useSvixToken(): string, // THIS_LINE_PLATFORM react-like
|
||||
useSvixToken(): { token: string, url: string | undefined }, // THIS_LINE_PLATFORM react-like
|
||||
|
||||
sendTestEmail(options: {
|
||||
recipientEmail: string,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user