diff --git a/site/src/api/queries/templates.ts b/site/src/api/queries/templates.ts new file mode 100644 index 0000000000..155129b5aa --- /dev/null +++ b/site/src/api/queries/templates.ts @@ -0,0 +1,17 @@ +import * as API from "api/api"; + +const getTemplatesQueryKey = (orgId: string) => [orgId, "templates"]; + +export const templates = (orgId: string) => { + return { + queryKey: getTemplatesQueryKey(orgId), + queryFn: () => API.getTemplates(orgId), + }; +}; + +export const templateExamples = (orgId: string) => { + return { + queryKey: [...getTemplatesQueryKey(orgId), "examples"], + queryFn: () => API.getTemplateExamples(orgId), + }; +}; diff --git a/site/src/pages/TemplatesPage/EmptyTemplates.tsx b/site/src/pages/TemplatesPage/EmptyTemplates.tsx index 3589aa332e..2b3c32b469 100644 --- a/site/src/pages/TemplatesPage/EmptyTemplates.tsx +++ b/site/src/pages/TemplatesPage/EmptyTemplates.tsx @@ -9,7 +9,6 @@ import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExam import { FC } from "react"; import { Link as RouterLink } from "react-router-dom"; import { docs } from "utils/docs"; -import { Permissions } from "xServices/auth/authXService"; // Those are from https://github.com/coder/coder/tree/main/examples/templates const featuredExampleIds = [ @@ -37,13 +36,13 @@ const findFeaturedExamples = (examples: TemplateExample[]) => { }; export const EmptyTemplates: FC<{ - permissions: Permissions; + canCreateTemplates: boolean; examples: TemplateExample[]; -}> = ({ permissions, examples }) => { +}> = ({ canCreateTemplates, examples }) => { const styles = useStyles(); const featuredExamples = findFeaturedExamples(examples); - if (permissions.createTemplates) { + if (canCreateTemplates) { return ( { const organizationId = useOrganizationId(); const permissions = usePermissions(); - const [templatesState] = useMachine(templatesMachine, { - context: { - organizationId, - permissions, - }, + const templatesQuery = useQuery(templates(organizationId)); + const examplesQuery = useQuery({ + ...templateExamples(organizationId), + enabled: permissions.createTemplates, }); + const error = templatesQuery.error || examplesQuery.error; return ( <> {pageTitle("Templates")} - + ); }; diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx index a54a43497b..36f305d4a2 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx @@ -1,7 +1,5 @@ import { mockApiError, - MockOrganization, - MockPermissions, MockTemplate, MockTemplateExample, MockTemplateExample2, @@ -19,32 +17,29 @@ type Story = StoryObj; export const WithTemplates: Story = { args: { - context: { - organizationId: MockOrganization.id, - permissions: MockPermissions, - error: undefined, - templates: [ - MockTemplate, - { - ...MockTemplate, - active_user_count: -1, - description: "🚀 Some new template that has no activity data", - icon: "/icon/goland.svg", - }, - { - ...MockTemplate, - active_user_count: 150, - description: "😮 Wow, this one has a bunch of usage!", - icon: "", - }, - { - ...MockTemplate, - description: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ", - }, - ], - examples: [], - }, + canCreateTemplates: true, + error: undefined, + templates: [ + MockTemplate, + { + ...MockTemplate, + active_user_count: -1, + description: "🚀 Some new template that has no activity data", + icon: "/icon/goland.svg", + }, + { + ...MockTemplate, + active_user_count: 150, + description: "😮 Wow, this one has a bunch of usage!", + icon: "", + }, + { + ...MockTemplate, + description: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ", + }, + ], + examples: [], }, }; @@ -59,44 +54,29 @@ export const WithTemplatesSmallViewPort: Story = { export const EmptyCanCreate: Story = { args: { - context: { - organizationId: MockOrganization.id, - permissions: MockPermissions, - error: undefined, - templates: [], - examples: [MockTemplateExample, MockTemplateExample2], - }, + canCreateTemplates: true, + error: undefined, + templates: [], + examples: [MockTemplateExample, MockTemplateExample2], }, }; export const EmptyCannotCreate: Story = { args: { - context: { - organizationId: MockOrganization.id, - permissions: { - ...MockPermissions, - createTemplates: false, - }, - error: undefined, - templates: [], - examples: [MockTemplateExample, MockTemplateExample2], - }, + error: undefined, + templates: [], + examples: [MockTemplateExample, MockTemplateExample2], + canCreateTemplates: false, }, }; export const Error: Story = { args: { - context: { - organizationId: MockOrganization.id, - permissions: { - ...MockPermissions, - createTemplates: false, - }, - error: mockApiError({ - message: "Something went wrong fetching templates.", - }), - templates: undefined, - examples: undefined, - }, + error: mockApiError({ + message: "Something went wrong fetching templates.", + }), + templates: undefined, + examples: undefined, + canCreateTemplates: false, }, }; diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index 9302a74cf9..a9d5c15726 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -16,18 +16,18 @@ import { formatTemplateBuildTime, formatTemplateActiveDevelopers, } from "utils/templates"; -import { AvatarData } from "../../components/AvatarData/AvatarData"; -import { Margins } from "../../components/Margins/Margins"; +import { AvatarData } from "components/AvatarData/AvatarData"; +import { Margins } from "components/Margins/Margins"; import { PageHeader, PageHeaderSubtitle, PageHeaderTitle, -} from "../../components/PageHeader/PageHeader"; -import { Stack } from "../../components/Stack/Stack"; +} from "components/PageHeader/PageHeader"; +import { Stack } from "components/Stack/Stack"; import { TableLoaderSkeleton, TableRowSkeleton, -} from "../../components/TableLoader/TableLoader"; +} from "components/TableLoader/TableLoader"; import { HelpTooltip, HelpTooltipLink, @@ -36,9 +36,8 @@ import { HelpTooltipTitle, } from "components/HelpTooltip/HelpTooltip"; import { EmptyTemplates } from "./EmptyTemplates"; -import { TemplatesContext } from "xServices/templates/templatesXService"; import { useClickableTableRow } from "hooks/useClickableTableRow"; -import { Template } from "api/typesGenerated"; +import { Template, TemplateExample } from "api/typesGenerated"; import { combineClasses } from "utils/combineClasses"; import { colors } from "theme/colors"; import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined"; @@ -141,13 +140,18 @@ const TemplateRow: FC<{ template: Template }> = ({ template }) => { }; export interface TemplatesPageViewProps { - context: TemplatesContext; + error?: unknown; + examples: TemplateExample[] | undefined; + templates: Template[] | undefined; + canCreateTemplates: boolean; } -export const TemplatesPageView: FC< - React.PropsWithChildren -> = ({ context }) => { - const { templates, error, examples, permissions } = context; +export const TemplatesPageView: FC = ({ + templates, + error, + examples, + canCreateTemplates, +}) => { const isLoading = !templates; const isEmpty = Boolean(templates && templates.length === 0); @@ -155,7 +159,7 @@ export const TemplatesPageView: FC< + @@ -208,7 +212,7 @@ export const TemplatesPageView: FC< diff --git a/site/src/xServices/templates/templatesXService.ts b/site/src/xServices/templates/templatesXService.ts deleted file mode 100644 index 2a57833fb6..0000000000 --- a/site/src/xServices/templates/templatesXService.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Permissions } from "xServices/auth/authXService"; -import { assign, createMachine } from "xstate"; -import * as API from "../../api/api"; -import * as TypesGen from "../../api/typesGenerated"; - -export interface TemplatesContext { - organizationId: string; - permissions: Permissions; - templates?: TypesGen.Template[]; - examples?: TypesGen.TemplateExample[]; - error?: unknown; -} - -export const templatesMachine = createMachine( - { - id: "templatesState", - predictableActionArguments: true, - tsTypes: {} as import("./templatesXService.typegen").Typegen0, - schema: { - context: {} as TemplatesContext, - services: {} as { - load: { - data: { - templates: TypesGen.Template[]; - examples: TypesGen.TemplateExample[]; - }; - }; - }, - }, - initial: "loading", - states: { - loading: { - invoke: { - src: "load", - id: "load", - onDone: { - actions: ["assignData"], - target: "idle", - }, - onError: { - actions: "assignError", - target: "idle", - }, - }, - }, - idle: { - type: "final", - }, - }, - }, - { - actions: { - assignData: assign({ - templates: (_, event) => event.data.templates, - examples: (_, event) => event.data.examples, - }), - assignError: assign({ - error: (_, { data }) => data, - }), - }, - services: { - load: async ({ organizationId, permissions }) => { - const [templates, examples] = await Promise.all([ - API.getTemplates(organizationId), - permissions.createTemplates - ? API.getTemplateExamples(organizationId) - : Promise.resolve([]), - ]); - - return { - templates, - examples, - }; - }, - }, - }, -);