Skip to content

Commit 5edc16b

Browse files
committed
refactor tutorials and get filtering working for locale. TODO: need to filter by tag and fix types
1 parent f170007 commit 5edc16b

File tree

2 files changed

+121
-125
lines changed

2 files changed

+121
-125
lines changed

src/pages/developers/tutorials.tsx

Lines changed: 35 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from "react"
1+
import React, { useEffect, useState } from "react"
22
import styled from "@emotion/styled"
33
import { graphql, PageProps } from "gatsby"
44
import { useIntl } from "react-intl"
@@ -22,11 +22,15 @@ import {
2222

2323
import { getLocaleTimestamp, INVALID_DATETIME } from "../../utils/time"
2424

25-
import foreignTutorials from "../../data/externalTutorials.json"
25+
import externalTutorials from "../../data/externalTutorials.json"
2626
import FeedbackCard from "../../components/FeedbackCard"
2727
import { getSkillTranslationId, Skill } from "../../components/TutorialMetadata"
2828
import { Context } from "../../types"
2929
import { Lang } from "../../utils/languages"
30+
import {
31+
filterTutorialsByLang,
32+
getSortedTutorialTagsForLang,
33+
} from "../../utils/tutorials"
3034

3135
const SubSlogan = styled.p`
3236
font-size: 1.25rem;
@@ -217,7 +221,7 @@ const published = (locale: string, published: string) => {
217221
) : null
218222
}
219223

220-
interface IExternalTutorial {
224+
export interface IExternalTutorial {
221225
url: string
222226
title: string
223227
description: string
@@ -230,7 +234,7 @@ interface IExternalTutorial {
230234
publishDate: string
231235
}
232236

233-
interface ITutorial {
237+
export interface ITutorial {
234238
to: string
235239
title: string
236240
description: string
@@ -243,7 +247,7 @@ interface ITutorial {
243247
isExternal: boolean
244248
}
245249

246-
interface ITutorialsState {
250+
export interface ITutorialsState {
247251
activeTagNames: Array<string>
248252
filteredTutorials: Array<ITutorial>
249253
}
@@ -253,130 +257,36 @@ const TutorialsPage = ({
253257
pageContext,
254258
}: PageProps<Queries.DevelopersTutorialsPageQuery, Context>) => {
255259
const intl = useIntl()
256-
// Filter tutorials by language and map to object
257-
const internalTutorials = data.allTutorials.nodes.map<ITutorial>(
258-
(tutorial) => ({
259-
to:
260-
tutorial?.fields?.slug?.substr(0, 3) === "/en"
261-
? tutorial.fields.slug.substr(3)
262-
: tutorial.fields?.slug || "/",
263-
title: tutorial?.frontmatter?.title || "",
264-
description: tutorial?.frontmatter?.description || "",
265-
author: tutorial?.frontmatter?.author || "",
266-
tags: tutorial?.frontmatter?.tags?.map((tag) =>
267-
(tag || "").toLowerCase().trim()
268-
),
269-
skill: tutorial?.frontmatter?.skill as Skill,
270-
timeToRead: tutorial?.fields?.readingTime?.minutes
271-
? Math.round(tutorial?.fields?.readingTime?.minutes)
272-
: null,
273-
published: tutorial?.frontmatter?.published,
274-
lang: tutorial?.frontmatter?.lang || "en",
275-
isExternal: false,
276-
})
277-
)
278-
279-
const externalTutorials = foreignTutorials.map<ITutorial>(
280-
(tutorial: IExternalTutorial) => ({
281-
to: tutorial.url,
282-
title: tutorial.title,
283-
description: tutorial.description,
284-
author: tutorial.author,
285-
tags: tutorial.tags.map((tag) => tag.toLowerCase().trim()),
286-
skill: tutorial.skillLevel as Skill,
287-
timeToRead: Number(tutorial.timeToRead),
288-
published: new Date(tutorial.publishDate).toISOString(),
289-
lang: tutorial.lang || "en",
290-
isExternal: true,
291-
})
260+
const filteredTutorialsByLang = filterTutorialsByLang(
261+
data.allTutorials.nodes,
262+
externalTutorials,
263+
pageContext.locale
292264
)
265+
const allTags = getSortedTutorialTagsForLang(filteredTutorialsByLang)
293266

294-
const allTutorials: Array<ITutorial> = [
295-
...externalTutorials,
296-
...internalTutorials,
297-
]
298-
299-
const hasTutorialsCheck = allTutorials.some(
300-
(tutorial) => tutorial.lang === pageContext.language
267+
const [filteredTutorials, setFilteredTutorials] = useState(
268+
filteredTutorialsByLang
301269
)
302-
303-
const filteredTutorials = allTutorials
304-
.filter((tutorial) =>
305-
hasTutorialsCheck
306-
? tutorial.lang === pageContext.language
307-
: tutorial.lang === "en"
308-
)
309-
.sort((a, b) => {
310-
if (a.published && b.published) {
311-
return new Date(b.published).getTime() - new Date(a.published).getTime()
312-
}
313-
// Dont order if no published is present
314-
return 0
315-
})
316-
317-
// Tally all subject tag counts
318-
const tagsConcatenated: Array<string> = []
319-
for (const tutorial of filteredTutorials) {
320-
if (tutorial.tags) {
321-
tagsConcatenated.push(...tutorial.tags)
322-
}
323-
}
324-
325-
const allTags = tagsConcatenated.map((tag) => ({ name: tag, totalCount: 1 }))
326-
const sanitizedAllTags = Array.from(
327-
allTags.reduce(
328-
(m, { name, totalCount }) =>
329-
m.set(
330-
name.toLowerCase().trim(),
331-
(m.get(name.toLowerCase().trim()) || 0) + totalCount
332-
),
333-
new Map()
334-
),
335-
([name, totalCount]) => ({ name, totalCount })
336-
).sort((a, b) => a.name.localeCompare(b.name))
337-
338-
const [state, setState] = useState<ITutorialsState>({
339-
activeTagNames: [],
340-
filteredTutorials: filteredTutorials,
341-
})
342-
343-
const clearActiveTags = () => {
344-
setState({
345-
activeTagNames: [],
346-
filteredTutorials: filteredTutorials,
347-
})
348-
}
270+
const [selectedTags, setSelectedTags] = useState<string[]>([])
349271

350272
const handleTagSelect = (tagName: string) => {
351-
const activeTagNames = state.activeTagNames
273+
const tempSelectedTags = selectedTags
352274

353-
// Add or remove the selected tag
354-
const index = activeTagNames.indexOf(tagName)
275+
const index = tempSelectedTags.indexOf(tagName)
355276
if (index > -1) {
356-
activeTagNames.splice(index, 1)
277+
tempSelectedTags.splice(index, 1)
357278
} else {
358-
activeTagNames.push(tagName)
279+
tempSelectedTags.push(tagName)
359280
}
360281

361-
// If no tags are active, show all tutorials, otherwise filter by active tag
362-
let filteredTutorials = allTutorials
363-
if (activeTagNames.length > 0) {
364-
filteredTutorials = filteredTutorials.filter((tutorial) => {
365-
for (const tag of activeTagNames) {
366-
if (!tutorial.tags?.includes(tag)) {
367-
return false
368-
}
369-
}
370-
return hasTutorialsCheck
371-
? tutorial.lang === pageContext.language
372-
: tutorial.lang === "en"
373-
})
374-
}
375-
setState({ activeTagNames, filteredTutorials })
282+
setSelectedTags([...tempSelectedTags])
376283
}
377284

378-
const hasActiveTags = state.activeTagNames.length > 0
379-
const hasNoTutorials = state.filteredTutorials.length === 0
285+
// TODO: Update setFilteredTutorials with filteredTutorialsByLang filtered out by selectedTags
286+
useEffect(() => {
287+
console.log("CHANGE")
288+
}, [selectedTags])
289+
380290
const [isModalOpen, setModalOpen] = useState(false)
381291

382292
return (
@@ -456,28 +366,28 @@ const TutorialsPage = ({
456366
<TutorialContainer>
457367
<TagsContainer>
458368
<TagContainer>
459-
{sanitizedAllTags.map((tag) => {
460-
const name = `${tag.name} (${tag.totalCount})`
461-
const isActive = state.activeTagNames.includes(tag.name)
369+
{Object.entries(allTags).map(([tagName, tagCount]) => {
370+
const name = `${tagName} (${tagCount})`
371+
const isActive = selectedTags.includes(tagName)
462372
return (
463373
<Tag
464374
name={name}
465375
key={name}
466376
isActive={isActive}
467377
shouldShowIcon={false}
468378
onClick={handleTagSelect}
469-
value={tag.name}
379+
value={tagName}
470380
/>
471381
)
472382
})}
473-
{hasActiveTags && (
474-
<ClearLink onClick={clearActiveTags}>
383+
{selectedTags.length > 0 && (
384+
<ClearLink onClick={() => setSelectedTags([])}>
475385
<Translation id="page-find-wallet-clear" />
476386
</ClearLink>
477387
)}
478388
</TagContainer>
479389
</TagsContainer>
480-
{hasNoTutorials && (
390+
{filteredTutorials.length === 0 && (
481391
<ResultsContainer>
482392
<Emoji text=":crying_face:" size={3} mb={`2em`} mt={`2em`} />
483393
<h2>
@@ -488,7 +398,7 @@ const TutorialsPage = ({
488398
</p>
489399
</ResultsContainer>
490400
)}
491-
{state.filteredTutorials.map((tutorial) => {
401+
{filteredTutorials.map((tutorial) => {
492402
return (
493403
<TutorialCard
494404
key={tutorial.to}

src/utils/tutorials.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { IExternalTutorial, ITutorial } from "../pages/developers/tutorials"
2+
import { Skill } from "../components/TutorialMetadata"
3+
import { Lang } from "./languages"
4+
5+
// Take all tutorials, and return a list of tutorials for a specific locale
6+
export const filterTutorialsByLang = (
7+
internalTutorials: Array<any>,
8+
externalTutorials: Array<IExternalTutorial>,
9+
locale: Lang
10+
) => {
11+
const internalTutorialsMap = internalTutorials.map<any>((tutorial) => ({
12+
to:
13+
tutorial?.fields?.slug?.substr(0, 3) === "/en"
14+
? tutorial.fields.slug.substr(3)
15+
: tutorial.fields?.slug || "/",
16+
title: tutorial?.frontmatter?.title || "",
17+
description: tutorial?.frontmatter?.description || "",
18+
author: tutorial?.frontmatter?.author || "",
19+
tags: tutorial?.frontmatter?.tags?.map((tag) =>
20+
(tag || "").toLowerCase().trim()
21+
),
22+
skill: tutorial?.frontmatter?.skill as Skill,
23+
timeToRead: tutorial?.fields?.readingTime?.minutes
24+
? Math.round(tutorial?.fields?.readingTime?.minutes)
25+
: null,
26+
published: tutorial?.frontmatter?.published,
27+
lang: tutorial?.frontmatter?.lang || "en",
28+
isExternal: false,
29+
}))
30+
31+
const externalTutorialsMap = externalTutorials.map<ITutorial>(
32+
(tutorial: IExternalTutorial) => ({
33+
to: tutorial.url,
34+
title: tutorial.title,
35+
description: tutorial.description,
36+
author: tutorial.author,
37+
tags: tutorial.tags.map((tag) => tag.toLowerCase().trim()),
38+
skill: tutorial.skillLevel as Skill,
39+
timeToRead: Number(tutorial.timeToRead),
40+
published: new Date(tutorial.publishDate).toISOString(),
41+
lang: tutorial.lang || "en",
42+
isExternal: true,
43+
})
44+
)
45+
46+
const allTutorials: Array<ITutorial> = [
47+
...externalTutorialsMap,
48+
...internalTutorialsMap,
49+
]
50+
51+
const filteredTutorials = allTutorials
52+
.filter((tutorial) => tutorial.lang === locale)
53+
.sort((a, b) => {
54+
if (a.published && b.published) {
55+
return new Date(b.published).getTime() - new Date(a.published).getTime()
56+
}
57+
// Dont order if no published is present
58+
return 0
59+
})
60+
61+
return filteredTutorials
62+
}
63+
64+
export const getSortedTutorialTagsForLang = (filteredTutorialsByLang) => {
65+
const allTags = filteredTutorialsByLang.reduce((tags, tutorial) => {
66+
return [...tags, ...tutorial.tags]
67+
}, [])
68+
69+
const reducedTags = allTags.reduce((acc, tag) => {
70+
if (acc[tag]) {
71+
acc[tag] = acc[tag] + 1
72+
} else {
73+
acc[tag] = 1
74+
}
75+
return acc
76+
}, {})
77+
78+
const sortedTags = Object.keys(reducedTags)
79+
.sort()
80+
.reduce((obj, key) => {
81+
obj[key] = reducedTags[key]
82+
return obj
83+
}, {})
84+
85+
return sortedTags
86+
}

0 commit comments

Comments
 (0)