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.

<details>
<summary>Implementation details</summary>

- `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.

</details>

> Generated by Coder Agents on behalf of @jeremyruppel
This commit is contained in:
Jeremy Ruppel
2026-06-29 19:03:33 -04:00
committed by GitHub
parent a073273a3f
commit 1a07bd65d4
5 changed files with 61 additions and 6 deletions
@@ -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 <Loader />;
}
// 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 <Navigate to="/templates" replace />;
}
if (builderDisabled) {
return <Navigate to="/templates/new" replace />;
}
@@ -35,12 +35,14 @@ const findFeaturedExamples = (examples: TemplateExample[]) => {
interface EmptyTemplatesProps {
canCreateTemplates: boolean;
templateBuilderEnabled: boolean;
examples: TemplateExample[];
isUsingFilter: boolean;
}
export const EmptyTemplates: FC<EmptyTemplatesProps> = ({
canCreateTemplates,
templateBuilderEnabled,
examples,
isUsingFilter,
}) => {
@@ -76,7 +78,13 @@ export const EmptyTemplates: FC<EmptyTemplatesProps> = ({
))}
</div>
<Button size="sm" asChild className="rounded-full">
<RouterLink to="/starter-templates">
<RouterLink
to={
templateBuilderEnabled
? "/templates/new/builder"
: "/starter-templates"
}
>
View all starter templates
</RouterLink>
</Button>
@@ -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}
@@ -31,6 +31,7 @@ const meta: Meta<typeof TemplatesPageView> = {
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,
@@ -196,6 +196,7 @@ interface TemplatesPageViewProps {
filterState: TemplateFilterState;
showOrganizations: boolean;
canCreateTemplates: boolean;
templateBuilderEnabled: boolean;
examples: TemplateExample[] | undefined;
templates: Template[] | undefined;
workspacePermissions: Record<string, WorkspacePermissions> | undefined;
@@ -206,6 +207,7 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = ({
filterState,
showOrganizations,
canCreateTemplates,
templateBuilderEnabled,
examples,
templates,
workspacePermissions,
@@ -219,7 +221,13 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = ({
actions={
canCreateTemplates && (
<Button asChild size="lg">
<RouterLink to="/starter-templates">
<RouterLink
to={
templateBuilderEnabled
? "/templates/new/builder"
: "/starter-templates"
}
>
<PlusIcon />
New template
</RouterLink>
@@ -266,6 +274,7 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = ({
{isEmpty ? (
<EmptyTemplates
canCreateTemplates={canCreateTemplates}
templateBuilderEnabled={templateBuilderEnabled}
examples={examples ?? []}
isUsingFilter={filterState.filter.used}
/>