Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8b0f95b

Browse files
committed
Add empty state
1 parent cec4b00 commit 8b0f95b

File tree

7 files changed

+302
-214
lines changed

7 files changed

+302
-214
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import { TemplateExample } from "api/typesGenerated"
3+
import { FC } from "react"
4+
import { Link } from "react-router-dom"
5+
import { combineClasses } from "util/combineClasses"
6+
7+
export interface TemplateExampleCardProps {
8+
example: TemplateExample
9+
className?: string
10+
}
11+
12+
export const TemplateExampleCard: FC<TemplateExampleCardProps> = ({
13+
example,
14+
className,
15+
}) => {
16+
const styles = useStyles()
17+
18+
return (
19+
<Link
20+
to={`/starter-templates/${example.id}`}
21+
className={combineClasses([styles.template, className])}
22+
key={example.id}
23+
>
24+
<div className={styles.templateIcon}>
25+
<img src={example.icon} alt="" />
26+
</div>
27+
<div className={styles.templateInfo}>
28+
<span className={styles.templateName}>{example.name}</span>
29+
<span className={styles.templateDescription}>
30+
{example.description}
31+
</span>
32+
</div>
33+
</Link>
34+
)
35+
}
36+
37+
const useStyles = makeStyles((theme) => ({
38+
template: {
39+
border: `1px solid ${theme.palette.divider}`,
40+
borderRadius: theme.shape.borderRadius,
41+
background: theme.palette.background.paper,
42+
textDecoration: "none",
43+
textAlign: "left",
44+
color: "inherit",
45+
display: "flex",
46+
alignItems: "center",
47+
height: "fit-content",
48+
49+
"&:hover": {
50+
backgroundColor: theme.palette.background.paperLight,
51+
},
52+
},
53+
54+
templateIcon: {
55+
width: theme.spacing(12),
56+
height: theme.spacing(12),
57+
display: "flex",
58+
alignItems: "center",
59+
justifyContent: "center",
60+
flexShrink: 0,
61+
62+
"& img": {
63+
height: theme.spacing(4),
64+
},
65+
},
66+
67+
templateInfo: {
68+
padding: theme.spacing(2, 2, 2, 0),
69+
display: "flex",
70+
flexDirection: "column",
71+
gap: theme.spacing(0.5),
72+
overflow: "hidden",
73+
},
74+
75+
templateName: {
76+
fontSize: theme.spacing(2),
77+
textOverflow: "ellipsis",
78+
width: "100%",
79+
overflow: "hidden",
80+
whiteSpace: "nowrap",
81+
},
82+
83+
templateDescription: {
84+
fontSize: theme.spacing(1.75),
85+
color: theme.palette.text.secondary,
86+
textOverflow: "ellipsis",
87+
width: "100%",
88+
overflow: "hidden",
89+
whiteSpace: "nowrap",
90+
},
91+
}))

site/src/i18n/en/templatesPage.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,9 @@
22
"errors": {
33
"getOrganizationError": "Something went wrong fetching organizations.",
44
"getTemplatesError": "Something went wrong fetching templates."
5+
},
6+
"empty": {
7+
"message": "Create your first template",
8+
"descriptionWithoutPermissions": "Contact your Coder administrator to create a template. You can share the code below."
59
}
610
}

site/src/pages/StarterTemplatesPage/StarterTemplatesPageView.tsx

Lines changed: 2 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
PageHeaderTitle,
1010
} from "components/PageHeader/PageHeader"
1111
import { Stack } from "components/Stack/Stack"
12+
import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExampleCard"
1213
import { FC } from "react"
1314
import { useTranslation } from "react-i18next"
1415
import { Link, useSearchParams } from "react-router-dom"
@@ -85,21 +86,7 @@ export const StarterTemplatesPageView: FC<StarterTemplatesPageViewProps> = ({
8586
<div className={styles.templates}>
8687
{visibleTemplates &&
8788
visibleTemplates.map((example) => (
88-
<Link
89-
to={example.id}
90-
className={styles.template}
91-
key={example.id}
92-
>
93-
<div className={styles.templateIcon}>
94-
<img src={example.icon} alt="" />
95-
</div>
96-
<div className={styles.templateInfo}>
97-
<span className={styles.templateName}>{example.name}</span>
98-
<span className={styles.templateDescription}>
99-
{example.description}
100-
</span>
101-
</div>
102-
</Link>
89+
<TemplateExampleCard example={example} key={example.id} />
10390
))}
10491
</div>
10592
</Stack>
@@ -144,53 +131,4 @@ const useStyles = makeStyles((theme) => ({
144131
gap: theme.spacing(2),
145132
gridAutoRows: "min-content",
146133
},
147-
148-
template: {
149-
border: `1px solid ${theme.palette.divider}`,
150-
borderRadius: theme.shape.borderRadius,
151-
background: theme.palette.background.paper,
152-
textDecoration: "none",
153-
color: "inherit",
154-
display: "flex",
155-
alignItems: "center",
156-
height: "fit-content",
157-
158-
"&:hover": {
159-
backgroundColor: theme.palette.background.paperLight,
160-
},
161-
},
162-
163-
templateIcon: {
164-
width: theme.spacing(12),
165-
height: theme.spacing(12),
166-
display: "flex",
167-
alignItems: "center",
168-
justifyContent: "center",
169-
flexShrink: 0,
170-
171-
"& img": {
172-
height: theme.spacing(4),
173-
},
174-
},
175-
176-
templateInfo: {
177-
padding: theme.spacing(2, 2, 2, 0),
178-
display: "flex",
179-
flexDirection: "column",
180-
gap: theme.spacing(0.5),
181-
overflow: "hidden",
182-
},
183-
184-
templateName: {
185-
fontSize: theme.spacing(2),
186-
},
187-
188-
templateDescription: {
189-
fontSize: theme.spacing(1.75),
190-
color: theme.palette.text.secondary,
191-
textOverflow: "ellipsis",
192-
width: "100%",
193-
overflow: "hidden",
194-
whiteSpace: "nowrap",
195-
},
196134
}))
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import Button from "@material-ui/core/Button"
2+
import Link from "@material-ui/core/Link"
3+
import { makeStyles } from "@material-ui/core/styles"
4+
import { TemplateExample } from "api/typesGenerated"
5+
import { CodeExample } from "components/CodeExample/CodeExample"
6+
import { Stack } from "components/Stack/Stack"
7+
import { TableEmpty } from "components/TableEmpty/TableEmpty"
8+
import { TemplateExampleCard } from "components/TemplateExampleCard/TemplateExampleCard"
9+
import { FC } from "react"
10+
import { useTranslation } from "react-i18next"
11+
import { Link as RouterLink } from "react-router-dom"
12+
import { Permissions } from "xServices/auth/authXService"
13+
14+
// Those are from https://github.com/coder/coder/tree/main/examples/templates
15+
const featuredExamples = [
16+
"docker",
17+
"kubernetes",
18+
"aws-linux",
19+
"aws-windows",
20+
"gcp-linux",
21+
"gcp-windows",
22+
]
23+
24+
const findFeaturedExamples = (examples: TemplateExample[]) => {
25+
return examples.filter((example) => featuredExamples.includes(example.id))
26+
}
27+
28+
export const EmptyTemplates: FC<{
29+
permissions: Permissions
30+
examples: TemplateExample[]
31+
}> = ({ permissions, examples }) => {
32+
const styles = useStyles()
33+
const { t } = useTranslation("templatesPage")
34+
const featuredExamples = findFeaturedExamples(examples)
35+
36+
if (permissions.createTemplates) {
37+
return (
38+
<TableEmpty
39+
message={t("empty.message")}
40+
description={
41+
<>
42+
You can create a template using our starter templates or{" "}
43+
<Link component={RouterLink} to="/new">
44+
uploading a template
45+
</Link>
46+
. You can also{" "}
47+
<Link
48+
href="https://coder.com/docs/coder-oss/latest/templates#add-a-template"
49+
target="_blank"
50+
rel="noreferrer"
51+
>
52+
use the CLI
53+
</Link>
54+
.
55+
</>
56+
}
57+
cta={
58+
<Stack alignItems="center" spacing={4}>
59+
<div className={styles.featuredExamples}>
60+
{featuredExamples.map((example) => (
61+
<TemplateExampleCard
62+
example={example}
63+
key={example.id}
64+
className={styles.template}
65+
/>
66+
))}
67+
</div>
68+
69+
<Button
70+
size="small"
71+
component={RouterLink}
72+
to="/starter-templates"
73+
className={styles.viewAllButton}
74+
>
75+
View all starter templates
76+
</Button>
77+
</Stack>
78+
}
79+
/>
80+
)
81+
}
82+
83+
return (
84+
<TableEmpty
85+
className={styles.withImage}
86+
message={t("empty.message")}
87+
description={t("empty.descriptionWithoutPermissions")}
88+
cta={<CodeExample code="coder templates init" />}
89+
image={
90+
<div className={styles.emptyImage}>
91+
<img src="/featured/templates.webp" alt="" />
92+
</div>
93+
}
94+
/>
95+
)
96+
}
97+
98+
const useStyles = makeStyles((theme) => ({
99+
withImage: {
100+
paddingBottom: 0,
101+
},
102+
103+
emptyImage: {
104+
maxWidth: "50%",
105+
height: theme.spacing(40),
106+
overflow: "hidden",
107+
opacity: 0.85,
108+
109+
"& img": {
110+
maxWidth: "100%",
111+
},
112+
},
113+
114+
featuredExamples: {
115+
maxWidth: theme.spacing(100),
116+
display: "grid",
117+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
118+
gap: theme.spacing(2),
119+
gridAutoRows: "min-content",
120+
},
121+
122+
template: {
123+
backgroundColor: theme.palette.background.paperLight,
124+
125+
"&:hover": {
126+
backgroundColor: theme.palette.divider,
127+
},
128+
},
129+
130+
viewAllButton: {
131+
borderRadius: 9999,
132+
},
133+
}))

site/src/pages/TemplatesPage/TemplatesPage.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
1-
import { useActor, useMachine } from "@xstate/react"
2-
import React, { useContext } from "react"
1+
import { useMachine } from "@xstate/react"
2+
import { useOrganizationId } from "hooks/useOrganizationId"
3+
import { usePermissions } from "hooks/usePermissions"
4+
import React from "react"
35
import { Helmet } from "react-helmet-async"
46
import { pageTitle } from "../../util/page"
5-
import { XServiceContext } from "../../xServices/StateContext"
67
import { templatesMachine } from "../../xServices/templates/templatesXService"
78
import { TemplatesPageView } from "./TemplatesPageView"
89

910
export const TemplatesPage: React.FC = () => {
10-
const xServices = useContext(XServiceContext)
11-
const [authState] = useActor(xServices.authXService)
12-
const [templatesState] = useMachine(templatesMachine)
13-
const { templates, getOrganizationsError, getTemplatesError } =
14-
templatesState.context
11+
const organizationId = useOrganizationId()
12+
const permissions = usePermissions()
13+
const [templatesState] = useMachine(templatesMachine, {
14+
context: {
15+
organizationId,
16+
permissions,
17+
},
18+
})
1519

1620
return (
1721
<>
1822
<Helmet>
1923
<title>{pageTitle("Templates")}</title>
2024
</Helmet>
2125
<TemplatesPageView
22-
templates={templates}
23-
canCreateTemplate={authState.context.permissions?.createTemplates}
24-
loading={templatesState.hasTag("loading")}
25-
getOrganizationsError={getOrganizationsError}
26-
getTemplatesError={getTemplatesError}
26+
context={templatesState.context}
27+
permissions={permissions}
2728
/>
2829
</>
2930
)

0 commit comments

Comments
 (0)