diff --git a/.gitignore b/.gitignore
index 8bf0160c..ba44e4d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,4 +39,5 @@ next-env.d.ts
pnpm-lock.yaml
bun.lockb
package-lock.json
-yarn.lock
\ No newline at end of file
+yarn.lock
+
diff --git a/package.json b/package.json
index 940ac277..64ba50e1 100644
--- a/package.json
+++ b/package.json
@@ -24,20 +24,22 @@
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
- "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.0",
+ "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0",
+ "dayjs": "^1.11.13",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
+ "@types/lodash": "^4.17.7",
"@uidotdev/usehooks": "^2.4.1",
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
- "dayjs": "^1.11.13",
+ "lodash": "^4.17.21",
"lucide-react": "^0.426.0",
"next": "14.2.5",
"next-auth": "^4.24.7",
diff --git a/src/actions/job.action.ts b/src/actions/job.action.ts
index 7d5ae5dd..bdfbb921 100644
--- a/src/actions/job.action.ts
+++ b/src/actions/job.action.ts
@@ -15,7 +15,6 @@ import {
import { getJobFilters } from '@/services/jobs.services';
import { ServerActionReturnType } from '@/types/api.types';
import { getAllJobsAdditonalType, getJobType } from '@/types/jobs.types';
-import { redirect } from 'next/navigation';
type additional = {
isVerifiedJob: boolean;
@@ -132,20 +131,3 @@ export const getJobById = withServerActionAsyncCatcher<
job,
}).serialize();
});
-
-export const jobFilterQuery = async (
- queries: JobQuerySchemaType,
- baseUrl: string
-) => {
- const { page, sortby, location, salaryrange, search, workmode } =
- JobQuerySchema.parse(queries);
- const searchParams = new URLSearchParams({
- page: page.toString(),
- sortby,
- ...(search && { search: search.trim() }),
- });
- location?.map((location) => searchParams.append('location', location));
- salaryrange?.map((range) => searchParams.append('salaryrange', range));
- workmode?.map((mode) => searchParams.append('workmode', mode));
- redirect(`${baseUrl}?${searchParams.toString()}`);
-};
diff --git a/src/app/jobs/page.tsx b/src/app/jobs/page.tsx
index 44a3290d..ee41d31e 100644
--- a/src/app/jobs/page.tsx
+++ b/src/app/jobs/page.tsx
@@ -1,36 +1,35 @@
import AllJobs from '@/components/all-jobs';
import Loader from '@/components/loader';
-import APP_PATHS from '@/config/path.config';
import JobFilters from '@/layouts/job-filters';
import JobsHeader from '@/layouts/jobs-header';
import {
JobQuerySchema,
JobQuerySchemaType,
} from '@/lib/validators/jobs.validator';
+import { redirect } from 'next/navigation';
import { Suspense } from 'react';
const page = async ({ searchParams }: { searchParams: JobQuerySchemaType }) => {
- const validatedSearchParams = JobQuerySchema.parse(searchParams);
-
+ const parsedData = JobQuerySchema.safeParse(searchParams);
+ if (!(parsedData.success && parsedData.data)) {
+ console.error(parsedData.error);
+ redirect('/jobs');
+ }
+ const parsedSearchParams = parsedData.data;
return (
diff --git a/src/components/job-landing.tsx b/src/components/job-landing.tsx
index e986c8a9..5f25702c 100644
--- a/src/components/job-landing.tsx
+++ b/src/components/job-landing.tsx
@@ -31,7 +31,7 @@ export const JobLanding = async ({
return (
-
+
}>
diff --git a/src/components/pagination-client.tsx b/src/components/pagination-client.tsx
index c90f56c5..b8571c48 100644
--- a/src/components/pagination-client.tsx
+++ b/src/components/pagination-client.tsx
@@ -1,28 +1,23 @@
'use client';
-import { jobFilterQuery } from '@/actions/job.action';
import { PaginationNext, PaginationPrevious } from './ui/pagination';
import { JobQuerySchemaType } from '@/lib/validators/jobs.validator';
+import useSetQueryParams from '@/hooks/useSetQueryParams';
const PAGE_INCREMENT = 1;
const PaginationPreviousButton = ({
- searchParams,
currentPage,
- baseUrl,
}: {
searchParams: JobQuerySchemaType;
currentPage: number;
baseUrl: string;
}) => {
+ const setQueryParams = useSetQueryParams();
return (
- jobFilterQuery(
- {
- ...searchParams,
- page: currentPage - PAGE_INCREMENT,
- },
- baseUrl
- )
+ setQueryParams({
+ page: (currentPage - PAGE_INCREMENT).toString(),
+ })
}
aria-disabled={currentPage - PAGE_INCREMENT < PAGE_INCREMENT}
role="button"
@@ -31,27 +26,22 @@ const PaginationPreviousButton = ({
);
};
const PaginationNextButton = ({
- searchParams,
currentPage,
totalPages,
- baseUrl,
}: {
searchParams: JobQuerySchemaType;
currentPage: number;
totalPages: number;
baseUrl: string;
}) => {
+ const setQueryParams = useSetQueryParams();
return (
- jobFilterQuery(
- {
- ...searchParams,
- page: currentPage + PAGE_INCREMENT,
- },
- baseUrl
- )
+ setQueryParams({
+ page: (currentPage + PAGE_INCREMENT).toString(),
+ })
}
aria-disabled={currentPage > totalPages - PAGE_INCREMENT}
className="aria-disabled:pointer-events-none dark:bg-neutral-900 rounded-full bg-neutral-100"
diff --git a/src/components/ui/paginator.tsx b/src/components/ui/paginator.tsx
index b2aa6446..604a6414 100644
--- a/src/components/ui/paginator.tsx
+++ b/src/components/ui/paginator.tsx
@@ -1,26 +1,25 @@
'use client';
-import { jobFilterQuery } from '@/actions/job.action';
import { JobQuerySchemaType } from '@/lib/validators/jobs.validator';
import {
PaginationEllipsis,
PaginationItem,
PaginationLink,
} from './pagination';
+import useSetQueryParams from '@/hooks/useSetQueryParams';
import { cn } from '@/lib/utils';
export const PaginationPages = ({
currentPage,
totalPages,
- searchParams,
- baseUrl,
}: {
currentPage: number;
totalPages: number;
searchParams: JobQuerySchemaType;
baseUrl: string;
}) => {
+ const setQueryParams = useSetQueryParams();
function paginationHandler(page: number) {
- jobFilterQuery({ ...searchParams, page: page }, baseUrl);
+ setQueryParams({ page: page.toString() });
}
const pages: JSX.Element[] = [];
if (totalPages <= 5) {
diff --git a/src/hooks/useSetQueryParams.ts b/src/hooks/useSetQueryParams.ts
new file mode 100644
index 00000000..06f5b1da
--- /dev/null
+++ b/src/hooks/useSetQueryParams.ts
@@ -0,0 +1,29 @@
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import _ from 'lodash';
+import { useCallback } from 'react';
+import { debounce } from 'lodash';
+
+//pass in key value pairs to update query params
+export default function useSetQueryParams() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const pathName = usePathname();
+
+ const updateQueryParams = useCallback(
+ debounce((params) => {
+ const newSearchParams = new URLSearchParams(searchParams?.toString());
+ for (const [key, value] of Object.entries(params)) {
+ //isEmpty reads number as empty too
+ if (_.isEmpty(value) && typeof value !== 'number') {
+ newSearchParams.delete(key);
+ } else {
+ newSearchParams.set(key, String(value));
+ }
+ }
+ router.push(`${pathName}?${newSearchParams}`, { scroll: false });
+ }, 300), // 300ms debounce
+ [router, searchParams, pathName]
+ );
+
+ return updateQueryParams;
+}
diff --git a/src/layouts/job-filters.tsx b/src/layouts/job-filters.tsx
index a2784617..52f0675a 100644
--- a/src/layouts/job-filters.tsx
+++ b/src/layouts/job-filters.tsx
@@ -1,5 +1,4 @@
'use client';
-import { jobFilterQuery } from '@/actions/job.action';
import { filters, WorkModeEnums } from '@/lib/constant/jobs.constant';
import {
JobQuerySchema,
@@ -24,55 +23,38 @@ import {
} from '../components/ui/form';
import { Separator } from '../components/ui/separator';
import { ScrollArea } from '@/components/ui/scroll-area';
-import { cn, formatFilterSearchParams } from '@/lib/utils';
-import { usePathname } from 'next/navigation';
-import APP_PATHS from '@/config/path.config';
+import { cn } from '@/lib/utils';
+import useSetQueryParams from '@/hooks/useSetQueryParams';
+import { useEffect } from 'react';
-const JobFilters = ({
- searchParams,
- baseUrl,
-}: {
- searchParams: JobQuerySchemaType;
- baseUrl: string;
-}) => {
- const pathname = usePathname();
- const isHome = pathname === APP_PATHS.HOME;
+const JobFilters = ({ searchParams }: { searchParams: JobQuerySchemaType }) => {
+ const setQueryParams = useSetQueryParams();
const form = useForm({
resolver: zodResolver(JobQuerySchema),
defaultValues: {
- workmode:
- searchParams.workmode &&
- (formatFilterSearchParams(searchParams.workmode) as WorkModeEnums[]),
- salaryrange:
- searchParams.salaryrange &&
- formatFilterSearchParams(searchParams.salaryrange),
- location:
- searchParams.location &&
- formatFilterSearchParams(searchParams.location),
+ workmode: searchParams.workmode,
+ salaryrange: searchParams.salaryrange,
+ location: searchParams.location,
},
});
- async function handleFormSubmit(data: JobQuerySchemaType) {
- await jobFilterQuery(
- {
- ...data,
- search: searchParams.search,
- sortby: searchParams.sortby,
- },
- baseUrl
- );
- }
+
+ const formValues = form.watch();
+
+ useEffect(() => {
+ if (formValues) {
+ setQueryParams(formValues);
+ }
+ }, [formValues, setQueryParams, searchParams]);
+
return (
-