Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 43bcf18

Browse files
committed
working search functionality with language and category set in the url, search and snippet set as query params
1 parent 5aa4619 commit 43bcf18

File tree

12 files changed

+191
-106
lines changed

12 files changed

+191
-106
lines changed

‎cspell-dict.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
quicksnip
2+
slugified
23
slugifyed

‎src/AppRouter.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Route, Routes } from "react-router-dom";
22

3-
import Container from "@components/Container";
3+
import App from "@components/App";
44
import SnippetList from "@components/SnippetList";
55

66
const AppRouter = () => {
77
return (
88
<Routes>
9-
<Route element={<Container />}>
9+
<Route element={<App />}>
1010
<Route path="/" element={<SnippetList />} />
1111
<Route path="/:languageName" element={<SnippetList />} />
1212
<Route path="/:languageName/:categoryName" element={<SnippetList />} />

‎src/components/App.tsx‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { FC } from "react";
2+
3+
import { AppProvider } from "@contexts/AppContext";
4+
5+
import Container from "./Container";
6+
7+
interface AppProps {}
8+
9+
const App: FC<AppProps> = () => {
10+
return (
11+
<AppProvider>
12+
<Container />
13+
</AppProvider>
14+
);
15+
};
16+
17+
export default App;

‎src/components/CategoryList.tsx‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
import { FC } from "react";
2+
import { useNavigate } from "react-router-dom";
23

34
import { useAppContext } from "@contexts/AppContext";
45
import { useCategories } from "@hooks/useCategories";
6+
import { slugify } from "@utils/slugify";
57

68
interface CategoryListItemProps {
79
name: string;
810
}
911

1012
const CategoryListItem: FC<CategoryListItemProps> = ({ name }) => {
11-
const { category, setCategory } = useAppContext();
13+
const navigate = useNavigate();
14+
15+
const { language, category, setCategory } = useAppContext();
1216

1317
const handleSelect = () => {
1418
setCategory(name);
19+
navigate(`/${slugify(language.name)}/${slugify(name)}`);
1520
};
1621

1722
return (
1823
<li className="category">
1924
<button
2025
className={`category__btn ${
21-
name === category ? "category__btn--active" : ""
26+
slugify(name) === slugify(category) ? "category__btn--active" : ""
2227
}`}
2328
onClick={handleSelect}
2429
>
@@ -31,9 +36,13 @@ const CategoryListItem: FC<CategoryListItemProps> = ({ name }) => {
3136
const CategoryList = () => {
3237
const { fetchedCategories, loading, error } = useCategories();
3338

34-
if (loading) return <div>Loading...</div>;
39+
if (loading) {
40+
return <div>Loading...</div>;
41+
}
3542

36-
if (error) return <div>Error occurred: {error}</div>;
43+
if (error) {
44+
return <div>Error occurred: {error}</div>;
45+
}
3746

3847
return (
3948
<ul role="list" className="categories">

‎src/components/Container.tsx‎

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FC } from "react";
22
import { Outlet } from "react-router-dom";
33

4-
import { AppProvider,useAppContext } from "@contexts/AppContext";
4+
import { useAppContext } from "@contexts/AppContext";
55
import Banner from "@layouts/Banner";
66
import Footer from "@layouts/Footer";
77
import Header from "@layouts/Header";
@@ -13,22 +13,20 @@ const Container: FC<ContainerProps> = () => {
1313
const { category } = useAppContext();
1414

1515
return (
16-
<AppProvider>
17-
<div className="container flow">
18-
<Header />
19-
<Banner />
20-
<main className="main">
21-
<Sidebar />
22-
<section className="flow">
23-
<h2 className="section-title">
24-
{category ? category : "Select a category"}
25-
</h2>
26-
<Outlet />
27-
</section>
28-
</main>
29-
<Footer />
30-
</div>
31-
</AppProvider>
16+
<div className="container flow">
17+
<Header />
18+
<Banner />
19+
<main className="main">
20+
<Sidebar />
21+
<section className="flow">
22+
<h2 className="section-title">
23+
{category ? category : "Select a category"}
24+
</h2>
25+
<Outlet />
26+
</section>
27+
</main>
28+
<Footer />
29+
</div>
3230
);
3331
};
3432

‎src/components/LanguageSelector.tsx‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
1+
/**
2+
* Inspired by https://blog.logrocket.com/creating-custom-select-dropdown-css/
3+
*/
4+
15
import { useRef, useEffect, useState } from "react";
26
import { useNavigate } from "react-router-dom";
37

48
import { useAppContext } from "@contexts/AppContext";
59
import { useKeyboardNavigation } from "@hooks/useKeyboardNavigation";
610
import { useLanguages } from "@hooks/useLanguages";
711
import { LanguageType } from "@types";
12+
import { configureProfile } from "@utils/configureProfile";
813
import { slugify } from "@utils/slugify";
914

10-
// Inspired by https://blog.logrocket.com/creating-custom-select-dropdown-css/
11-
1215
const LanguageSelector = () => {
1316
const navigate = useNavigate();
1417

15-
const { language, setLanguage } = useAppContext();
18+
const { language, setLanguage, setCategory } = useAppContext();
1619
const { fetchedLanguages, loading, error } = useLanguages();
1720

1821
const dropdownRef = useRef<HTMLDivElement>(null);
1922
const [isOpen, setIsOpen] = useState(false);
2023

21-
const handleSelect = (selected: LanguageType) => {
22-
setLanguage(selected);
23-
navigate(`/${slugify(selected.name)}`);
24+
const handleSelect = async (selected: LanguageType) => {
25+
const { language: newLanguage, category: newCategory } =
26+
await configureProfile({
27+
languageName: selected.name,
28+
});
29+
30+
setLanguage(newLanguage);
31+
setCategory(newCategory);
32+
navigate(`/${slugify(newLanguage.name)}/${slugify(newCategory)}`);
2433
setIsOpen(false);
2534
};
2635

@@ -66,8 +75,13 @@ const LanguageSelector = () => {
6675
}
6776
}, [isOpen, focusedIndex]);
6877

69-
if (loading) return <p>Loading languages...</p>;
70-
if (error) return <p>Error fetching languages: {error}</p>;
78+
if (loading) {
79+
return <p>Loading languages...</p>;
80+
}
81+
82+
if (error) {
83+
return <p>Error fetching languages: {error}</p>;
84+
}
7185

7286
return (
7387
<div

‎src/components/SearchInput.tsx‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ const SearchInput = () => {
2727

2828
const clearSearch = useCallback(() => {
2929
setInputVal("");
30-
// setCategory(defaultCategory);
3130
setSearchText("");
32-
setSearchParams({});
33-
}, [setSearchParams, setSearchText]);
31+
searchParams.delete("search");
32+
setSearchParams(searchParams);
33+
}, [searchParams, setSearchParams, setSearchText]);
3434

3535
const handleEscapePress = useCallback(
3636
(e: KeyboardEvent) => {
@@ -61,15 +61,16 @@ const SearchInput = () => {
6161

6262
const formattedVal = inputVal.trim().toLowerCase();
6363

64-
// setCategory(defaultCategory);
6564
setSearchText(formattedVal);
6665
if (!formattedVal) {
67-
setSearchParams({});
66+
searchParams.delete("search");
67+
setSearchParams(searchParams);
6868
} else {
69-
setSearchParams({ search: formattedVal });
69+
searchParams.set("search", formattedVal);
70+
setSearchParams(searchParams);
7071
}
7172
},
72-
[inputVal, setSearchParams, setSearchText]
73+
[inputVal, searchParams,setSearchParams, setSearchText]
7374
);
7475

7576
useEffect(() => {

‎src/components/SnippetList.tsx‎

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,56 @@
11
import { motion, AnimatePresence, useReducedMotion } from "motion/react";
2-
import { useState } from "react";
2+
import { useEffect, useState } from "react";
3+
import { useSearchParams } from "react-router-dom";
34

45
import { useAppContext } from "@contexts/AppContext";
56
import { useSnippets } from "@hooks/useSnippets";
67
import { SnippetType } from "@types";
8+
import { slugify } from "@utils/slugify";
79

810
import { LeftAngleArrowIcon } from "./Icons";
911
import SnippetModal from "./SnippetModal";
1012

1113
const SnippetList = () => {
14+
const [searchParams, setSearchParams] = useSearchParams();
1215
const shouldReduceMotion = useReducedMotion();
1316

1417
const { language, snippet, setSnippet } = useAppContext();
1518
const { fetchedSnippets } = useSnippets();
1619

1720
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
1821

19-
if (!fetchedSnippets) {
20-
return (
21-
<div>
22-
<LeftAngleArrowIcon />
23-
</div>
24-
);
25-
}
26-
27-
const handleOpenModal = (activeSnippet: SnippetType) => {
22+
const handleOpenModal = (selected: SnippetType) => () => {
2823
setIsModalOpen(true);
29-
setSnippet(activeSnippet);
24+
setSnippet(selected);
25+
searchParams.set("snippet", slugify(selected.title));
26+
setSearchParams(searchParams);
3027
};
3128

3229
const handleCloseModal = () => {
3330
setIsModalOpen(false);
3431
setSnippet(null);
32+
searchParams.delete("snippet");
33+
setSearchParams(searchParams);
3534
};
3635

36+
/**
37+
* open the relevant modal if the snippet is in the search params
38+
*/
39+
useEffect(() => {
40+
const snippetSlug = searchParams.get("snippet");
41+
if (!snippetSlug) {
42+
return;
43+
}
44+
45+
const selectedSnippet = (fetchedSnippets ?? []).find(
46+
(item) => slugify(item.title) === snippetSlug
47+
);
48+
if (selectedSnippet) {
49+
handleOpenModal(selectedSnippet)();
50+
}
51+
// eslint-disable-next-line react-hooks/exhaustive-deps
52+
}, [fetchedSnippets, searchParams]);
53+
3754
if (!fetchedSnippets) {
3855
return (
3956
<div>
@@ -77,7 +94,7 @@ const SnippetList = () => {
7794
<motion.button
7895
className="snippet | flow"
7996
data-flow-space="sm"
80-
onClick={()=>handleOpenModal(snippet)}
97+
onClick={handleOpenModal(snippet)}
8198
whileHover={{ scale: 1.01 }}
8299
whileTap={{ scale: 0.98 }}
83100
>

‎src/contexts/AppContext.tsx‎

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
1-
import {
2-
createContext,
3-
FC,
4-
useCallback,
5-
useContext,
6-
useEffect,
7-
useState,
8-
} from "react";
1+
import { createContext, FC, useContext, useEffect, useState } from "react";
92
import { useParams } from "react-router-dom";
103

114
import { useLanguages } from "@hooks/useLanguages";
12-
import { AppState, CategoryType, LanguageType, SnippetType } from "@types";
13-
import { defaultLanguage } from "@utils/consts";
14-
import { slugify } from "@utils/slugify";
5+
import { AppState, LanguageType, SnippetType } from "@types";
6+
import { configureProfile } from "@utils/configureProfile";
157

168
// TODO: add custom loading and error handling
179
const defaultState: AppState = {
@@ -39,50 +31,22 @@ export const AppProvider: FC<{ children: React.ReactNode }> = ({
3931
const [snippet, setSnippet] = useState<SnippetType | null>(null);
4032
const [searchText, setSearchText] = useState<string>("");
4133

42-
const assignLanguage = useCallback(() => {
43-
if (fetchedLanguages.length === 0) {
44-
return;
45-
}
34+
const configure = async () => {
35+
const { language, category } = await configureProfile({
36+
languageName,
37+
categoryName,
38+
});
4639

47-
const language = fetchedLanguages.find(
48-
(lang) => slugify(lang.name) === languageName
49-
);
50-
if (!language) {
51-
setLanguage(defaultLanguage);
52-
return;
53-
}
5440
setLanguage(language);
55-
}, [fetchedLanguages, languageName]);
56-
57-
const assignCategory = useCallback(async () => {
58-
if (!language) {
59-
return;
60-
}
61-
62-
let category: CategoryType | undefined;
63-
try {
64-
const res = await fetch(`/consolidated/${slugify(language.name)}.json`);
65-
const data: CategoryType[] = await res.json();
66-
category = data.find((item) => item.name === categoryName);
67-
if (!category) {
68-
setCategory(data[0].name);
69-
return;
70-
}
71-
setCategory(category.name);
72-
} catch (_error) {
73-
// no-op
74-
}
75-
}, [language, categoryName]);
76-
77-
useEffect(() => {
78-
assignLanguage();
79-
}, [assignLanguage, languageName]);
41+
setCategory(category);
42+
};
8043

8144
useEffect(() => {
82-
assignCategory();
83-
}, [assignCategory, categoryName]);
45+
configure();
46+
// eslint-disable-next-line react-hooks/exhaustive-deps
47+
}, [fetchedLanguages]);
8448

85-
if (!language || !category) {
49+
if (language ===null|| category===null) {
8650
return <div>Loading...</div>;
8751
}
8852

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /