Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9c26b3a
searchbar without functionality
cordulaaa Jul 22, 2024
ecc4122
add faux masonry layout
smatthies Jul 6, 2024
74288f6
upgrade to remix v2
smatthies Sep 18, 2024
535f752
rollback readme change
smatthies Sep 18, 2024
1ed0fc2
fix node version to v22.2.0
smatthies Sep 18, 2024
36ab83a
add netlify plugin without edge functions
smatthies Sep 19, 2024
43e6c81
try including the whole build dir and redirects
smatthies Sep 19, 2024
52498f7
add client dir again but keep redirects
smatthies Sep 19, 2024
2798f43
rollback redirects
smatthies Sep 19, 2024
5795e6c
fix: re-add postcss config for Tailwind to work
HerrBertling Sep 24, 2024
a9bc685
fix: add viewport meta tag in root layout
HerrBertling Sep 24, 2024
8ebe78f
refactor: use Layout component in root.tsx
HerrBertling Sep 24, 2024
26a6c23
chore: add prettier config files
HerrBertling Sep 24, 2024
5771ae0
chore: run prettier across project files
HerrBertling Sep 24, 2024
8325212
chore: update packages to latest version
HerrBertling Sep 24, 2024
b951d4c
chore: update packages again, bump Node version to 22
HerrBertling Sep 24, 2024
7e3aed9
fix the fade in component
smatthies Oct 3, 2024
2bebd10
feat: add caching according to Netlify caching article
HerrBertling Oct 6, 2024
4e90ad4
refactor: stop leaking request into Contentful queries
HerrBertling Oct 6, 2024
bae8c36
refactor: improve typing on pageIds object by as const-ing it
HerrBertling Oct 6, 2024
6bdae8b
chore: bump some packages
HerrBertling Oct 6, 2024
0fac2ed
fix: stringify background image url to avoid hydration error
HerrBertling Oct 6, 2024
db7a4b1
fix: use classnames package to avoid class undefined in HTML
HerrBertling Oct 6, 2024
198114b
Revert "Feat: better caching"
HerrBertling Oct 12, 2024
5789211
add searchbar functionality
smatthies Nov 17, 2024
0abe7df
debounce input
smatthies Nov 17, 2024
8dad9e6
Adjust the styling of the searchbar
smatthies Dec 17, 2024
56de590
Merge branch 'main' into searchbar
smatthies Dec 17, 2024
eb45a9b
chore: update package-lock file
HerrBertling Dec 29, 2024
63adf16
chore: remove committed IDE files
HerrBertling Dec 29, 2024
cab6ef0
chore: run prettier
HerrBertling Dec 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ node_modules
# Local Netlify folder
.netlify
.DS_Store

# IDE

.idea
.vscode
.vs
2 changes: 2 additions & 0 deletions @types/generated/contentful.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,14 @@ export interface ICoachFields {
/** Die Coaches */

export interface ICoach extends Entry<ICoachFields> {
fields: ICoachFields;
sys: {
id: string;
type: string;
createdAt: string;
updatedAt: string;
locale: string;

contentType: {
sys: {
id: "coach";
Expand Down
2 changes: 1 addition & 1 deletion app/components/CoachList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ReactNode } from "react";
export default function CoachList({ children }: { children: ReactNode }) {
return (
<div className="bg-slate-100">
<section className="mx-auto block max-w-7xl gap-x-6 py-12 px-4 columns-sm">
<section className="mx-auto block max-w-7xl gap-x-6 px-4 columns-sm">
{children}
</section>
</div>
Expand Down
11 changes: 11 additions & 0 deletions app/components/CoachSearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import SearchBar from "./SearchBar";

function CoachSearch() {
return (
<div className="flex flex-row flex-grow w-full lg:w-10 ">
<SearchBar />
</div>
);
}

export default CoachSearch;
42 changes: 42 additions & 0 deletions app/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { ChangeEvent, useRef } from "react";

const SearchBar = () => {
const [inputValue, setInputValue] = React.useState("");
const [debouncedInputValue, setDebouncedInputValue] = React.useState("");
const inputRef = useRef<HTMLInputElement>(null);

const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
const clonedEvent = new Event(e.nativeEvent.type, e.nativeEvent);
setInputValue(e.currentTarget.value);
setTimeout(() => {
if (inputRef.current) {
inputRef.current.dispatchEvent(clonedEvent);
}
}, 1000);
};

return (
<div className="inline-flex gap-2 w-full lg:w-96">
<input
name="search"
type="text"
placeholder="Suche..."
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"
value={inputValue}
onChange={(e) => {
handleInputChange(e);
}}
></input>
{/* <button
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 "
type="button"
disabled={!searchTerm}
onClick={()=>{setSearchTerm('')}}
>
CLEAR
</button> */}
</div>
);
};
export default SearchBar;
73 changes: 43 additions & 30 deletions app/components/SpeakingTimeContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ContentBlocks from "./ContentBlocks";
import ArrowDown from "./icons/ArrowDown";
import FilterIcon from "./icons/FilterIcon";
import Spinner from "./icons/Spinner";
import CoachSearch from "./CoachSearch";
import CoachCard from "./CoachCard";
import ContentfulRichText from "./ContentfulRichText";
import type { EmailTemplate } from "~/utils/contentful";
Expand Down Expand Up @@ -52,15 +53,23 @@ export default function SpeakingTimeContent({
const submit = useSubmit();
const formRef = useRef<HTMLFormElement>(null);
const { t } = useTranslation("searchingCoach");
const [isActive, setIsActive] = useState(false);
var timeout: NodeJS.Timeout;

const handleChange = () => {
if (formRef) {
submit(formRef.current, { replace: true, preventScrollReset: true });
// 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
// to debounce the search input to not sent a request for each input .. happy for suggestions
clearTimeout(timeout);
timeout = setTimeout(
() =>
submit(formRef.current, { replace: true, preventScrollReset: true }),
300,
);
}
};
const state = useNavigation();

const [isActive, setIsActive] = useState(false);
// sort tags to ensure the "quick response" one is always the first in the array
tags.unshift(
tags.splice(
Expand All @@ -76,31 +85,31 @@ export default function SpeakingTimeContent({
return (
<div>
<ContentBlocks content={page.fields.content} locale={locale} />
<details open={true} className="mx-auto max-w-7xl py-8 px-4">
<summary
className="inline-flex cursor-pointer items-center hover:text-vsp-500"
onClick={() => setIsActive(!isActive)}
>
<FilterIcon />
<h5 className="inline-block px-4 text-xl" id="details-filter">
{t("filter.showFilter")}
</h5>
<div
className={`transition-transform hover:text-vsp-500 ${
isActive ? "rotate-180" : "rotate-0"
}`}
<Form
onChange={handleChange}
ref={formRef}
method="get"
id="filter-form"
className="bg-slate-100"
>
<details open={false} className="mx-auto max-w-7xl pt-6 px-4">
<summary
className="inline-flex cursor-pointer items-center hover:text-vsp-500"
onClick={() => setIsActive(!isActive)}
>
<ArrowDown />
</div>
</summary>
<FilterIcon />
<h5 className="inline-block px-4 text-xl" id="details-filter">
{t("filter.showFilter")}
</h5>
<div
className={`transition-transform hover:text-vsp-500 ${
isActive ? "rotate-180" : "rotate-0"
}`}
>
<ArrowDown />
</div>
</summary>

<Form
onChange={handleChange}
ref={formRef}
method="get"
id="filter-form"
className="flex flex-col gap-2"
>
<fieldset className="mt-8">
<legend className="mb-4 inline-block text-xl">
{t("filter.language")}
Expand Down Expand Up @@ -178,11 +187,15 @@ export default function SpeakingTimeContent({
</button>
</noscript>
</div>
</Form>
</details>
<div className="text-m mx-auto max-w-7xl py-4 px-4 font-semibold text-slate-700">
{coachesAmount ? `${coachesAmount} ${t("result")}` : t("noResult")}
</div>
</details>
<div className="py-6 px-4 max-w-7xl mx-auto flex justify-end lg:justify-between items-center flex-wrap">
<CoachSearch />
<div className="text-m font-semibold text-slate-700">
{coachesAmount ? `${coachesAmount} ${t("result")}` : t("noResult")}
</div>
</div>
</Form>

<div className="relative">
{state.state === "loading" && (
<div className="absolute inset-0 z-50 flex items-start justify-center bg-white bg-opacity-50">
Expand Down
Empty file.
7 changes: 7 additions & 0 deletions app/components/icons/SearchIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function SearchIcon({ classNames = "h-6 w-6" }) {
return (
<svg viewBox="-1 -3 25 30" fill="currentColor" className={classNames}>
<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>
</svg>
);
}
85 changes: 85 additions & 0 deletions app/utils/documentToSimpleString.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { expect, test } from "vitest";
import { documentContentToSimpleString } from "./documentToSimpleString";
const exampleDescription = {
data: {},
content: [
{
data: {},
content: [
{
data: {},
content: [
{
data: {},
content: [
{
data: {},
marks: [],
value: "A",
nodeType: "text",
},
],
nodeType: "paragraph",
},
],
nodeType: "list-item",
},
{
data: {},
content: [
{
data: {},
content: [
{
data: {},
marks: [],
value: "B",
nodeType: "text",
},
],
nodeType: "paragraph",
},
],
nodeType: "list-item",
},
{
data: {},
content: [
{
data: {},
content: [
{
data: {},
marks: [],
value: "C",
nodeType: "text",
},
],
nodeType: "paragraph",
},
],
nodeType: "list-item",
},
],
nodeType: "unordered-list",
},
{
data: {},
content: [
{
data: {},
marks: [],
value: "",
nodeType: "text",
},
],
nodeType: "paragraph",
},
],
nodeType: "document",
};

test("get simple string from Document object", () => {
const simpleText = documentContentToSimpleString(exampleDescription.content);
expect(simpleText).toEqual("ABC");
});
32 changes: 32 additions & 0 deletions app/utils/documentToSimpleString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// This function processes each individual node of the Rich Text document
export function documentContentToSimpleString(nodes: any) {
// Initialize an empty string to hold the result
let result = "";

// Loop through each node in the input content array
nodes.forEach((node: any) => {
// If the node type is 'text', we append its value to the result
if (node.nodeType === "text" && node.value) {
result += node.value;
}

// If the node type is a list or list-item, recurse into its content
else if (
node.nodeType === "unordered-list" ||
node.nodeType === "list-item"
) {
if (node.content) {
// Recursively process the content of the list or list-item node
result += documentContentToSimpleString(node.content);
}
}

// If the node type is paragraph, recurse into its content
else if (node.nodeType === "paragraph" && node.content) {
result += documentContentToSimpleString(node.content);
}
});

// Return the concatenated string
return result;
}
13 changes: 13 additions & 0 deletions app/utils/getSearchPageContents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "~/utils/contentful";
import pageIds from "~/utils/pageIds";
import type { ICoachtag, LOCALE_CODE } from "../../@types/generated/contentful";
import { documentContentToSimpleString } from "./documentToSimpleString";

export const getSearchPageContents = async (
request: Request,
Expand All @@ -21,6 +22,8 @@ export const getSearchPageContents = async (

const checkedGender = searchParams.getAll("gender");

const searchTerm = searchParams.getAll("search");

const [page, coaches, languages, gender, tags, navigation, emailTemplate] =
await Promise.all([
getPageById(pageIds.SEARCH_HELP, locale),
Expand Down Expand Up @@ -65,6 +68,16 @@ export const getSearchPageContents = async (
!!coachGenders &&
coachGenders.some((gender) => checkedGender.includes(gender))
);
})
.filter((coach) => {
if (searchTerm[0] && searchTerm[0] != "") {
const description = documentContentToSimpleString(
coach.fields.description?.content,
);
return `${coach.fields.name} ${description}`.includes(searchTerm[0]);
} else {
return true;
}
});

// get available tags from all coaches
Expand Down
Loading
Loading