From 68b0e3db2ec5880cabb2efd36e6c0cbb6098fd9a Mon Sep 17 00:00:00 2001 From: Shreyag02 Date: Mon, 13 Jul 2026 22:39:25 +0530 Subject: [PATCH 1/3] feat(webhooks): make admin webhooks view-only via enable_actions flag --- pkg/server/config.go | 2 + pkg/server/server.go | 6 +- web/apps/admin/configs.dev.json | 3 +- .../admin/src/pages/webhooks/WebhooksPage.tsx | 5 +- web/apps/admin/src/utils/constants.ts | 3 + .../admin/views/webhooks/webhooks/columns.tsx | 125 +++++++++--------- .../admin/views/webhooks/webhooks/index.tsx | 46 ++++--- 7 files changed, 104 insertions(+), 86 deletions(-) diff --git a/pkg/server/config.go b/pkg/server/config.go index 4eb2dab3a..fa2c1a668 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -15,6 +15,8 @@ import ( type WebhooksConfig struct { EnableDelete bool `yaml:"enable_delete" mapstructure:"enable_delete" default:"false"` + // EnableActions gates webhook write actions (create, update, delete) in the admin UI. + EnableActions bool `yaml:"enable_actions" mapstructure:"enable_actions" default:"false"` } type EntityTerminology struct { diff --git a/pkg/server/server.go b/pkg/server/server.go index 315da19a5..180147516 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -42,7 +42,8 @@ const ( ) type WebhooksConfigApiResponse struct { - EnableDelete bool `json:"enable_delete"` + EnableDelete bool `json:"enable_delete"` + EnableActions bool `json:"enable_actions"` } type UIConfigApiResponse struct { @@ -93,7 +94,8 @@ func ServeUI(ctx context.Context, logger *slog.Logger, uiConfig UIConfig, apiSer TokenProductId: uiConfig.TokenProductId, OrganizationTypes: uiConfig.OrganizationTypes, Webhooks: WebhooksConfigApiResponse{ - EnableDelete: uiConfig.Webhooks.EnableDelete, + EnableDelete: uiConfig.Webhooks.EnableDelete, + EnableActions: uiConfig.Webhooks.EnableActions, }, Terminology: uiConfig.Terminology, } diff --git a/web/apps/admin/configs.dev.json b/web/apps/admin/configs.dev.json index d858368e7..b6d5aad00 100644 --- a/web/apps/admin/configs.dev.json +++ b/web/apps/admin/configs.dev.json @@ -4,7 +4,8 @@ "token_product_id": "token", "organization_types": [], "webhooks": { - "enable_delete": false + "enable_delete": false, + "enable_actions": false }, "terminology": { "organization": { diff --git a/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx b/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx index b919da73b..fb2f30b8c 100644 --- a/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx +++ b/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx @@ -10,7 +10,8 @@ export default function WebhooksPage() { const navigate = useNavigate(); const isCreate = useMatch("/webhooks/create"); - const enableDelete = config?.webhooks?.enable_delete ?? false; + // View-only unless `webhooks.enable_actions` is enabled in the deployment config. + const enableActions = config?.webhooks?.enable_actions ?? false; return ( navigate("/webhooks")} onSelectWebhook={(id: string) => navigate(`/webhooks/${encodeURIComponent(id)}`)} onOpenCreate={() => navigate("/webhooks/create")} - enableDelete={enableDelete} + enableActions={enableActions} icon={} /> ); diff --git a/web/apps/admin/src/utils/constants.ts b/web/apps/admin/src/utils/constants.ts index ded60ed1a..ddd246ad4 100644 --- a/web/apps/admin/src/utils/constants.ts +++ b/web/apps/admin/src/utils/constants.ts @@ -15,6 +15,8 @@ export const SUBSCRIPTION_STATUSES = [ export interface WebhooksConfig { enable_delete: boolean; + /** Enables webhook write actions (create, update, delete). Defaults to `false`. */ + enable_actions: boolean; } export interface EntityTerminologies { @@ -57,6 +59,7 @@ export const defaultConfig: Config = { organization_types: [], webhooks: { enable_delete: false, + enable_actions: false, }, terminology: defaultTerminology, }; diff --git a/web/sdk/admin/views/webhooks/webhooks/columns.tsx b/web/sdk/admin/views/webhooks/webhooks/columns.tsx index 46c45c0a8..3aae31082 100644 --- a/web/sdk/admin/views/webhooks/webhooks/columns.tsx +++ b/web/sdk/admin/views/webhooks/webhooks/columns.tsx @@ -20,12 +20,73 @@ import { DeleteWebhookDialog } from "./delete"; interface getColumnsOptions { openEditPage: (id: string) => void; deleteWebhookMutation: ReturnType; - enableDelete: boolean; + /** Renders the row Action menu (Update + Delete). `false` = view-only. */ + enableActions: boolean; } export const getColumns: ( opt: getColumnsOptions, -) => DataTableColumnDef[] = ({ openEditPage, deleteWebhookMutation, enableDelete }) => { +) => DataTableColumnDef[] = ({ openEditPage, deleteWebhookMutation, enableActions }) => { + const actionColumn: DataTableColumnDef = { + header: "Action", + accessorKey: "id", + classNames: { cell: styles.actionColumn, header: styles.actionColumn }, + cell: ({ getValue, row }) => { + const ActionCell = () => { + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const webhookId = getValue() as string; + const webhook = row.original; + + return ( + <> + {/* @ts-ignore */} + + } + /> + + + + openEditPage(webhookId)} + > + + Update + + + + setIsDeleteDialogOpen(true)} + > + + Delete + + + + + + + + + ); + }; + + return ; + }, + }; + return [ { header: "Description", @@ -61,64 +122,6 @@ export const getColumns: ( return {date}; }, }, - { - header: "Action", - accessorKey: "id", - classNames: { cell: styles.actionColumn, header: styles.actionColumn }, - cell: ({ getValue, row }) => { - const ActionCell = () => { - const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); - const webhookId = getValue() as string; - const webhook = row.original; - - return ( - <> - {/* @ts-ignore */} - - } - /> - - - - openEditPage(webhookId)} - > - - Update - - - - enableDelete && setIsDeleteDialogOpen(true)} - > - - Delete - - - - - - - - - ); - }; - - return ; - }, - }, + ...(enableActions ? [actionColumn] : []), ]; }; diff --git a/web/sdk/admin/views/webhooks/webhooks/index.tsx b/web/sdk/admin/views/webhooks/webhooks/index.tsx index ccb543543..ddf021b51 100644 --- a/web/sdk/admin/views/webhooks/webhooks/index.tsx +++ b/web/sdk/admin/views/webhooks/webhooks/index.tsx @@ -19,8 +19,8 @@ export type WebhooksViewProps = { onSelectWebhook?: (id: string) => void; /** Called when the "Create" button is clicked. Use to update the URL or open the create panel. */ onOpenCreate?: () => void; - /** When true, shows the delete option for webhooks. Defaults to `false`. */ - enableDelete?: boolean; + /** Shows write actions (New Webhook, Update, Delete). Defaults to `false` (view-only). */ + enableActions?: boolean; /** Icon rendered in the page header next to the title. */ icon?: ReactNode; }; @@ -31,7 +31,7 @@ export default function WebhooksView({ onCloseDetail, onSelectWebhook, onOpenCreate, - enableDelete = false, + enableActions = false, icon, }: WebhooksViewProps = {}) { const { @@ -70,7 +70,7 @@ export default function WebhooksView({ openEditPage(id); }, deleteWebhookMutation, - enableDelete, + enableActions, }); return ( @@ -90,28 +90,34 @@ export default function WebhooksView({ className={styles.header} > - + {enableActions && ( + + )} - - + {enableActions && ( + <> + + + + )} ); } From ec9649d7eba9ca43b368a5d9c563f7e5c4bd0bd2 Mon Sep 17 00:00:00 2001 From: Shreyag02 Date: Tue, 14 Jul 2026 15:43:28 +0530 Subject: [PATCH 2/3] refactor(webhooks): make admin webhooks view-only, drop enable_actions flag --- pkg/server/config.go | 7 - pkg/server/server.go | 24 +- web/apps/admin/configs.dev.json | 4 - .../admin/src/pages/webhooks/WebhooksPage.tsx | 23 +- web/apps/admin/src/routes.tsx | 5 +- web/apps/admin/src/utils/constants.ts | 11 - .../admin/views/webhooks/webhooks/columns.tsx | 83 +------ .../views/webhooks/webhooks/create/index.tsx | 192 --------------- .../views/webhooks/webhooks/delete/index.tsx | 85 ------- .../webhooks/hooks/useWebhookQueries.ts | 25 +- .../admin/views/webhooks/webhooks/index.tsx | 117 +++------- .../views/webhooks/webhooks/update/index.tsx | 220 ------------------ .../webhooks/webhooks/webhooks.module.css | 9 - 13 files changed, 39 insertions(+), 766 deletions(-) delete mode 100644 web/sdk/admin/views/webhooks/webhooks/create/index.tsx delete mode 100644 web/sdk/admin/views/webhooks/webhooks/delete/index.tsx delete mode 100644 web/sdk/admin/views/webhooks/webhooks/update/index.tsx diff --git a/pkg/server/config.go b/pkg/server/config.go index fa2c1a668..76603c1cd 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -13,12 +13,6 @@ import ( "github.com/raystack/frontier/core/authenticate" ) -type WebhooksConfig struct { - EnableDelete bool `yaml:"enable_delete" mapstructure:"enable_delete" default:"false"` - // EnableActions gates webhook write actions (create, update, delete) in the admin UI. - EnableActions bool `yaml:"enable_actions" mapstructure:"enable_actions" default:"false"` -} - type EntityTerminology struct { Singular string `yaml:"singular" mapstructure:"singular" json:"singular"` Plural string `yaml:"plural" mapstructure:"plural" json:"plural"` @@ -40,7 +34,6 @@ type UIConfig struct { AppURL string `yaml:"app_url" mapstructure:"app_url"` TokenProductId string `yaml:"token_product_id" mapstructure:"token_product_id"` OrganizationTypes []string `yaml:"organization_types" mapstructure:"organization_types"` - Webhooks WebhooksConfig `yaml:"webhooks" mapstructure:"webhooks"` Terminology TerminologyConfig `yaml:"terminology" mapstructure:"terminology"` } diff --git a/pkg/server/server.go b/pkg/server/server.go index 180147516..2b3f80ba2 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -41,19 +41,13 @@ const ( connectServerGracePeriod = 10 * time.Second ) -type WebhooksConfigApiResponse struct { - EnableDelete bool `json:"enable_delete"` - EnableActions bool `json:"enable_actions"` -} - type UIConfigApiResponse struct { - Title string `json:"title"` - Logo string `json:"logo"` - AppUrl string `json:"app_url"` - TokenProductId string `json:"token_product_id"` - OrganizationTypes []string `json:"organization_types"` - Webhooks WebhooksConfigApiResponse `json:"webhooks"` - Terminology TerminologyConfig `json:"terminology"` + Title string `json:"title"` + Logo string `json:"logo"` + AppUrl string `json:"app_url"` + TokenProductId string `json:"token_product_id"` + OrganizationTypes []string `json:"organization_types"` + Terminology TerminologyConfig `json:"terminology"` } func ServeUI(ctx context.Context, logger *slog.Logger, uiConfig UIConfig, apiServerConfig Config) { @@ -93,11 +87,7 @@ func ServeUI(ctx context.Context, logger *slog.Logger, uiConfig UIConfig, apiSer AppUrl: uiConfig.AppURL, TokenProductId: uiConfig.TokenProductId, OrganizationTypes: uiConfig.OrganizationTypes, - Webhooks: WebhooksConfigApiResponse{ - EnableDelete: uiConfig.Webhooks.EnableDelete, - EnableActions: uiConfig.Webhooks.EnableActions, - }, - Terminology: uiConfig.Terminology, + Terminology: uiConfig.Terminology, } json.NewEncoder(w).Encode(confResp) }) diff --git a/web/apps/admin/configs.dev.json b/web/apps/admin/configs.dev.json index b6d5aad00..a5678293b 100644 --- a/web/apps/admin/configs.dev.json +++ b/web/apps/admin/configs.dev.json @@ -3,10 +3,6 @@ "app_url": "localhost:5173", "token_product_id": "token", "organization_types": [], - "webhooks": { - "enable_delete": false, - "enable_actions": false - }, "terminology": { "organization": { "singular": "Organization", diff --git a/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx b/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx index fb2f30b8c..916859a84 100644 --- a/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx +++ b/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx @@ -1,27 +1,6 @@ -import { useContext } from "react"; -import { useMatch, useParams, useNavigate } from "react-router-dom"; import { WebhooksView } from "@raystack/frontier/admin"; -import { AppContext } from "~/contexts/App"; import WebhooksIcon from "~/assets/icons/webhooks.svg?react"; export default function WebhooksPage() { - const { config } = useContext(AppContext); - const { webhookId } = useParams(); - const navigate = useNavigate(); - const isCreate = useMatch("/webhooks/create"); - - // View-only unless `webhooks.enable_actions` is enabled in the deployment config. - const enableActions = config?.webhooks?.enable_actions ?? false; - - return ( - navigate("/webhooks")} - onSelectWebhook={(id: string) => navigate(`/webhooks/${encodeURIComponent(id)}`)} - onOpenCreate={() => navigate("/webhooks/create")} - enableActions={enableActions} - icon={} - /> - ); + return } />; } diff --git a/web/apps/admin/src/routes.tsx b/web/apps/admin/src/routes.tsx index fd90b17d8..3a76ebb70 100644 --- a/web/apps/admin/src/routes.tsx +++ b/web/apps/admin/src/routes.tsx @@ -108,10 +108,7 @@ export default memo(function AppRoutes() { } /> } /> - }> - } /> - } /> - + } /> } /> diff --git a/web/apps/admin/src/utils/constants.ts b/web/apps/admin/src/utils/constants.ts index ddd246ad4..cc78e5859 100644 --- a/web/apps/admin/src/utils/constants.ts +++ b/web/apps/admin/src/utils/constants.ts @@ -13,12 +13,6 @@ export const SUBSCRIPTION_STATUSES = [ { label: "Ended", value: "ended" }, ]; -export interface WebhooksConfig { - enable_delete: boolean; - /** Enables webhook write actions (create, update, delete). Defaults to `false`. */ - enable_actions: boolean; -} - export interface EntityTerminologies { singular: string; plural: string; @@ -39,7 +33,6 @@ export interface Config { app_url?: string; token_product_id?: string; organization_types?: string[]; - webhooks?: WebhooksConfig; terminology?: AdminTerminologyConfig; } @@ -57,10 +50,6 @@ export const defaultConfig: Config = { app_url: "example.com", token_product_id: DEFAULT_TOKEN_PRODUCT_NAME, organization_types: [], - webhooks: { - enable_delete: false, - enable_actions: false, - }, terminology: defaultTerminology, }; diff --git a/web/sdk/admin/views/webhooks/webhooks/columns.tsx b/web/sdk/admin/views/webhooks/webhooks/columns.tsx index 3aae31082..38339ce2a 100644 --- a/web/sdk/admin/views/webhooks/webhooks/columns.tsx +++ b/web/sdk/admin/views/webhooks/webhooks/columns.tsx @@ -1,10 +1,4 @@ -import { DotsVerticalIcon, TrashIcon, UpdateIcon } from "@radix-ui/react-icons"; -import { - Menu, - Flex, - Text, - type DataTableColumnDef, -} from "@raystack/apsara"; +import { Text, type DataTableColumnDef } from "@raystack/apsara"; import styles from "./webhooks.module.css"; import { type Webhook } from "@raystack/proton/frontier"; import { @@ -13,80 +7,8 @@ import { type TimeStamp, } from "../../../utils/connect-timestamp"; import dayjs from "dayjs"; -import { useState } from "react"; -import type { useMutation } from "@connectrpc/connect-query"; -import { DeleteWebhookDialog } from "./delete"; - -interface getColumnsOptions { - openEditPage: (id: string) => void; - deleteWebhookMutation: ReturnType; - /** Renders the row Action menu (Update + Delete). `false` = view-only. */ - enableActions: boolean; -} - -export const getColumns: ( - opt: getColumnsOptions, -) => DataTableColumnDef[] = ({ openEditPage, deleteWebhookMutation, enableActions }) => { - const actionColumn: DataTableColumnDef = { - header: "Action", - accessorKey: "id", - classNames: { cell: styles.actionColumn, header: styles.actionColumn }, - cell: ({ getValue, row }) => { - const ActionCell = () => { - const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); - const webhookId = getValue() as string; - const webhook = row.original; - - return ( - <> - {/* @ts-ignore */} - - } - /> - - - - openEditPage(webhookId)} - > - - Update - - - - setIsDeleteDialogOpen(true)} - > - - Delete - - - - - - - - - ); - }; - - return ; - }, - }; +export const getColumns: () => DataTableColumnDef[] = () => { return [ { header: "Description", @@ -122,6 +44,5 @@ export const getColumns: ( return {date}; }, }, - ...(enableActions ? [actionColumn] : []), ]; }; diff --git a/web/sdk/admin/views/webhooks/webhooks/create/index.tsx b/web/sdk/admin/views/webhooks/webhooks/create/index.tsx deleted file mode 100644 index f6dbfffce..000000000 --- a/web/sdk/admin/views/webhooks/webhooks/create/index.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { useCallback } from "react"; -import { Button, Flex, Drawer, toastManager } from "@raystack/apsara"; -import { SheetHeader } from "../../../../components/SheetHeader"; -import { SheetFooter } from "../../../../components/SheetFooter"; -import * as z from "zod"; -import { FormProvider, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Form, FormSubmit } from "@radix-ui/react-form"; -import { CustomFieldName } from "../../../../components/CustomField"; -import events from "../../../../utils/webhook-events"; -import { useMutation } from "@connectrpc/connect-query"; -import { handleConnectError } from "~/utils/error"; -import { - AdminServiceQueries, - type WebhookRequestBody, - WebhookRequestBodySchema, -} from "@raystack/proton/frontier"; -import { create } from "@bufbuild/protobuf"; -import { useWebhookQueries } from "../hooks/useWebhookQueries"; - -const NewWebookSchema = z.object({ - url: z.string().trim().url(), - description: z - .string() - .trim() - .min(3, { message: "Must be 3 or more characters long" }), - state: z.boolean().default(false), - subscribed_events: z.array(z.string()).default([]), -}); - -export type NewWebhook = z.infer; - -export type CreateWebhooksProps = { - open?: boolean; - onClose?: () => void; -}; - -export default function CreateWebhooks({ open = false, onClose: onCloseProp }: CreateWebhooksProps = {}) { - const { invalidateWebhooksList } = useWebhookQueries(); - - const onOpenChange = useCallback(() => { - onCloseProp?.(); - }, [onCloseProp]); - - const { mutateAsync: createWebhook, isPending: isSubmitting } = useMutation( - AdminServiceQueries.createWebhook, - ); - - const methods = useForm({ - resolver: zodResolver(NewWebookSchema), - defaultValues: { - url: "", - description: "", - state: false, - subscribed_events: [], - }, - }); - - const onSubmit = async (data: NewWebhook) => { - try { - const body: WebhookRequestBody = create(WebhookRequestBodySchema, { - url: data.url, - description: data.description, - state: data.state ? "enabled" : "disabled", - subscribedEvents: data.subscribed_events || [], - headers: {}, - }); - - const resp = await createWebhook({ body }); - - if (resp?.webhook) { - toastManager.add({ title: "Webhook created", type: "success" }); - await invalidateWebhooksList(); - methods.reset(); - onOpenChange(); - } else { - toastManager.add({ - title: "Something went wrong", - description: "Webhook was not created. Please try again.", - type: "error", - }); - } - } catch (err) { - console.error("Failed to create webhook:", err); - handleConnectError(err, { - PermissionDenied: () => - toastManager.add({ - title: "You don't have permission to perform this action", - type: "error", - }), - Default: (e) => - toastManager.add({ - title: "Something went wrong", - description: e.rawMessage, - type: "error", - }), - }); - } - }; - - return ( - !open && onCloseProp?.()}> - - -
- - - - - ({ label: e, value: e }))} - /> - - - - - - - - -
-
-
- ); -} - -const styles = { - main: { - padding: "32px", - margin: 0, - height: "calc(100vh - 125px)", - overflow: "auto", - }, - formfield: { - width: "80%", - marginBottom: "40px", - }, - select: { - height: "32px", - borderRadius: "8px", - padding: "8px", - border: "none", - backgroundColor: "transparent", - "&:active,&:focus": { - border: "none", - outline: "none", - boxShadow: "none", - }, - }, -}; diff --git a/web/sdk/admin/views/webhooks/webhooks/delete/index.tsx b/web/sdk/admin/views/webhooks/webhooks/delete/index.tsx deleted file mode 100644 index 6bcf5dd24..000000000 --- a/web/sdk/admin/views/webhooks/webhooks/delete/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Button, Dialog, Flex, Text, toastManager } from "@raystack/apsara"; -import type { useMutation } from "@connectrpc/connect-query"; -import { handleConnectError } from "~/utils/error"; - -interface DeleteWebhookDialogProps { - isOpen: boolean; - onOpenChange: (open: boolean) => void; - webhookId: string; - webhookDescription?: string; - deleteWebhookMutation: ReturnType; -} - -export function DeleteWebhookDialog({ - isOpen, - onOpenChange, - webhookId, - webhookDescription, - deleteWebhookMutation, -}: DeleteWebhookDialogProps) { - const handleDelete = async () => { - try { - await deleteWebhookMutation.mutateAsync({ id: webhookId }); - toastManager.add({ title: "Webhook deleted", type: "success" }); - onOpenChange(false); - } catch (err) { - console.error("Failed to delete webhook:", err); - handleConnectError(err, { - PermissionDenied: () => - toastManager.add({ - title: "You don't have permission to perform this action", - type: "error", - }), - Default: (e) => - toastManager.add({ - title: "Failed to delete webhook", - description: e.rawMessage, - type: "error", - }), - }); - } - }; - - return ( - - - - - - Delete Webhook - - - Are you sure you want to delete this webhook - {webhookDescription ? ` "${webhookDescription}"` : ""}? This action - cannot be undone. - - - - - - - - - - - ); -} diff --git a/web/sdk/admin/views/webhooks/webhooks/hooks/useWebhookQueries.ts b/web/sdk/admin/views/webhooks/webhooks/hooks/useWebhookQueries.ts index 488330535..6c94205b1 100644 --- a/web/sdk/admin/views/webhooks/webhooks/hooks/useWebhookQueries.ts +++ b/web/sdk/admin/views/webhooks/webhooks/hooks/useWebhookQueries.ts @@ -1,11 +1,7 @@ -import { createConnectQueryKey, useTransport, useQuery, useMutation } from "@connectrpc/connect-query"; +import { useQuery } from "@connectrpc/connect-query"; import { AdminServiceQueries } from "@raystack/proton/frontier"; -import { useQueryClient } from "@tanstack/react-query"; export function useWebhookQueries() { - const queryClient = useQueryClient(); - const transport = useTransport(); - const listWebhooks = useQuery( AdminServiceQueries.listWebhooks, {}, @@ -15,26 +11,7 @@ export function useWebhookQueries() { }, ); - const invalidateWebhooksList = async () => { - await queryClient.invalidateQueries({ - queryKey: createConnectQueryKey({ - schema: AdminServiceQueries.listWebhooks, - transport, - input: {}, - cardinality: "finite", - }), - }); - }; - - const deleteWebhookMutation = useMutation(AdminServiceQueries.deleteWebhook, { - onSuccess: () => { - invalidateWebhooksList(); - }, - }); - return { listWebhooks, - invalidateWebhooksList, - deleteWebhookMutation, }; } diff --git a/web/sdk/admin/views/webhooks/webhooks/index.tsx b/web/sdk/admin/views/webhooks/webhooks/index.tsx index ddf021b51..230b350e7 100644 --- a/web/sdk/admin/views/webhooks/webhooks/index.tsx +++ b/web/sdk/admin/views/webhooks/webhooks/index.tsx @@ -1,56 +1,23 @@ -import { Button, Flex, DataTable, EmptyState } from "@raystack/apsara"; -import { useCallback, type ReactNode } from "react"; +import { Flex, DataTable, EmptyState } from "@raystack/apsara"; +import { type ReactNode } from "react"; import { getColumns } from "./columns"; import styles from "./webhooks.module.css"; import { useWebhookQueries } from "./hooks/useWebhookQueries"; -import { ExclamationTriangleIcon, PlusIcon } from "@radix-ui/react-icons"; +import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; import { PageHeader } from "../../../components/PageHeader"; -import CreateWebhooks from "./create"; -import UpdateWebhooks from "./update"; export type WebhooksViewProps = { - /** When set, opens the update panel for this webhook. */ - selectedWebhookId?: string; - /** When true, opens the create webhook panel. */ - createOpen?: boolean; - /** Called when the detail/create panel is closed. */ - onCloseDetail?: () => void; - /** Called when a user clicks a webhook row. Use to update the URL or local state. */ - onSelectWebhook?: (id: string) => void; - /** Called when the "Create" button is clicked. Use to update the URL or open the create panel. */ - onOpenCreate?: () => void; - /** Shows write actions (New Webhook, Update, Delete). Defaults to `false` (view-only). */ - enableActions?: boolean; /** Icon rendered in the page header next to the title. */ icon?: ReactNode; }; -export default function WebhooksView({ - selectedWebhookId, - createOpen, - onCloseDetail, - onSelectWebhook, - onOpenCreate, - enableActions = false, - icon, -}: WebhooksViewProps = {}) { +export default function WebhooksView({ icon }: WebhooksViewProps = {}) { const { - listWebhooks: { - data: webhooksResponse, - isLoading, - error, - isError, - }, - deleteWebhookMutation, + listWebhooks: { data: webhooksResponse, isLoading, error, isError }, } = useWebhookQueries(); const webhooks = webhooksResponse?.webhooks || []; - const openEditPage = useCallback( - (id: string) => (onSelectWebhook ? onSelectWebhook(id) : undefined), - [onSelectWebhook] - ); - if (isError) { console.error("ConnectRPC Error:", error); return ( @@ -65,59 +32,29 @@ export default function WebhooksView({ ); } - const columns = getColumns({ - openEditPage: (id) => { - openEditPage(id); - }, - deleteWebhookMutation, - enableActions, - }); + const columns = getColumns(); return ( - <> - - - - - {enableActions && ( - - )} - - - - - {enableActions && ( - <> - - - - )} - + + + + + + + + ); } diff --git a/web/sdk/admin/views/webhooks/webhooks/update/index.tsx b/web/sdk/admin/views/webhooks/webhooks/update/index.tsx deleted file mode 100644 index 3da2b4692..000000000 --- a/web/sdk/admin/views/webhooks/webhooks/update/index.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import { useCallback, useEffect } from "react"; -import { Button, Flex, Drawer, toastManager } from "@raystack/apsara"; -import { SheetHeader } from "../../../../components/SheetHeader"; -import { SheetFooter } from "../../../../components/SheetFooter"; -import * as z from "zod"; -import { FormProvider, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Form, FormSubmit } from "@radix-ui/react-form"; -import { CustomFieldName } from "../../../../components/CustomField"; -import events from "../../../../utils/webhook-events"; -import { useMutation } from "@connectrpc/connect-query"; -import { handleConnectError } from "~/utils/error"; -import { - AdminServiceQueries, - type WebhookRequestBody, - WebhookRequestBodySchema, -} from "@raystack/proton/frontier"; -import { create } from "@bufbuild/protobuf"; -import { useWebhookQueries } from "../hooks/useWebhookQueries"; - -const UpdateWebhookSchema = z.object({ - url: z.string().trim().url(), - description: z - .string() - .trim() - .min(3, { message: "Must be 3 or more characters long" }), - state: z.boolean().default(false), - subscribed_events: z.array(z.string()).default([]), -}); - -export type UpdateWebhook = z.infer; - -export type UpdateWebhooksProps = { - open?: boolean; - webhookId?: string; - onClose?: () => void; -}; - -export default function UpdateWebhooks({ open = false, webhookId: webhookIdProp, onClose: onCloseProp }: UpdateWebhooksProps = {}) { - const webhookId = webhookIdProp ?? ""; - - const { - listWebhooks: { - data: webhooksResponse, - isLoading: isWebhookLoading, - }, - invalidateWebhooksList, - } = useWebhookQueries(); - - const onClose = useCallback(() => { - onCloseProp?.(); - }, [onCloseProp]); - - const methods = useForm({ - resolver: zodResolver(UpdateWebhookSchema), - defaultValues: {}, - }); - - const webhook = webhooksResponse?.webhooks?.find( - (wb) => wb?.id === webhookId, - ); - - const { mutateAsync: updateWebhook, isPending: isSubmitting } = useMutation( - AdminServiceQueries.updateWebhook, - ); - - const onSubmit = async (data: UpdateWebhook) => { - try { - const body: WebhookRequestBody = create(WebhookRequestBodySchema, { - url: data.url, - description: data.description, - state: data.state ? "enabled" : "disabled", - subscribedEvents: data.subscribed_events || [], - headers: {}, - }); - - const resp = await updateWebhook({ - id: webhookId, - body, - }); - - if (resp?.webhook) { - toastManager.add({ title: "Webhook updated", type: "success" }); - await invalidateWebhooksList(); - onClose(); - } - } catch (err) { - console.error("Failed to update webhook:", err); - handleConnectError(err, { - PermissionDenied: () => - toastManager.add({ - title: "You don't have permission to perform this action", - type: "error", - }), - Default: (e) => - toastManager.add({ - title: "Something went wrong", - description: e.rawMessage, - type: "error", - }), - }); - } - }; - - useEffect(() => { - if (webhook) { - methods.reset({ - url: webhook.url, - description: webhook.description, - subscribed_events: webhook.subscribedEvents || [], - state: webhook.state === "enabled", - }); - } - }, [webhook, methods.reset]); - - return ( - !open && onClose()}> - - -
- - - - - ({ label: e, value: e }))} - isLoading={isWebhookLoading} - /> - - - - - - - - -
-
-
- ); -} - -const styles = { - main: { - padding: "32px", - margin: 0, - height: "calc(100vh - 125px)", - overflow: "auto", - }, - formfield: { - width: "80%", - marginBottom: "40px", - }, - select: { - height: "32px", - borderRadius: "8px", - padding: "8px", - border: "none", - backgroundColor: "transparent", - "&:active,&:focus": { - border: "none", - outline: "none", - boxShadow: "none", - }, - }, -}; diff --git a/web/sdk/admin/views/webhooks/webhooks/webhooks.module.css b/web/sdk/admin/views/webhooks/webhooks/webhooks.module.css index 1f1a4f6b2..ffa3a1d04 100644 --- a/web/sdk/admin/views/webhooks/webhooks/webhooks.module.css +++ b/web/sdk/admin/views/webhooks/webhooks/webhooks.module.css @@ -14,10 +14,6 @@ padding-left: var(--rs-space-7); } -.actionColumn { - width: 80px; -} - .stateColumn { width: 100px; } @@ -31,9 +27,4 @@ border-bottom: 1px solid var(--rs-color-border-base-primary); display: flex; align-items: center; -} - -.deleteMenuItem { - padding: var(--rs-space-4); - color: var(--rs-color-foreground-danger-primary); } \ No newline at end of file From 7ad599fd5d11ab8a8b9221f9f05ce867ce8814e5 Mon Sep 17 00:00:00 2001 From: Shreyag02 Date: Wed, 15 Jul 2026 13:36:49 +0530 Subject: [PATCH 3/3] revert(webhooks): keep server Go config unchanged from main Reverts the Go-side removal of WebhooksConfig/WebhooksConfigApiResponse so pkg/server matches main; view-only scope is handled in the web layer. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/server/config.go | 5 +++++ pkg/server/server.go | 24 ++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/pkg/server/config.go b/pkg/server/config.go index e9eba6056..c8e6124b2 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -15,6 +15,10 @@ import ( "github.com/raystack/frontier/core/authenticate" ) +type WebhooksConfig struct { + EnableDelete bool `yaml:"enable_delete" mapstructure:"enable_delete" default:"false"` +} + type EntityTerminology struct { Singular string `yaml:"singular" mapstructure:"singular" json:"singular"` Plural string `yaml:"plural" mapstructure:"plural" json:"plural"` @@ -36,6 +40,7 @@ type UIConfig struct { AppURL string `yaml:"app_url" mapstructure:"app_url"` TokenProductId string `yaml:"token_product_id" mapstructure:"token_product_id"` OrganizationTypes []string `yaml:"organization_types" mapstructure:"organization_types"` + Webhooks WebhooksConfig `yaml:"webhooks" mapstructure:"webhooks"` Terminology TerminologyConfig `yaml:"terminology" mapstructure:"terminology"` } diff --git a/pkg/server/server.go b/pkg/server/server.go index ce7fa51f8..9ea490fb0 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -37,17 +37,18 @@ import ( frontierv1beta1connect "github.com/raystack/frontier/proto/v1beta1/frontierv1beta1connect" ) -const ( - connectServerGracePeriod = 10 * time.Second -) +type WebhooksConfigApiResponse struct { + EnableDelete bool `json:"enable_delete"` +} type UIConfigApiResponse struct { - Title string `json:"title"` - Logo string `json:"logo"` - AppUrl string `json:"app_url"` - TokenProductId string `json:"token_product_id"` - OrganizationTypes []string `json:"organization_types"` - Terminology TerminologyConfig `json:"terminology"` + Title string `json:"title"` + Logo string `json:"logo"` + AppUrl string `json:"app_url"` + TokenProductId string `json:"token_product_id"` + OrganizationTypes []string `json:"organization_types"` + Webhooks WebhooksConfigApiResponse `json:"webhooks"` + Terminology TerminologyConfig `json:"terminology"` } func ServeUI(ctx context.Context, logger *slog.Logger, uiConfig UIConfig, apiServerConfig Config) { @@ -89,7 +90,10 @@ func ServeUI(ctx context.Context, logger *slog.Logger, uiConfig UIConfig, apiSer AppUrl: uiConfig.AppURL, TokenProductId: uiConfig.TokenProductId, OrganizationTypes: uiConfig.OrganizationTypes, - Terminology: uiConfig.Terminology, + Webhooks: WebhooksConfigApiResponse{ + EnableDelete: uiConfig.Webhooks.EnableDelete, + }, + Terminology: uiConfig.Terminology, } json.NewEncoder(w).Encode(confResp) })