Skip to content

refactor(site): improve templates empty state #10518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 6, 2023
189 changes: 113 additions & 76 deletions site/src/components/TemplateExampleCard/TemplateExampleCard.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,123 @@
import { type Interpolation, type Theme } from "@emotion/react";
import Button from "@mui/material/Button";
import Link from "@mui/material/Link";
import type { TemplateExample } from "api/typesGenerated";
import { type FC } from "react";
import { Link } from "react-router-dom";
import { Pill } from "components/Pill/Pill";
import { HTMLProps } from "react";
import { Link as RouterLink } from "react-router-dom";

export interface TemplateExampleCardProps {
type TemplateExampleCardProps = {
example: TemplateExample;
className?: string;
}
activeTag?: string;
} & HTMLProps<HTMLDivElement>;

export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
example,
className,
}) => {
export const TemplateExampleCard = (props: TemplateExampleCardProps) => {
const { example, activeTag, ...divProps } = props;
return (
<Link
to={`/starter-templates/${example.id}`}
css={styles.template}
className={className}
key={example.id}
<div
css={(theme) => ({
width: "320px",
padding: 24,
borderRadius: 6,
border: `1px solid ${theme.palette.divider}`,
textAlign: "left",
textDecoration: "none",
color: "inherit",
display: "flex",
flexDirection: "column",
})}
{...divProps}
>
<div css={styles.templateIcon}>
<img src={example.icon} alt="" />
</div>
<div css={styles.templateInfo}>
<span css={styles.templateName}>{example.name}</span>
<span css={styles.templateDescription}>{example.description}</span>
</div>
</Link>
);
};

const styles = {
template: (theme) => ({
border: `1px solid ${theme.palette.divider}`,
borderRadius: 8,
background: theme.palette.background.paper,
textDecoration: "none",
textAlign: "left",
color: "inherit",
display: "flex",
alignItems: "center",
height: "fit-content",

"&:hover": {
backgroundColor: theme.palette.background.paperLight,
},
}),
<div
css={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 24,
}}
>
<div
css={{
flexShrink: 0,
paddingTop: 4,
width: 32,
height: 32,
}}
>
<img
src={example.icon}
alt=""
css={{ width: "100%", height: "100%", objectFit: "contain" }}
/>
</div>

templateIcon: {
width: 96,
height: 96,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
<div css={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
{example.tags.map((tag) => {
const isActive = activeTag === tag;

"& img": {
height: 32,
},
},

templateInfo: {
padding: "16px 16px 16px 0",
display: "flex",
flexDirection: "column",
overflow: "hidden",
},
return (
<RouterLink key={tag} to={`/starter-templates?tag=${tag}`}>
<Pill
text={tag}
css={(theme) => ({
borderColor: isActive
? theme.palette.primary.main
: theme.palette.divider,
cursor: "pointer",
backgroundColor: isActive
? theme.palette.primary.dark
: undefined,
"&: hover": {
borderColor: theme.palette.primary.main,
},
})}
/>
</RouterLink>
);
})}
</div>
</div>

templateName: {
fontSize: 16,
textOverflow: "ellipsis",
width: "100%",
overflow: "hidden",
whiteSpace: "nowrap",
},
<div>
<h4 css={{ fontSize: 14, fontWeight: 600, margin: 0, marginBottom: 4 }}>
{example.name}
</h4>
<span
css={(theme) => ({
fontSize: 13,
color: theme.palette.text.secondary,
lineHeight: "1.6",
display: "block",
})}
>
{example.description}{" "}
<Link
component={RouterLink}
to={`/starter-templates/${example.id}`}
css={{ display: "inline-block", fontSize: 13, marginTop: 4 }}
>
Read more
</Link>
</span>
</div>

templateDescription: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
textOverflow: "ellipsis",
width: "100%",
overflow: "hidden",
whiteSpace: "nowrap",
}),
} satisfies Record<string, Interpolation<Theme>>;
<div
css={{
display: "flex",
gap: 12,
flexDirection: "column",
paddingTop: 24,
marginTop: "auto",
alignItems: "center",
}}
>
<Button
component={RouterLink}
fullWidth
to={`/templates/new?exampleId=${example.id}`}
>
Use template
</Button>
</div>
</div>
);
};
6 changes: 3 additions & 3 deletions site/src/pages/SetupPage/SetupPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ describe("Setup Page", () => {
element: <SetupPage />,
},
{
path: "/",
element: <h1>Workspaces</h1>,
path: "/templates",
element: <h1>Templates</h1>,
},
],
{ initialEntries: ["/setup"] },
),
);
await waitForLoaderToBeRemoved();
await fillForm();
await waitFor(() => screen.findByText("Workspaces"));
await waitFor(() => screen.findByText("Templates"));
});
});
2 changes: 1 addition & 1 deletion site/src/pages/SetupPage/SetupPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const SetupPage: FC = () => {
onSubmit={async (firstUser) => {
await createFirstUserMutation.mutateAsync(firstUser);
await signIn(firstUser.email, firstUser.password);
navigate("/");
navigate("/templates");
}}
/>
</>
Expand Down
37 changes: 20 additions & 17 deletions site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({

{Boolean(!starterTemplatesByTag) && <Loader />}

<Stack direction="row" spacing={4}>
<Stack direction="row" spacing={4} alignItems="flex-start">
{starterTemplatesByTag && tags && (
<Stack css={styles.filter}>
<Stack
css={{ width: 208, flexShrink: 0, position: "sticky", top: 48 }}
>
<span css={styles.filterCaption}>Filter</span>
{tags.map((tag) => (
<Link
Expand All @@ -79,10 +81,24 @@ export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({
</Stack>
)}

<div css={styles.templates}>
<div
css={{
display: "flex",
flexWrap: "wrap",
gap: 32,
height: "max-content",
}}
>
{visibleTemplates &&
visibleTemplates.map((example) => (
<TemplateExampleCard example={example} key={example.id} />
<TemplateExampleCard
css={(theme) => ({
backgroundColor: theme.palette.background.paper,
})}
example={example}
key={example.id}
activeTag={activeTag}
/>
))}
</div>
</Stack>
Expand All @@ -91,11 +107,6 @@ export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({
};

const styles = {
filter: {
width: 208,
flexShrink: 0,
},

filterCaption: (theme) => ({
textTransform: "uppercase",
fontWeight: 600,
Expand All @@ -119,12 +130,4 @@ const styles = {
color: theme.palette.text.primary,
fontWeight: 600,
}),

templates: {
flex: "1",
display: "grid",
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
gap: 16,
gridAutoRows: "min-content",
},
} satisfies Record<string, Interpolation<Theme>>;
36 changes: 9 additions & 27 deletions site/src/pages/TemplatesPage/EmptyTemplates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,11 @@ export const EmptyTemplates: FC<{
if (canCreateTemplates) {
return (
<TableEmpty
message="Create a Template"
message="Create your first template"
description={
<>
Templates are written in Terraform and describe the infrastructure
for workspaces (e.g., docker_container, aws_instance,
kubernetes_pod). Select a starter template below or
for workspaces. You can start using a starter template below or{" "}
<Link
href={docs("/templates/tutorial")}
target="_blank"
Expand All @@ -64,19 +63,15 @@ export const EmptyTemplates: FC<{
<Stack alignItems="center" spacing={4}>
<div css={styles.featuredExamples}>
{featuredExamples.map((example) => (
<TemplateExampleCard
example={example}
key={example.id}
css={styles.template}
/>
<TemplateExampleCard example={example} key={example.id} />
))}
</div>

<Button
size="small"
component={RouterLink}
to="/starter-templates"
css={styles.viewAllButton}
css={{ borderRadius: 9999 }}
>
View all starter templates
</Button>
Expand Down Expand Up @@ -117,23 +112,10 @@ const styles = {
},
},

featuredExamples: {
maxWidth: 800,
display: "grid",
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
gap: 16,
gridAutoRows: "min-content",
},

template: (theme) => ({
backgroundColor: theme.palette.background.paperLight,

"&:hover": {
backgroundColor: theme.palette.divider,
},
featuredExamples: (theme) => ({
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
gap: theme.spacing(2),
}),

viewAllButton: {
borderRadius: 9999,
},
} satisfies Record<string, Interpolation<Theme>>;
2 changes: 1 addition & 1 deletion site/src/pages/WorkspacesPage/WorkspacesEmpty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export const WorkspacesEmpty = (props: {
</div>
<div>
<h4 css={{ fontSize: 14, fontWeight: 600, margin: 0 }}>
{t.display_name}
{t.display_name.length > 0 ? t.display_name : t.name}
</h4>
<span
css={(theme) => ({
Expand Down