From 1a07bd65d4a6594f69d1f0a719e71ef50414c720 Mon Sep 17 00:00:00 2001 From: Jeremy Ruppel Date: Mon, 29 Jun 2026 19:03:33 -0400 Subject: [PATCH] feat(site): wire template builder entrypoint from templates list page (#26756) The templates list page "New template" button and the empty-state "View all starter templates" button now link to `/templates/new/builder` when the template builder is enabled in the deployment config, falling back to `/starter-templates` when disabled. The `TemplateBuilderPage` now checks `createTemplates` permission and redirects to `/templates` if the user lacks access. The `templateBuilderBases` query is also gated on `enabled` to avoid a 404 when the feature is disabled.
Implementation details - `TemplatesPage` fetches `deploymentConfig()` (only when user has `createTemplates` permission) and computes `templateBuilderEnabled` using `=== false` to default safely while loading. - `templateBuilderEnabled` is threaded through `TemplatesPageView` and `EmptyTemplates` to conditionally set link targets. - `TemplateBuilderPage` uses `useAuthenticated()` to check `permissions.createTemplates` before rendering. - Added Storybook story variants for the builder-enabled state.
> Generated by Coder Agents on behalf of @jeremyruppel --- .../TemplateBuilder/TemplateBuilderPage.tsx | 17 +++++++++++++---- .../src/pages/TemplatesPage/EmptyTemplates.tsx | 10 +++++++++- site/src/pages/TemplatesPage/TemplatesPage.tsx | 11 +++++++++++ .../TemplatesPageView.stories.tsx | 18 ++++++++++++++++++ .../pages/TemplatesPage/TemplatesPageView.tsx | 11 ++++++++++- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/site/src/pages/TemplateBuilder/TemplateBuilderPage.tsx b/site/src/pages/TemplateBuilder/TemplateBuilderPage.tsx index 5561bbe9c5..ffda44a0bf 100644 --- a/site/src/pages/TemplateBuilder/TemplateBuilderPage.tsx +++ b/site/src/pages/TemplateBuilder/TemplateBuilderPage.tsx @@ -7,6 +7,7 @@ import { templateBuilderBases, } from "#/api/queries/templateBuilder"; import { Loader } from "#/components/Loader/Loader"; +import { useAuthenticated } from "#/hooks/useAuthenticated"; import { linkToTemplate, useLinks } from "#/modules/navigation"; import { pageTitle } from "#/utils/page"; import { TemplateBuilderPageView } from "./TemplateBuilderPageView"; @@ -16,17 +17,25 @@ import { toCreateTemplateRequest } from "./wizardState"; const TemplateBuilderPage: FC = () => { const navigate = useNavigate(); const getLink = useLinks(); + const { permissions } = useAuthenticated(); const { data, error, isLoading } = useQuery(deploymentConfig()); - const basesQuery = useQuery(templateBuilderBases()); const createMutation = useMutation(createTemplateFromBuilder()); + const builderDisabled = data?.config?.template_builder?.disabled ?? false; + + const basesQuery = useQuery({ + ...templateBuilderBases(), + enabled: !builderDisabled && !isLoading && permissions.createTemplates, + }); + if (isLoading) { return ; } - // If the template builder is disabled in the deployment config, - // redirect to the new template page. - const builderDisabled = data?.config?.template_builder?.disabled ?? false; + if (!permissions.createTemplates) { + return ; + } + if (builderDisabled) { return ; } diff --git a/site/src/pages/TemplatesPage/EmptyTemplates.tsx b/site/src/pages/TemplatesPage/EmptyTemplates.tsx index ab58d48ab7..a3d1848e20 100644 --- a/site/src/pages/TemplatesPage/EmptyTemplates.tsx +++ b/site/src/pages/TemplatesPage/EmptyTemplates.tsx @@ -35,12 +35,14 @@ const findFeaturedExamples = (examples: TemplateExample[]) => { interface EmptyTemplatesProps { canCreateTemplates: boolean; + templateBuilderEnabled: boolean; examples: TemplateExample[]; isUsingFilter: boolean; } export const EmptyTemplates: FC = ({ canCreateTemplates, + templateBuilderEnabled, examples, isUsingFilter, }) => { @@ -76,7 +78,13 @@ export const EmptyTemplates: FC = ({ ))} diff --git a/site/src/pages/TemplatesPage/TemplatesPage.tsx b/site/src/pages/TemplatesPage/TemplatesPage.tsx index d35c6e767b..20def48a3a 100644 --- a/site/src/pages/TemplatesPage/TemplatesPage.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPage.tsx @@ -1,6 +1,7 @@ import type { FC } from "react"; import { useQuery } from "react-query"; import { useSearchParams } from "react-router"; +import { deploymentConfig } from "#/api/queries/deployment"; import { workspacePermissionsByOrganization } from "#/api/queries/organizations"; import { templateExamples, templates } from "#/api/queries/templates"; import { type UseFilterResult, useFilter } from "#/components/Filter/Filter"; @@ -33,6 +34,15 @@ const TemplatesPage: FC = () => { ), ); + const deploymentConfigQuery = useQuery({ + ...deploymentConfig(), + enabled: permissions.createTemplates, + }); + const templateBuilderEnabled = + deploymentConfigQuery.isSuccess && + !deploymentConfigQuery.data?.config?.template_builder?.disabled && + permissions.createTemplates; + const error = templatesQuery.error || examplesQuery.error || @@ -46,6 +56,7 @@ const TemplatesPage: FC = () => { filterState={filterState} showOrganizations={showOrganizations} canCreateTemplates={permissions.createTemplates} + templateBuilderEnabled={templateBuilderEnabled} examples={examplesQuery.data} templates={templatesQuery.data} workspacePermissions={workspacePermissionsQuery.data} diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx index a2c1ee8201..79f4ae39bc 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx @@ -31,6 +31,7 @@ const meta: Meta = { component: TemplatesPageView, args: { filterState: defaultFilterProps, + templateBuilderEnabled: false, }, }; @@ -99,6 +100,13 @@ export const WithTemplates: Story = { }, }; +export const WithTemplatesBuilderEnabled: Story = { + args: { + ...WithTemplates.args, + templateBuilderEnabled: true, + }, +}; + export const MultipleOrganizations: Story = { args: { ...WithTemplates.args, @@ -159,6 +167,16 @@ export const EmptyCanCreate: Story = { }, }; +export const EmptyCanCreateWithBuilder: Story = { + args: { + canCreateTemplates: true, + templateBuilderEnabled: true, + error: undefined, + templates: [], + examples: [MockTemplateExample, MockTemplateExample2], + }, +}; + export const EmptyCannotCreate: Story = { args: { error: undefined, diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index ea3272eca3..8ba88d2706 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -196,6 +196,7 @@ interface TemplatesPageViewProps { filterState: TemplateFilterState; showOrganizations: boolean; canCreateTemplates: boolean; + templateBuilderEnabled: boolean; examples: TemplateExample[] | undefined; templates: Template[] | undefined; workspacePermissions: Record | undefined; @@ -206,6 +207,7 @@ export const TemplatesPageView: FC = ({ filterState, showOrganizations, canCreateTemplates, + templateBuilderEnabled, examples, templates, workspacePermissions, @@ -219,7 +221,13 @@ export const TemplatesPageView: FC = ({ actions={ canCreateTemplates && (