Skip to content

Commit 5affc53

Browse files
committed
Update job board page to sync with realtime data
1 parent 18ac449 commit 5affc53

File tree

4 files changed

+331
-270
lines changed

4 files changed

+331
-270
lines changed

src/components/JobSearch.js

Lines changed: 0 additions & 174 deletions
This file was deleted.

src/components/JobSearch.tsx

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import React from "react";
2+
import { useQueryClient, useQuery } from "@tanstack/react-query";
3+
import styled from "styled-components";
4+
5+
import { Button, FocusBoundary, Form, Link, Offer } from "@components";
6+
import { toQueryString } from "@utils/toQueryString";
7+
8+
const ResultsFooter = styled.div`
9+
margin: 8rem 2rem;
10+
text-align: center;
11+
12+
@media (min-width: 600px) {
13+
margin: 8rem;
14+
}
15+
16+
> button {
17+
margin: 0;
18+
}
19+
`;
20+
21+
const fields = [
22+
{
23+
name: "type",
24+
type: "select",
25+
value: "hiring",
26+
options: [
27+
{ value: "hiring", label: "I'm looking for a job" },
28+
{ value: "forhire", label: "I'm looking for an employee" },
29+
],
30+
},
31+
{
32+
name: "limit",
33+
type: "select",
34+
value: "10",
35+
options: [
36+
{ value: "10", label: "load 10 results at a time" },
37+
{ value: "25", label: "load 25 results at a time" },
38+
{ value: "50", label: "load 50 results at a time" },
39+
],
40+
},
41+
{
42+
label: "Part time role",
43+
name: "part time",
44+
type: "checkbox",
45+
value: false,
46+
},
47+
{
48+
label: "Full time role",
49+
name: "full time",
50+
type: "checkbox",
51+
value: false,
52+
},
53+
{
54+
label: "Employer helps with visa",
55+
name: "visa",
56+
type: "checkbox",
57+
value: false,
58+
},
59+
{
60+
label: "Accepts remote candidates",
61+
name: "remote",
62+
type: "checkbox",
63+
value: false,
64+
},
65+
{
66+
label: "Offer is an internship / no exp. required",
67+
name: "intern",
68+
type: "checkbox",
69+
value: false,
70+
},
71+
];
72+
73+
interface JobPostQuery {
74+
count: number;
75+
page: number;
76+
pages: number;
77+
limit: number;
78+
data: {
79+
tags: string[];
80+
type: "hiring" | "forhire";
81+
createdAt: string;
82+
description: string;
83+
messageLink: string;
84+
reactions: [string, number][];
85+
author: {
86+
username: string;
87+
displayName: string;
88+
avatar: string;
89+
};
90+
}[];
91+
}
92+
93+
const toQueryStringArray = (
94+
tags: Record<string, boolean>,
95+
queryValue: string,
96+
): string =>
97+
Object.entries(tags)
98+
.filter(([k, v]) => v)
99+
.map(([k]) => `${queryValue}[]=${k}`)
100+
.join("&");
101+
102+
const JobSearch = ({
103+
toggleModal,
104+
setSidebar,
105+
}: {
106+
toggleModal: () => void;
107+
setSidebar: (open: boolean) => void;
108+
}) => {
109+
const debounceTimer = React.useRef();
110+
const [state, setState] = React.useState({
111+
type: "hiring",
112+
limit: 10,
113+
page: 1,
114+
"part time": false,
115+
"full time": false,
116+
visa: false,
117+
remote: false,
118+
intern: false,
119+
});
120+
const {
121+
type,
122+
page,
123+
limit,
124+
visa,
125+
remote,
126+
intern,
127+
"full time": ft,
128+
"part time": pt,
129+
} = state;
130+
const { status, data, error } = useQuery({
131+
queryKey: ["jobPosts", type, page, limit, visa, remote, intern, ft, pt],
132+
133+
queryFn: async ({ pageParam }): Promise<JobPostQuery> => {
134+
const requiredTags = toQueryStringArray(
135+
{
136+
visa,
137+
remote,
138+
intern,
139+
fulltime: ft,
140+
parttime: pt,
141+
},
142+
"requiredTags",
143+
);
144+
console.log({ type, requiredTags });
145+
const url = `/api/jobs/${type}?${toQueryString({
146+
limit,
147+
page: pageParam,
148+
})}${requiredTags ? `&${requiredTags}` : ""}`;
149+
const res = await fetch(url);
150+
return await res.json();
151+
},
152+
});
153+
const onChangeQuery = React.useCallback((query) => {
154+
console.log({ query });
155+
setState((_state) => ({
156+
..._state,
157+
...query,
158+
limit: parseInt(query.limit, 10),
159+
}));
160+
}, []);
161+
162+
const onClickNext = () => {
163+
if (page < (data?.pages || Infinity)) {
164+
setState((_s) => ({
165+
..._s,
166+
page: _s.page + 1,
167+
}));
168+
}
169+
};
170+
171+
return (
172+
<>
173+
{/* @ts-expect-error */}
174+
<FocusBoundary onChange={setSidebar}>
175+
{/* @ts-expect-error */}
176+
<Form allowSubmit={false} fields={fields} onChange={onChangeQuery} />
177+
</FocusBoundary>
178+
<div>
179+
{status === "success" &&
180+
data?.data.map((result, i) => (
181+
<Offer
182+
key={result?.createdAt}
183+
description={result.description}
184+
tags={result.tags}
185+
author={result.author}
186+
createdAt={result.createdAt}
187+
reactions={result.reactions}
188+
messageLink={result.messageLink}
189+
onClickGetInTouch={toggleModal}
190+
/>
191+
))}
192+
<ResultsFooter>
193+
{status === "pending" ? (
194+
<p>Fetching posts...</p>
195+
) : (
196+
<Button onClick={onClickNext}>Fetch More Results</Button>
197+
)}
198+
{status === "error" ? (
199+
<p>
200+
Something went wrong - please try again! If this problem persists,
201+
please <Link href="/contact/">let us know</Link>.
202+
</p>
203+
) : status === "success" && data?.data.length === 0 ? (
204+
<p>
205+
We couldn’t find any results for your query - try something
206+
different!
207+
</p>
208+
) : null}
209+
</ResultsFooter>
210+
</div>
211+
</>
212+
);
213+
};
214+
215+
export default JobSearch;

0 commit comments

Comments
 (0)