Skip to content

Commit 41d3c91

Browse files
authored
[Learning Paths] Move landing page to new grid layout (#19833)
* [Learning Paths] Move landing page to new grid layout * fix lp icon
1 parent 2aba07f commit 41d3c91

File tree

7 files changed

+221
-208
lines changed

7 files changed

+221
-208
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { useState, type ChangeEvent } from "react";
2+
import Markdown from "react-markdown";
3+
import type { CollectionEntry } from "astro:content";
4+
import type { IconifyIconBuildResult } from "@iconify/utils";
5+
6+
type LearningPaths = CollectionEntry<"learning-paths">["data"][];
7+
type Icons = Record<string, IconifyIconBuildResult>;
8+
9+
type Filters = {
10+
products: string[];
11+
groups: string[];
12+
};
13+
14+
const LearningPathCatalog = ({
15+
paths,
16+
icons,
17+
}: {
18+
paths: LearningPaths;
19+
icons: Icons;
20+
}) => {
21+
const [filters, setFilters] = useState<Filters>({
22+
products: [],
23+
groups: [],
24+
});
25+
26+
const sorted = paths.sort((lp1, lp2) => {
27+
return lp1.priority < lp2.priority ? -1 : 1;
28+
});
29+
30+
const mapped = sorted.map((lp) => {
31+
const icon = icons[lp.product_group];
32+
const groups = [lp.product_group];
33+
34+
if (lp.additional_groups) {
35+
groups.push(...lp.additional_groups);
36+
}
37+
38+
return {
39+
title: lp.title,
40+
icon,
41+
link: lp.path,
42+
description: lp.description,
43+
products: lp.products,
44+
groups,
45+
};
46+
});
47+
48+
const products = [...new Set(mapped.flatMap((lp) => lp.products).sort())];
49+
const groups = [...new Set(mapped.flatMap((lp) => lp.groups).sort())];
50+
51+
// apply filters to the fields list
52+
const filtered = mapped.filter((path) => {
53+
if (filters.groups.length > 0) {
54+
if (!path.groups.some((c) => filters.groups.includes(c))) {
55+
return false;
56+
}
57+
}
58+
59+
if (filters.products.length > 0) {
60+
if (!path.products.some((c) => filters.products.includes(c))) {
61+
return false;
62+
}
63+
}
64+
65+
return true;
66+
});
67+
68+
return (
69+
<div className="md:flex">
70+
<div className="mr-8 w-full md:w-1/4">
71+
<div className="!mb-8 hidden md:block">
72+
<span className="text-sm font-bold uppercase text-gray-600 dark:text-gray-200">
73+
Product groups
74+
</span>
75+
76+
{groups.map((group) => (
77+
<label key={group} className="!my-2 block">
78+
<input
79+
type="checkbox"
80+
className="mr-2"
81+
value={group}
82+
checked={filters.groups.includes(group)}
83+
onChange={(e: ChangeEvent<HTMLInputElement>) => {
84+
if (e.target.checked) {
85+
setFilters({
86+
...filters,
87+
groups: [...filters.groups, e.target.value],
88+
});
89+
} else {
90+
setFilters({
91+
...filters,
92+
groups: filters.groups.filter(
93+
(f) => f !== e.target.value,
94+
),
95+
});
96+
}
97+
}}
98+
/>{" "}
99+
{group}
100+
</label>
101+
))}
102+
</div>
103+
104+
<div className="!mb-8 hidden md:block">
105+
<span className="text-sm font-bold uppercase text-gray-600 dark:text-gray-200">
106+
Products
107+
</span>
108+
109+
{products.map((product) => (
110+
<label key={product} className="!my-2 block">
111+
<input
112+
type="checkbox"
113+
className="mr-2"
114+
value={product}
115+
checked={filters.products.includes(product)}
116+
onChange={(e: ChangeEvent<HTMLInputElement>) => {
117+
if (e.target.checked) {
118+
setFilters({
119+
...filters,
120+
products: [...filters.products, e.target.value],
121+
});
122+
} else {
123+
setFilters({
124+
...filters,
125+
products: filters.products.filter(
126+
(f) => f !== e.target.value,
127+
),
128+
});
129+
}
130+
}}
131+
/>{" "}
132+
{product}
133+
</label>
134+
))}
135+
</div>
136+
</div>
137+
138+
<div className="!mt-0 grid w-full grid-cols-1 items-stretch gap-2 self-start lg:w-3/4 lg:grid-cols-2 lg:gap-4">
139+
{filtered.length === 0 && (
140+
<div className="flex w-full flex-col justify-center rounded-md border bg-gray-50 py-6 text-center align-middle dark:border-gray-500 dark:bg-gray-800 md:col-span-2 lg:col-span-3">
141+
<span className="text-lg !font-bold">No products found</span>
142+
<p>
143+
Try a different search term, or broaden your search by removing
144+
filters.
145+
</p>
146+
</div>
147+
)}
148+
{filtered.map((path) => {
149+
return (
150+
<a
151+
key={path.title}
152+
href={path.link}
153+
className="rounded-md border border-solid border-gray-200 p-6 !text-inherit no-underline hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
154+
>
155+
{path.icon && (
156+
<div className="w-fit rounded-full bg-orange-50 p-1 text-orange-500 dark:bg-orange-950">
157+
<svg
158+
{...path.icon.attributes}
159+
width={24}
160+
height={24}
161+
dangerouslySetInnerHTML={{ __html: path.icon.body }}
162+
/>
163+
</div>
164+
)}
165+
<p className="!mt-3 font-semibold">{path.title}</p>
166+
<Markdown
167+
className="!mt-1 text-sm leading-6"
168+
disallowedElements={["a"]}
169+
unwrapDisallowed={true}
170+
>
171+
{path.description}
172+
</Markdown>
173+
</a>
174+
);
175+
})}
176+
</div>
177+
</div>
178+
);
179+
};
180+
181+
export default LearningPathCatalog;

src/content/learning-paths/cybersafe.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"title": "Project Cybersafe Schools",
33
"path": "/learning-paths/cybersafe/concepts/",
44
"priority": 5,
5-
"description": "Prevent children from accessing obscene or harmful content over the Internet. Go to <a href='https://www.cloudflare.com/lp/cybersafe-schools/'>Project Cybersafe Schools</a> to apply.",
5+
"description": "Prevent children from accessing obscene or harmful content over the Internet. Go to [Project Cybersafe Schools](https://www.cloudflare.com/lp/cybersafe-schools/) to apply.",
66
"products": ["Cloudflare Gateway", "WARP", "DNS"],
77
"product_group": "Cloudflare One",
88
"additional_groups": ["Application security"]

src/icons/learning-paths.svg

Lines changed: 1 addition & 1 deletion
Loading

src/icons/notifications.svg

Lines changed: 1 addition & 1 deletion
Loading

src/icons/speed.svg

Lines changed: 1 addition & 1 deletion
Loading

src/pages/changelog/index.astro

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const props = {
3030
{
3131
notes.map(async (entry) => {
3232
const date = format(entry.data.date, "MMM dd, yyyy");
33-
const time = format(entry.data.date, "hh:mm a");
33+
3434
const productIds = JSON.stringify(
3535
entry.data.products.map((product) => product.id),
3636
);
@@ -41,10 +41,9 @@ const props = {
4141
<div class="!mt-0 sm:flex" data-products={productIds}>
4242
<time
4343
datetime={entry.data.date.toISOString()}
44-
class="whitespace-nowrap leading-none sm:pr-4 sm:text-right"
44+
class="whitespace-nowrap leading-6 sm:pr-4 sm:text-right"
4545
>
4646
<strong class="text-xs">{date}</strong>
47-
<span class="text-xs sm:block">{time}</span>
4847
</time>
4948
<Steps>
5049
<ol class="!mt-0 overflow-x-auto">

0 commit comments

Comments
 (0)