mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore(site): refactor starter templates to use react-query (#9697)
* Remove starter templates service * Remove starter template service * Remove template x service
This commit is contained in:
@@ -1,31 +1,30 @@
|
||||
import { useMachine } from "@xstate/react";
|
||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||
import { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { starterTemplateMachine } from "xServices/starterTemplates/starterTemplateXService";
|
||||
import { StarterTemplatePageView } from "./StarterTemplatePageView";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { templateExamples } from "api/queries/templates";
|
||||
|
||||
const StarterTemplatePage: FC = () => {
|
||||
const { exampleId } = useParams() as { exampleId: string };
|
||||
const organizationId = useOrganizationId();
|
||||
const [state] = useMachine(starterTemplateMachine, {
|
||||
context: {
|
||||
organizationId,
|
||||
exampleId,
|
||||
},
|
||||
});
|
||||
const templateExamplesQuery = useQuery(templateExamples(organizationId));
|
||||
const starterTemplate = templateExamplesQuery.data?.find(
|
||||
(example) => example.id === exampleId,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{pageTitle(state.context.starterTemplate?.name ?? exampleId)}
|
||||
</title>
|
||||
<title>{pageTitle(starterTemplate?.name ?? exampleId)}</title>
|
||||
</Helmet>
|
||||
|
||||
<StarterTemplatePageView context={state.context} />
|
||||
<StarterTemplatePageView
|
||||
starterTemplate={starterTemplate}
|
||||
error={templateExamplesQuery.error}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
mockApiError,
|
||||
MockOrganization,
|
||||
MockTemplateExample,
|
||||
} from "testHelpers/entities";
|
||||
import { mockApiError, MockTemplateExample } from "testHelpers/entities";
|
||||
import { StarterTemplatePageView } from "./StarterTemplatePageView";
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
@@ -17,23 +13,15 @@ type Story = StoryObj<typeof StarterTemplatePageView>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
context: {
|
||||
exampleId: MockTemplateExample.id,
|
||||
organizationId: MockOrganization.id,
|
||||
error: undefined,
|
||||
starterTemplate: MockTemplateExample,
|
||||
},
|
||||
error: undefined,
|
||||
starterTemplate: MockTemplateExample,
|
||||
},
|
||||
};
|
||||
export const Error: Story = {
|
||||
args: {
|
||||
context: {
|
||||
exampleId: MockTemplateExample.id,
|
||||
organizationId: MockOrganization.id,
|
||||
error: mockApiError({
|
||||
message: `Example ${MockTemplateExample.id} not found.`,
|
||||
}),
|
||||
starterTemplate: undefined,
|
||||
},
|
||||
error: mockApiError({
|
||||
message: `Example ${MockTemplateExample.id} not found.`,
|
||||
}),
|
||||
starterTemplate: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,27 +9,28 @@ import {
|
||||
PageHeaderTitle,
|
||||
} from "components/PageHeader/PageHeader";
|
||||
import { FC } from "react";
|
||||
import { StarterTemplateContext } from "xServices/starterTemplates/starterTemplateXService";
|
||||
import ViewCodeIcon from "@mui/icons-material/OpenInNewOutlined";
|
||||
import PlusIcon from "@mui/icons-material/AddOutlined";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { Link } from "react-router-dom";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { TemplateExample } from "api/typesGenerated";
|
||||
|
||||
export interface StarterTemplatePageViewProps {
|
||||
context: StarterTemplateContext;
|
||||
starterTemplate?: TemplateExample;
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export const StarterTemplatePageView: FC<StarterTemplatePageViewProps> = ({
|
||||
context,
|
||||
starterTemplate,
|
||||
error,
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
const { starterTemplate } = context;
|
||||
|
||||
if (context.error) {
|
||||
if (error) {
|
||||
return (
|
||||
<Margins>
|
||||
<ErrorAlert error={context.error} />
|
||||
<ErrorAlert error={error} />
|
||||
</Margins>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { useMachine } from "@xstate/react";
|
||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||
import { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { starterTemplatesMachine } from "xServices/starterTemplates/starterTemplatesXService";
|
||||
import { StarterTemplatesPageView } from "./StarterTemplatesPageView";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { templateExamples } from "api/queries/templates";
|
||||
import { getTemplatesByTag } from "utils/starterTemplates";
|
||||
|
||||
const StarterTemplatesPage: FC = () => {
|
||||
const organizationId = useOrganizationId();
|
||||
const [state] = useMachine(starterTemplatesMachine, {
|
||||
context: { organizationId },
|
||||
});
|
||||
const templateExamplesQuery = useQuery(templateExamples(organizationId));
|
||||
const starterTemplatesByTag = templateExamplesQuery.data
|
||||
? getTemplatesByTag(templateExamplesQuery.data)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -18,7 +20,10 @@ const StarterTemplatesPage: FC = () => {
|
||||
<title>{pageTitle("Starter Templates")}</title>
|
||||
</Helmet>
|
||||
|
||||
<StarterTemplatesPageView context={state.context} />
|
||||
<StarterTemplatesPageView
|
||||
error={templateExamplesQuery.error}
|
||||
starterTemplatesByTag={starterTemplatesByTag}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
mockApiError,
|
||||
MockOrganization,
|
||||
MockTemplateExample,
|
||||
MockTemplateExample2,
|
||||
} from "testHelpers/entities";
|
||||
@@ -18,25 +17,19 @@ type Story = StoryObj<typeof StarterTemplatesPageView>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
context: {
|
||||
organizationId: MockOrganization.id,
|
||||
error: undefined,
|
||||
starterTemplatesByTag: getTemplatesByTag([
|
||||
MockTemplateExample,
|
||||
MockTemplateExample2,
|
||||
]),
|
||||
},
|
||||
error: undefined,
|
||||
starterTemplatesByTag: getTemplatesByTag([
|
||||
MockTemplateExample,
|
||||
MockTemplateExample2,
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
export const Error: Story = {
|
||||
args: {
|
||||
context: {
|
||||
organizationId: MockOrganization.id,
|
||||
error: mockApiError({
|
||||
message: "Error on loading the template examples",
|
||||
}),
|
||||
starterTemplatesByTag: undefined,
|
||||
},
|
||||
error: mockApiError({
|
||||
message: "Error on loading the template examples",
|
||||
}),
|
||||
starterTemplatesByTag: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExam
|
||||
import { FC } from "react";
|
||||
import { Link, useSearchParams } from "react-router-dom";
|
||||
import { combineClasses } from "utils/combineClasses";
|
||||
import { StarterTemplatesContext } from "xServices/starterTemplates/starterTemplatesXService";
|
||||
import { StarterTemplatesByTag } from "utils/starterTemplates";
|
||||
|
||||
const getTagLabel = (tag: string) => {
|
||||
const labelByTag: Record<string, string> = {
|
||||
@@ -26,22 +26,25 @@ const getTagLabel = (tag: string) => {
|
||||
return labelByTag[tag] ?? tag;
|
||||
};
|
||||
|
||||
const selectTags = ({ starterTemplatesByTag }: StarterTemplatesContext) => {
|
||||
const selectTags = (starterTemplatesByTag: StarterTemplatesByTag) => {
|
||||
return starterTemplatesByTag
|
||||
? Object.keys(starterTemplatesByTag).sort((a, b) => a.localeCompare(b))
|
||||
: undefined;
|
||||
};
|
||||
export interface StarterTemplatesPageViewProps {
|
||||
context: StarterTemplatesContext;
|
||||
starterTemplatesByTag?: StarterTemplatesByTag;
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({
|
||||
context,
|
||||
starterTemplatesByTag,
|
||||
error,
|
||||
}) => {
|
||||
const [urlParams] = useSearchParams();
|
||||
const styles = useStyles();
|
||||
const { starterTemplatesByTag } = context;
|
||||
const tags = selectTags(context);
|
||||
const tags = starterTemplatesByTag
|
||||
? selectTags(starterTemplatesByTag)
|
||||
: undefined;
|
||||
const activeTag = urlParams.get("tag") ?? "all";
|
||||
const visibleTemplates = starterTemplatesByTag
|
||||
? starterTemplatesByTag[activeTag]
|
||||
@@ -56,8 +59,8 @@ export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({
|
||||
</PageHeaderSubtitle>
|
||||
</PageHeader>
|
||||
|
||||
<Maybe condition={Boolean(context.error)}>
|
||||
<ErrorAlert error={context.error} />
|
||||
<Maybe condition={Boolean(error)}>
|
||||
<ErrorAlert error={error} />
|
||||
</Maybe>
|
||||
|
||||
<Maybe condition={Boolean(!starterTemplatesByTag)}>
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { getTemplateExamples } from "api/api";
|
||||
import { TemplateExample } from "api/typesGenerated";
|
||||
import { assign, createMachine } from "xstate";
|
||||
|
||||
export interface StarterTemplateContext {
|
||||
organizationId: string;
|
||||
exampleId: string;
|
||||
starterTemplate?: TemplateExample;
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export const starterTemplateMachine = createMachine(
|
||||
{
|
||||
id: "starterTemplate",
|
||||
predictableActionArguments: true,
|
||||
schema: {
|
||||
context: {} as StarterTemplateContext,
|
||||
services: {} as {
|
||||
loadStarterTemplate: {
|
||||
data: TemplateExample;
|
||||
};
|
||||
},
|
||||
},
|
||||
tsTypes: {} as import("./starterTemplateXService.typegen").Typegen0,
|
||||
initial: "loading",
|
||||
states: {
|
||||
loading: {
|
||||
invoke: {
|
||||
src: "loadStarterTemplate",
|
||||
onDone: {
|
||||
actions: ["assignStarterTemplate"],
|
||||
target: "idle.ok",
|
||||
},
|
||||
onError: {
|
||||
actions: ["assignError"],
|
||||
target: "idle.error",
|
||||
},
|
||||
},
|
||||
},
|
||||
idle: {
|
||||
initial: "ok",
|
||||
states: {
|
||||
ok: { type: "final" },
|
||||
error: { type: "final" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
services: {
|
||||
loadStarterTemplate: async ({ organizationId, exampleId }) => {
|
||||
const examples = await getTemplateExamples(organizationId);
|
||||
const starterTemplate = examples.find(
|
||||
(example) => example.id === exampleId,
|
||||
);
|
||||
if (!starterTemplate) {
|
||||
throw new Error(`Example ${exampleId} not found.`);
|
||||
}
|
||||
return starterTemplate;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
assignError: assign({
|
||||
error: (_, { data }) => data,
|
||||
}),
|
||||
assignStarterTemplate: assign({
|
||||
starterTemplate: (_, { data }) => data,
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1,66 +0,0 @@
|
||||
import { getTemplateExamples } from "api/api";
|
||||
import { TemplateExample } from "api/typesGenerated";
|
||||
import {
|
||||
getTemplatesByTag,
|
||||
StarterTemplatesByTag,
|
||||
} from "utils/starterTemplates";
|
||||
import { assign, createMachine } from "xstate";
|
||||
|
||||
export interface StarterTemplatesContext {
|
||||
organizationId: string;
|
||||
starterTemplatesByTag?: StarterTemplatesByTag;
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export const starterTemplatesMachine = createMachine(
|
||||
{
|
||||
id: "starterTemplates",
|
||||
predictableActionArguments: true,
|
||||
schema: {
|
||||
context: {} as StarterTemplatesContext,
|
||||
services: {} as {
|
||||
loadStarterTemplates: {
|
||||
data: TemplateExample[];
|
||||
};
|
||||
},
|
||||
},
|
||||
tsTypes: {} as import("./starterTemplatesXService.typegen").Typegen0,
|
||||
initial: "loading",
|
||||
states: {
|
||||
loading: {
|
||||
invoke: {
|
||||
src: "loadStarterTemplates",
|
||||
onDone: {
|
||||
actions: ["assignStarterTemplates"],
|
||||
target: "idle.ok",
|
||||
},
|
||||
onError: {
|
||||
actions: ["assignError"],
|
||||
target: "idle.error",
|
||||
},
|
||||
},
|
||||
},
|
||||
idle: {
|
||||
initial: "ok",
|
||||
states: {
|
||||
ok: { type: "final" },
|
||||
error: { type: "final" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
services: {
|
||||
loadStarterTemplates: ({ organizationId }) =>
|
||||
getTemplateExamples(organizationId),
|
||||
},
|
||||
actions: {
|
||||
assignError: assign({
|
||||
error: (_, { data }) => data,
|
||||
}),
|
||||
assignStarterTemplates: assign({
|
||||
starterTemplatesByTag: (_, { data }) => getTemplatesByTag(data),
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user