Skip to content

Commit 3815948

Browse files
committed
Fixed routing for CategoryList and SnippetList
1 parent f1192d3 commit 3815948

File tree

17 files changed

+243
-124
lines changed

17 files changed

+243
-124
lines changed

index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!doctype html>
1+
<!DOCTYPE html>
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
@@ -8,6 +8,7 @@
88
</head>
99
<body>
1010
<div id="root"></div>
11+
<div id="modal-root"></div>
1112
<script type="module" src="/src/main.tsx"></script>
1213
</body>
1314
</html>

public/data/javascript.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
"code": "document.getElementById('myElement').classList.remove('myClass');",
1616
"tags": ["dom", "class", "javascript"],
1717
"author": "@technoph1le"
18+
},
19+
{
20+
"title": "Something else",
21+
"description": "Remove a CSS class from a DOM element.",
22+
"code": "document.getElementById('myElement').classList.remove('myClass');",
23+
"tags": ["dom", "class", "javascript"],
24+
"author": "@technoph1le"
1825
}
1926
]
2027
},

src/App.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { Outlet } from "react-router-dom";
21
import Footer from "./layouts/Footer";
32
import Header from "./layouts/Header";
3+
import SnippetList from "./components/SnippetList";
44
import Sidebar from "./layouts/Sidebar";
5-
import SnippetList from "./layouts/SnippetList";
6-
import LanguageSwitch from "./components/LanguageSwitch";
75

86
const App = () => {
97
return (
@@ -19,10 +17,7 @@ const App = () => {
1917
</p>
2018
</div>
2119
<main className="main">
22-
<aside className="sidebar flow">
23-
<LanguageSwitch />
24-
<Outlet />
25-
</aside>
20+
<Sidebar />
2621
<SnippetList />
2722
</main>
2823
<hr className="divider" />

src/components/Button.tsx

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@ import { ReactNode } from "react";
33
type ButtonProps = {
44
as?: "button" | "link";
55
href?: string;
6-
children: ReactNode;
76
className?: string;
8-
};
9-
10-
type IconButtonProps = {
7+
isIcon?: boolean;
118
children: ReactNode;
12-
className?: string;
139
};
1410

1511
const Button = ({
1612
as = "button",
1713
href,
1814
className,
1915
children,
16+
isIcon,
2017
...props
2118
}: ButtonProps) => {
2219
return as === "button" ? (
23-
<button className={`button ${className}`} {...props}>
20+
<button
21+
className={`button ${isIcon ?? "button--icon"} ${className}`}
22+
{...props}
23+
>
2424
{children}
2525
</button>
2626
) : (
@@ -30,16 +30,4 @@ const Button = ({
3030
);
3131
};
3232

33-
export const IconButton = ({
34-
className,
35-
children,
36-
...props
37-
}: IconButtonProps) => {
38-
return (
39-
<button className={`button--icon ${className}`} {...props}>
40-
{children}
41-
</button>
42-
);
43-
};
44-
4533
export default Button;

src/components/Category.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const Category = ({ title, language }: CategoryProps) => {
66
return (
77
<li className="category">
88
<Link
9-
to={`/${slugify(language || "")}/${slugify(title)}`}
9+
to={`/${slugify(language)}/${slugify(title)}`}
1010
className="category__link"
1111
>
1212
{title}

src/components/CategoryList.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,41 @@
1-
import { useLoaderData, useParams } from "react-router-dom";
1+
import { useParams } from "react-router-dom";
22
import Category from "./Category";
3+
import { useEffect, useState } from "react";
4+
import { LanguageData } from "../types";
5+
import { getCategoryNames } from "../utils/filters";
36

47
const CategoryList = () => {
58
const { language } = useParams();
6-
const categories = useLoaderData() as string[];
9+
const [categoryNames, setCategoryNames] = useState<string[]>([]);
710

8-
console.log(categories);
11+
useEffect(() => {
12+
const fetchCategories = async () => {
13+
try {
14+
const res = await fetch(`/data/${language}.json`);
15+
if (!res.ok) {
16+
throw new Error("Failed to fetch languages in CategoryList.tsx");
17+
}
18+
const data: LanguageData = await res.json();
19+
const filteredCategoryNames = getCategoryNames(data);
920

10-
if (!categories) {
21+
setCategoryNames(filteredCategoryNames);
22+
} catch (error) {
23+
console.error("Error occured with CategoryList.tsx", error);
24+
}
25+
};
26+
27+
fetchCategories();
28+
}, [language]);
29+
30+
if (!categoryNames) {
1131
return <div>empty</div>;
1232
}
1333

1434
return (
1535
<>
1636
<ul role="list" className="categories">
17-
{categories.map((category) => (
18-
<Category key={category} title={category} language={language || ""} />
37+
{categoryNames.map((name) => (
38+
<Category key={name} title={name} language={language || ""} />
1939
))}
2040
</ul>
2141
</>

src/components/Logo.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { Link } from "react-router-dom";
12
import { LogoIcon } from "./Icons";
23

34
const Logo = () => {
45
return (
5-
<a className="logo" href="/">
6+
<Link to={"/"} className="logo">
67
<LogoIcon />
78
<span>QuickSnip</span>
8-
</a>
9+
</Link>
910
);
1011
};
1112

src/components/SnippetCard.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1+
import { Link } from "react-router-dom";
12
import { SnippetCardProps } from "../types";
2-
import { IconButton } from "./Button";
33
import { CopyIcon, ExpandIcon } from "./Icons";
4+
import slugify from "../utils/slugify";
5+
import Button from "./Button";
46

5-
const SnippetCard = ({ title }: SnippetCardProps) => {
7+
const SnippetCard = ({ title, language, category }: SnippetCardProps) => {
68
return (
79
<li className="snippet">
810
<div className="snippet__preview">
9-
<IconButton className="snippet__copy">
11+
<Button isIcon={true} className="snippet__copy">
1012
<CopyIcon />
11-
</IconButton>
13+
</Button>
1214
</div>
1315
<div className="snippet__content">
1416
<h3 className="snippet__title">{title}</h3>
15-
<IconButton>
17+
<Link
18+
to={`/${slugify(language)}/${slugify(category)}/${slugify(title)}`}
19+
>
1620
<ExpandIcon />
17-
</IconButton>
21+
</Link>
1822
</div>
1923
</li>
2024
);

src/components/SnippetList.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useParams } from "react-router-dom";
2+
import SnippetCard from "./SnippetCard";
3+
import { LanguageData, SnippetType } from "../types";
4+
import { useEffect, useState } from "react";
5+
import { getCategoryNames, getSnippetsByCategory } from "../utils/filters";
6+
import slugify from "../utils/slugify";
7+
8+
const SnippetList = () => {
9+
const { language, category } = useParams();
10+
const [snippets, setSnippets] = useState<SnippetType[] | null>(null);
11+
const [categoryTitle, setCategoryTitle] = useState<string>("");
12+
13+
useEffect(() => {
14+
const fetchSnippets = async () => {
15+
try {
16+
const res = await fetch(`/data/${language}.json`);
17+
if (!res.ok) {
18+
throw new Error("Failed to fetch languages in SnippetList.tsx");
19+
}
20+
const data: LanguageData = await res.json();
21+
22+
const filteredCategoryTitle = getCategoryNames(data).find(
23+
(item) => slugify(item) === slugify(category || "")
24+
);
25+
setCategoryTitle(filteredCategoryTitle || "");
26+
27+
// TODO: instead of "", use default category as fallback
28+
const filteredSnippets = getSnippetsByCategory(data, category || "");
29+
setSnippets(filteredSnippets);
30+
} catch (error) {
31+
console.error("Error occured with SnippetList.tsx: ", error);
32+
}
33+
};
34+
35+
fetchSnippets();
36+
}, [category]);
37+
38+
if (!snippets) return <div>empty</div>;
39+
40+
return (
41+
<section className="flow">
42+
<h2 className="section-title">{categoryTitle}</h2>
43+
<ul role="list" className="snippets">
44+
{snippets.map((snippet) => (
45+
<SnippetCard
46+
key={snippet.title}
47+
language={language || ""}
48+
category={category || ""}
49+
{...snippet}
50+
/>
51+
))}
52+
</ul>
53+
</section>
54+
);
55+
};
56+
57+
export default SnippetList;

src/components/SnippetModal.tsx

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,55 @@
1+
import { useEffect, useState } from "react";
2+
import ReactDOM from "react-dom";
3+
import { useNavigate, useParams } from "react-router-dom";
4+
import { LanguageData, SnippetType } from "../types";
5+
import { getSnippetByTitleAndCategory } from "../utils/filters";
6+
17
const SnippetModal = () => {
2-
return <p>Modal</p>;
8+
console.log("hello modal");
9+
const modalRoot = document.getElementById("modal-root");
10+
if (!modalRoot) return null;
11+
12+
const { language, category, snippet_title } = useParams();
13+
const [snippet, setSnippet] = useState<SnippetType | null>(null);
14+
const navigate = useNavigate();
15+
console.log(language, category, snippet_title);
16+
17+
useEffect(() => {
18+
const fetchSnippets = async () => {
19+
try {
20+
const res = await fetch(`/data/${language}.json`);
21+
if (!res.ok) {
22+
throw new Error("Failed to fetch languages in SnippetModal.tsx");
23+
}
24+
const data: LanguageData = await res.json();
25+
26+
// TODO: instead of "", use default category as fallback
27+
const filteredSnippet = getSnippetByTitleAndCategory(
28+
data,
29+
category || "",
30+
snippet_title || ""
31+
);
32+
33+
setSnippet(filteredSnippet);
34+
} catch (error) {
35+
console.error("Error occured with SnippetModal.tsx: ", error);
36+
}
37+
};
38+
39+
fetchSnippets();
40+
}, []);
41+
42+
if (!snippet) return null;
43+
44+
return ReactDOM.createPortal(
45+
<div className="modal-overlay">
46+
<div className="modal-content">
47+
{snippet.title}
48+
<button onClick={() => navigate(-1)}>Close</button>
49+
</div>
50+
</div>,
51+
modalRoot
52+
);
353
};
454

555
export default SnippetModal;

0 commit comments

Comments
 (0)