Skip to content

Commit 298c32b

Browse files
authored
Merge pull request #272 from HerrBertling/searchbar
WIP - searchbar
2 parents 04d4f4c + cab6ef0 commit 298c32b

File tree

13 files changed

+1663
-932
lines changed

13 files changed

+1663
-932
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@ node_modules
88
# Local Netlify folder
99
.netlify
1010
.DS_Store
11+
12+
# IDE
13+
14+
.idea
15+
.vscode
16+
.vs

@types/generated/contentful.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,14 @@ export interface ICoachFields {
193193
/** Die Coaches */
194194

195195
export interface ICoach extends Entry<ICoachFields> {
196+
fields: ICoachFields;
196197
sys: {
197198
id: string;
198199
type: string;
199200
createdAt: string;
200201
updatedAt: string;
201202
locale: string;
203+
202204
contentType: {
203205
sys: {
204206
id: "coach";

app/components/CoachList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { ReactNode } from "react";
33
export default function CoachList({ children }: { children: ReactNode }) {
44
return (
55
<div className="bg-slate-100">
6-
<section className="mx-auto block max-w-7xl gap-x-6 py-12 px-4 columns-sm">
6+
<section className="mx-auto block max-w-7xl gap-x-6 px-4 columns-sm">
77
{children}
88
</section>
99
</div>

app/components/CoachSearch.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import SearchBar from "./SearchBar";
2+
3+
function CoachSearch() {
4+
return (
5+
<div className="flex flex-row flex-grow w-full lg:w-10 ">
6+
<SearchBar />
7+
</div>
8+
);
9+
}
10+
11+
export default CoachSearch;

app/components/SearchBar.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React, { ChangeEvent, useRef } from "react";
2+
3+
const SearchBar = () => {
4+
const [inputValue, setInputValue] = React.useState("");
5+
const [debouncedInputValue, setDebouncedInputValue] = React.useState("");
6+
const inputRef = useRef<HTMLInputElement>(null);
7+
8+
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
9+
e.preventDefault();
10+
const clonedEvent = new Event(e.nativeEvent.type, e.nativeEvent);
11+
setInputValue(e.currentTarget.value);
12+
setTimeout(() => {
13+
if (inputRef.current) {
14+
inputRef.current.dispatchEvent(clonedEvent);
15+
}
16+
}, 1000);
17+
};
18+
19+
return (
20+
<div className="inline-flex gap-2 w-full lg:w-96">
21+
<input
22+
name="search"
23+
type="text"
24+
placeholder="Suche..."
25+
className="text-[1rem] px-2 py-1 rounded-full hover:text-vsp-900 border border-vsp-400 active:border-vsp-900 focus:border-vsp-900 disabled:border-vsp-200 w-full"
26+
value={inputValue}
27+
onChange={(e) => {
28+
handleInputChange(e);
29+
}}
30+
></input>
31+
{/* <button
32+
className="py-2 px-2 font-inherit inline-flex items-center justify-center rounded-full text-vsp-900 hover:text-white no-underline transition-opacity duration-300 hover:bg-vsp-900 focus:opacity-90 active:opacity-90 disabled:pointer-events-none "
33+
type="button"
34+
disabled={!searchTerm}
35+
onClick={()=>{setSearchTerm('')}}
36+
>
37+
CLEAR
38+
</button> */}
39+
</div>
40+
);
41+
};
42+
export default SearchBar;

app/components/SpeakingTimeContent.tsx

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ContentBlocks from "./ContentBlocks";
1414
import ArrowDown from "./icons/ArrowDown";
1515
import FilterIcon from "./icons/FilterIcon";
1616
import Spinner from "./icons/Spinner";
17+
import CoachSearch from "./CoachSearch";
1718
import CoachCard from "./CoachCard";
1819
import ContentfulRichText from "./ContentfulRichText";
1920
import type { EmailTemplate } from "~/utils/contentful";
@@ -52,15 +53,23 @@ export default function SpeakingTimeContent({
5253
const submit = useSubmit();
5354
const formRef = useRef<HTMLFormElement>(null);
5455
const { t } = useTranslation("searchingCoach");
56+
const [isActive, setIsActive] = useState(false);
57+
var timeout: NodeJS.Timeout;
5558

5659
const handleChange = () => {
5760
if (formRef) {
58-
submit(formRef.current, { replace: true, preventScrollReset: true });
61+
// not the best solution as the tags now will timeout for 300ms as well - but with the onchange on the form I could not find another way
62+
// to debounce the search input to not sent a request for each input .. happy for suggestions
63+
clearTimeout(timeout);
64+
timeout = setTimeout(
65+
() =>
66+
submit(formRef.current, { replace: true, preventScrollReset: true }),
67+
300,
68+
);
5969
}
6070
};
6171
const state = useNavigation();
6272

63-
const [isActive, setIsActive] = useState(false);
6473
// sort tags to ensure the "quick response" one is always the first in the array
6574
tags.unshift(
6675
tags.splice(
@@ -76,31 +85,31 @@ export default function SpeakingTimeContent({
7685
return (
7786
<div>
7887
<ContentBlocks content={page.fields.content} locale={locale} />
79-
<details open={true} className="mx-auto max-w-7xl py-8 px-4">
80-
<summary
81-
className="inline-flex cursor-pointer items-center hover:text-vsp-500"
82-
onClick={() => setIsActive(!isActive)}
83-
>
84-
<FilterIcon />
85-
<h5 className="inline-block px-4 text-xl" id="details-filter">
86-
{t("filter.showFilter")}
87-
</h5>
88-
<div
89-
className={`transition-transform hover:text-vsp-500 ${
90-
isActive ? "rotate-180" : "rotate-0"
91-
}`}
88+
<Form
89+
onChange={handleChange}
90+
ref={formRef}
91+
method="get"
92+
id="filter-form"
93+
className="bg-slate-100"
94+
>
95+
<details open={false} className="mx-auto max-w-7xl pt-6 px-4">
96+
<summary
97+
className="inline-flex cursor-pointer items-center hover:text-vsp-500"
98+
onClick={() => setIsActive(!isActive)}
9299
>
93-
<ArrowDown />
94-
</div>
95-
</summary>
100+
<FilterIcon />
101+
<h5 className="inline-block px-4 text-xl" id="details-filter">
102+
{t("filter.showFilter")}
103+
</h5>
104+
<div
105+
className={`transition-transform hover:text-vsp-500 ${
106+
isActive ? "rotate-180" : "rotate-0"
107+
}`}
108+
>
109+
<ArrowDown />
110+
</div>
111+
</summary>
96112

97-
<Form
98-
onChange={handleChange}
99-
ref={formRef}
100-
method="get"
101-
id="filter-form"
102-
className="flex flex-col gap-2"
103-
>
104113
<fieldset className="mt-8">
105114
<legend className="mb-4 inline-block text-xl">
106115
{t("filter.language")}
@@ -178,11 +187,15 @@ export default function SpeakingTimeContent({
178187
</button>
179188
</noscript>
180189
</div>
181-
</Form>
182-
</details>
183-
<div className="text-m mx-auto max-w-7xl py-4 px-4 font-semibold text-slate-700">
184-
{coachesAmount ? `${coachesAmount} ${t("result")}` : t("noResult")}
185-
</div>
190+
</details>
191+
<div className="py-6 px-4 max-w-7xl mx-auto flex justify-end lg:justify-between items-center flex-wrap">
192+
<CoachSearch />
193+
<div className="text-m font-semibold text-slate-700">
194+
{coachesAmount ? `${coachesAmount} ${t("result")}` : t("noResult")}
195+
</div>
196+
</div>
197+
</Form>
198+
186199
<div className="relative">
187200
{state.state === "loading" && (
188201
<div className="absolute inset-0 z-50 flex items-start justify-center bg-white bg-opacity-50">

app/components/icons/CancelIcon.tsx

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function SearchIcon({ classNames = "h-6 w-6" }) {
2+
return (
3+
<svg viewBox="-1 -3 25 30" fill="currentColor" className={classNames}>
4+
<path d="m22.89,20.77l-8.2-8.2s0,0,0,0c.88-1.29,1.4-2.85,1.4-4.52C16.09,3.61,12.48,0,8.04,0S0,3.61,0,8.04s3.61,8.04,8.04,8.04c1.68,0,3.23-.52,4.52-1.4,0,0,0,0,0,0l8.2,8.2c.29.29.68.44,1.06.44s.77-.15,1.06-.44c.59-.59.59-1.54,0-2.12ZM2,8.04c0-3.33,2.71-6.04,6.04-6.04s6.04,2.71,6.04,6.04-2.71,6.04-6.04,6.04-6.04-2.71-6.04-6.04Z"></path>
5+
</svg>
6+
);
7+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { expect, test } from "vitest";
2+
import { documentContentToSimpleString } from "./documentToSimpleString";
3+
const exampleDescription = {
4+
data: {},
5+
content: [
6+
{
7+
data: {},
8+
content: [
9+
{
10+
data: {},
11+
content: [
12+
{
13+
data: {},
14+
content: [
15+
{
16+
data: {},
17+
marks: [],
18+
value: "A",
19+
nodeType: "text",
20+
},
21+
],
22+
nodeType: "paragraph",
23+
},
24+
],
25+
nodeType: "list-item",
26+
},
27+
{
28+
data: {},
29+
content: [
30+
{
31+
data: {},
32+
content: [
33+
{
34+
data: {},
35+
marks: [],
36+
value: "B",
37+
nodeType: "text",
38+
},
39+
],
40+
nodeType: "paragraph",
41+
},
42+
],
43+
nodeType: "list-item",
44+
},
45+
{
46+
data: {},
47+
content: [
48+
{
49+
data: {},
50+
content: [
51+
{
52+
data: {},
53+
marks: [],
54+
value: "C",
55+
nodeType: "text",
56+
},
57+
],
58+
nodeType: "paragraph",
59+
},
60+
],
61+
nodeType: "list-item",
62+
},
63+
],
64+
nodeType: "unordered-list",
65+
},
66+
{
67+
data: {},
68+
content: [
69+
{
70+
data: {},
71+
marks: [],
72+
value: "",
73+
nodeType: "text",
74+
},
75+
],
76+
nodeType: "paragraph",
77+
},
78+
],
79+
nodeType: "document",
80+
};
81+
82+
test("get simple string from Document object", () => {
83+
const simpleText = documentContentToSimpleString(exampleDescription.content);
84+
expect(simpleText).toEqual("ABC");
85+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// This function processes each individual node of the Rich Text document
2+
export function documentContentToSimpleString(nodes: any) {
3+
// Initialize an empty string to hold the result
4+
let result = "";
5+
6+
// Loop through each node in the input content array
7+
nodes.forEach((node: any) => {
8+
// If the node type is 'text', we append its value to the result
9+
if (node.nodeType === "text" && node.value) {
10+
result += node.value;
11+
}
12+
13+
// If the node type is a list or list-item, recurse into its content
14+
else if (
15+
node.nodeType === "unordered-list" ||
16+
node.nodeType === "list-item"
17+
) {
18+
if (node.content) {
19+
// Recursively process the content of the list or list-item node
20+
result += documentContentToSimpleString(node.content);
21+
}
22+
}
23+
24+
// If the node type is paragraph, recurse into its content
25+
else if (node.nodeType === "paragraph" && node.content) {
26+
result += documentContentToSimpleString(node.content);
27+
}
28+
});
29+
30+
// Return the concatenated string
31+
return result;
32+
}

0 commit comments

Comments
 (0)