73 |
74 |
81 |
82 | {
87 | setSearch(e.target.value);
88 |
89 | if (!isOpen) setIsOpen(true);
90 |
91 | if (e.target.value === "" && isOpen) setIsOpen(false);
92 | }}
93 | className="text-dark400_light700 paragraph-regular no-focus placeholder border-none bg-transparent shadow-none outline-none"
94 | />
95 |
96 |
97 | {isOpen &&
}
98 |
99 | );
100 | };
101 |
102 | export default GlobalSearch;
103 |
--------------------------------------------------------------------------------
/lib/actions/job.action.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from "fs";
2 | import path from "path";
3 |
4 | import type { GetJobsParams } from "./shared.types";
5 |
6 | let _jsearch: any;
7 | let _countries: any;
8 |
9 | export async function getJobs(params: GetJobsParams) {
10 | try {
11 | const {
12 | page = 1,
13 | pageSize = 10,
14 | filter,
15 | location,
16 | remote,
17 | wage,
18 | skills,
19 | searchQuery,
20 | } = params;
21 |
22 | // Calculate the number of jobs to skip based on the page number and page size
23 | const skipAmount = (page - 1) * pageSize;
24 |
25 | if (!_jsearch) {
26 | const file = path.join(process.cwd(), "content", "jsearch.json");
27 | const fileSync = readFileSync(file, "utf8");
28 |
29 | const jsonData = JSON.parse(fileSync);
30 |
31 | _jsearch = jsonData;
32 | }
33 |
34 | const allJobs = _jsearch.data || [];
35 |
36 | const searchQueryRegExp = new RegExp(
37 | (searchQuery || "").toLowerCase(),
38 | "i"
39 | );
40 | const locationRegExp = new RegExp((location || "").toLowerCase(), "i");
41 |
42 | const filteredJobs = allJobs.filter((job: any) => {
43 | return (
44 | job &&
45 | searchQueryRegExp.test(job.job_title) &&
46 | locationRegExp.test(job.job_country) &&
47 | (!remote || job.job_is_remote === true) &&
48 | (!wage ||
49 | (job.job_min_salary !== null && job.job_max_salary !== null)) &&
50 | (!skills || job.job_required_skills)
51 | );
52 | });
53 |
54 | let filterOptions = {
55 | job_employment_type: "",
56 | };
57 |
58 | switch (filter) {
59 | case "fulltime":
60 | filterOptions = { job_employment_type: "FULLTIME" };
61 | break;
62 | case "parttime":
63 | filterOptions = { job_employment_type: "PARTTIME" };
64 | break;
65 | case "contractor":
66 | filterOptions = { job_employment_type: "CONTRACTOR" };
67 | break;
68 | case "intern":
69 | filterOptions = { job_employment_type: "INTERN" };
70 | break;
71 | default:
72 | filterOptions = { job_employment_type: "" };
73 | break;
74 | }
75 |
76 | const data = filteredJobs
77 | .filter((job: any) =>
78 | filterOptions.job_employment_type !== ""
79 | ? job.job_employment_type === filterOptions.job_employment_type
80 | : true
81 | )
82 | .slice(skipAmount, skipAmount + pageSize);
83 |
84 | const totalJobs = allJobs.length;
85 |
86 | const isNext = totalJobs > skipAmount + data.length;
87 |
88 | return { data, isNext };
89 | } catch (error) {
90 | console.log(error);
91 | throw error;
92 | }
93 | }
94 |
95 | export async function getCountryFilters() {
96 | try {
97 | if (!_countries) {
98 | const file = path.join(process.cwd(), "content", "countries.json");
99 | const fileSync = readFileSync(file, "utf8");
100 |
101 | const jsonData = JSON.parse(fileSync);
102 |
103 | _countries = jsonData;
104 | }
105 |
106 | const result = _countries.map((country: any) => ({
107 | name: country.name,
108 | value: country.cca2,
109 | }));
110 |
111 | return result;
112 | } catch (error: any) {
113 | console.log(error);
114 | throw error;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/components/shared/LeftSidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import Image from "next/image";
5 | import { usePathname } from "next/navigation";
6 |
7 | import { SignedOut, useAuth } from "@clerk/nextjs";
8 |
9 | import { Button } from "@/components/ui/button";
10 |
11 | import { sidebarLinks } from "@/constants";
12 |
13 | const LeftSidebar = () => {
14 | const { userId } = useAuth();
15 | const pathname = usePathname();
16 |
17 | return (
18 | ;
24 | createdAt: Date;
25 | clerkId?: string | null;
26 | }
27 |
28 | const QuestionCard = ({
29 | _id,
30 | title,
31 | tags,
32 | author,
33 | upvotes,
34 | views,
35 | answers,
36 | createdAt,
37 | clerkId,
38 | }: QuestionProps) => {
39 | const showActionButtons = clerkId && clerkId === author.clerkId;
40 |
41 | return (
42 |
43 |
44 |
45 |
46 | {getTimestamp(createdAt)}
47 |
48 |
49 |
50 | {title}
51 |
52 |
53 |
54 |
55 |
56 | {showActionButtons && (
57 |
58 | )}
59 |
60 |
61 |
62 |
63 | {tags.map((tag) => (
64 |
65 | ))}
66 |
67 |
68 |
69 |
78 |
79 |
80 |
87 |
94 |
101 |
102 |
103 |
104 | );
105 | };
106 |
107 | export default QuestionCard;
108 |
--------------------------------------------------------------------------------
/app/api/webhook/clerk/route.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | import { NextResponse } from "next/server";
3 | import { headers } from "next/headers";
4 |
5 | import { Webhook } from "svix";
6 | import { WebhookEvent } from "@clerk/nextjs/server";
7 |
8 | import { createUser, deleteUser, updateUser } from "@/lib/actions/user.action";
9 |
10 | export async function POST(req: Request) {
11 | // You can find this in the Clerk Dashboard -> Webhooks -> choose the webhook
12 | const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
13 |
14 | if (!WEBHOOK_SECRET) {
15 | throw new Error(
16 | "Please add WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local"
17 | );
18 | }
19 |
20 | // Get the headers
21 | const headerPayload = headers();
22 | const svixId = headerPayload.get("svix-id");
23 | const svixTimestamp = headerPayload.get("svix-timestamp");
24 | const svixSignature = headerPayload.get("svix-signature");
25 |
26 | // If there are no headers, error out
27 | if (!svixId || !svixTimestamp || !svixSignature) {
28 | return new Response("Error occured -- no svix headers", {
29 | status: 400,
30 | });
31 | }
32 |
33 | // Get the body
34 | const payload = await req.json();
35 | const body = JSON.stringify(payload);
36 |
37 | // Create a new SVIX instance with your secret.
38 | const wh = new Webhook(WEBHOOK_SECRET);
39 |
40 | let evt: WebhookEvent;
41 |
42 | // Verify the payload with the headers
43 | try {
44 | evt = wh.verify(body, {
45 | "svix-id": svixId,
46 | "svix-timestamp": svixTimestamp,
47 | "svix-signature": svixSignature,
48 | }) as WebhookEvent;
49 | } catch (err) {
50 | console.error("Error verifying webhook:", err);
51 | return new Response("Error occured", {
52 | status: 400,
53 | });
54 | }
55 |
56 | // Get the ID and type
57 | const eventType = evt.type;
58 |
59 | if (eventType === "user.created") {
60 | const { id, email_addresses, image_url, username, first_name, last_name } =
61 | evt.data;
62 |
63 | const parts = email_addresses[0].email_address.split("@");
64 |
65 | // create a new user in database
66 | const mongoUser = await createUser({
67 | clerkId: id,
68 | name: `${first_name}${last_name ? ` ${last_name}` : ""}`,
69 | username: username || `${parts[0]}-${parts[1].split(".")[0]}`,
70 | email: email_addresses[0].email_address,
71 | picture: image_url,
72 | });
73 |
74 | console.log(mongoUser);
75 |
76 | return NextResponse.json({ message: "User created", user: mongoUser });
77 | }
78 |
79 | if (eventType === "user.updated") {
80 | const { id, email_addresses, image_url, username, first_name, last_name } =
81 | evt.data;
82 |
83 | // create a new user in database
84 | const mongoUser = await updateUser({
85 | clerkId: id,
86 | updateData: {
87 | name: `${first_name}${last_name ? ` ${last_name}` : ""}`,
88 | username: username!,
89 | email: email_addresses[0].email_address,
90 | picture: image_url,
91 | },
92 | path: `/profile/${id}`,
93 | });
94 |
95 | return NextResponse.json({ message: "User updated", user: mongoUser });
96 | }
97 |
98 | if (eventType === "user.deleted") {
99 | const { id } = evt.data;
100 |
101 | const deletedUser = await deleteUser({
102 | clerkId: id!,
103 | });
104 |
105 | return NextResponse.json({ message: "User deleted", user: deletedUser });
106 | }
107 |
108 | return new Response("", { status: 201 });
109 | }
110 |
--------------------------------------------------------------------------------
/app/(root)/jobs/page.tsx:
--------------------------------------------------------------------------------
1 | import Filter from "@/components/shared/Filter";
2 | import LocalSearchbar from "@/components/shared/search/LocalSearchbar";
3 |
4 | import JobFilters from "@/components/shared/Filters";
5 | import NoResult from "@/components/shared/NoResult";
6 | import Pagination from "@/components/shared/Pagination";
7 | import JobCard from "@/components/cards/JobCard";
8 |
9 | import { getCountryFilters, getJobs } from "@/lib/actions/job.action";
10 |
11 | import { JobPageFilters } from "@/constants/filters";
12 |
13 | import type { SearchParamsProps } from "@/types";
14 | import type { Metadata } from "next";
15 |
16 | export const metadata: Metadata = {
17 | title: "Jobs — DevOverflow",
18 | };
19 |
20 | const Page = async ({ searchParams }: SearchParamsProps) => {
21 | const CountryFilters = await getCountryFilters();
22 |
23 | const result = await getJobs({
24 | searchQuery: searchParams.q,
25 | filter: searchParams.filter,
26 | location: searchParams.location,
27 | remote: searchParams.remote,
28 | page: searchParams.page ? +searchParams.page : 1,
29 | wage: searchParams.wage,
30 | skills: searchParams.skills,
31 | });
32 |
33 | return (
34 | <>
35 | Jobs
36 |
37 |
38 |
45 | {CountryFilters && (
46 |
51 | )}
52 |
53 |
54 |
55 |
56 |
57 | {result.data.length > 0 ? (
58 | result.data.map((jobItem: any) => (
59 |
81 | ))
82 | ) : (
83 |
89 | )}
90 |
91 |
92 |
98 | >
99 | );
100 | };
101 |
102 | export default Page;
103 |
--------------------------------------------------------------------------------
/components/shared/navbar/Mobile.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import Image from "next/image";
5 | import { usePathname } from "next/navigation";
6 |
7 | import { SignedOut } from "@clerk/nextjs";
8 |
9 | import { Button } from "@/components/ui/button";
10 | import {
11 | Sheet,
12 | SheetClose,
13 | SheetContent,
14 | SheetTrigger,
15 | } from "@/components/ui/sheet";
16 |
17 | import { sidebarLinks } from "@/constants";
18 |
19 | const NavContent = () => {
20 | const pathname = usePathname();
21 |
22 | return (
23 |
24 | {sidebarLinks.map((link) => {
25 | const isActive: boolean =
26 | (pathname.includes(link.route) && link.route.length > 1) ||
27 | pathname === link.route;
28 |
29 | return (
30 |
31 |
39 |
46 |
47 | {link.label}
48 |
49 |
50 |
51 | );
52 | })}
53 |
54 | );
55 | };
56 |
57 | const Mobile = () => {
58 | return (
59 |
60 |
61 |
68 |
69 |
73 |
74 |
80 |
81 |
82 | Dev Overflow
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | Log In
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | Sign Up
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | );
113 | };
114 |
115 | export default Mobile;
116 |
--------------------------------------------------------------------------------
/constants/index.ts:
--------------------------------------------------------------------------------
1 | import type { SidebarLink, ThemeOption } from "@/types";
2 |
3 | export const themes: ThemeOption[] = [
4 | { value: "light", label: "Light", icon: "/assets/icons/sun.svg" },
5 | { value: "dark", label: "Dark", icon: "/assets/icons/moon.svg" },
6 | { value: "system", label: "System", icon: "/assets/icons/computer.svg" },
7 | ];
8 |
9 | export const sidebarLinks: SidebarLink[] = [
10 | {
11 | imgURL: "/assets/icons/home.svg",
12 | route: "/",
13 | label: "Home",
14 | },
15 | {
16 | imgURL: "/assets/icons/users.svg",
17 | route: "/community",
18 | label: "Community",
19 | },
20 | {
21 | imgURL: "/assets/icons/star.svg",
22 | route: "/collection",
23 | label: "Collections",
24 | },
25 | {
26 | imgURL: "/assets/icons/suitcase.svg",
27 | route: "/jobs",
28 | label: "Find Jobs",
29 | },
30 | {
31 | imgURL: "/assets/icons/tag.svg",
32 | route: "/tags",
33 | label: "Tags",
34 | },
35 | {
36 | imgURL: "/assets/icons/user.svg",
37 | route: "/profile",
38 | label: "Profile",
39 | },
40 | {
41 | imgURL: "/assets/icons/question.svg",
42 | route: "/ask-question",
43 | label: "Ask a question",
44 | },
45 | ];
46 |
47 | export const BADGE_CRITERIA = {
48 | QUESTION_COUNT: {
49 | BRONZE: 10,
50 | SILVER: 50,
51 | GOLD: 100,
52 | },
53 | ANSWER_COUNT: {
54 | BRONZE: 10,
55 | SILVER: 50,
56 | GOLD: 100,
57 | },
58 | QUESTION_UPVOTES: {
59 | BRONZE: 10,
60 | SILVER: 50,
61 | GOLD: 100,
62 | },
63 | ANSWER_UPVOTES: {
64 | BRONZE: 10,
65 | SILVER: 50,
66 | GOLD: 100,
67 | },
68 | TOTAL_VIEWS: {
69 | BRONZE: 1000,
70 | SILVER: 10000,
71 | GOLD: 100000,
72 | },
73 | };
74 |
75 | export const CURRENCY_NOTATIONS: { [key: string]: string } = {
76 | ALL: "Lek",
77 | AFN: "؋",
78 | ARS: "$",
79 | AWG: "ƒ",
80 | AUD: "$",
81 | AZN: "ман",
82 | BSD: "$",
83 | BYR: "p.",
84 | BZD: "BZ$",
85 | BMD: "$",
86 | BOB: "$b",
87 | BAM: "KM",
88 | BWP: "P",
89 | BGN: "лв",
90 | BRL: "R$",
91 | BND: "$",
92 | KHR: "៛",
93 | CAD: "$",
94 | KYD: "$",
95 | CLP: "$",
96 | CNY: "¥",
97 | COP: "$",
98 | CRC: "₡",
99 | HRK: "kn",
100 | CUP: "₱",
101 | CZK: "Kč",
102 | DKK: "kr",
103 | DOP: "RD$",
104 | XCD: "$",
105 | EGP: "£",
106 | SVC: "$",
107 | EEK: "kr",
108 | EUR: "€",
109 | FKP: "£",
110 | FJD: "$",
111 | GHC: "¢",
112 | GIP: "£",
113 | GTQ: "Q",
114 | GGP: "£",
115 | GYD: "$",
116 | HNL: "L",
117 | HKD: "$",
118 | HUF: "Ft",
119 | ISK: "kr",
120 | INR: "₹",
121 | IDR: "Rp",
122 | IRR: "﷼",
123 | IMP: "£",
124 | ILS: "₪",
125 | JMD: "J$",
126 | JPY: "¥",
127 | JEP: "£",
128 | KZT: "лв",
129 | KPW: "₩",
130 | KRW: "₩",
131 | KGS: "лв",
132 | LAK: "₭",
133 | LVL: "Ls",
134 | LBP: "£",
135 | LRD: "$",
136 | LTL: "Lt",
137 | MKD: "ден",
138 | MYR: "RM",
139 | MUR: "₨",
140 | MXN: "$",
141 | MNT: "₮",
142 | MZN: "MT",
143 | NAD: "$",
144 | NPR: "₨",
145 | ANG: "ƒ",
146 | NZD: "$",
147 | NIO: "C$",
148 | NGN: "₦",
149 | NOK: "kr",
150 | OMR: "﷼",
151 | PKR: "₨",
152 | PAB: "B/.",
153 | PYG: "Gs",
154 | PEN: "S/.",
155 | PHP: "₱",
156 | PLN: "zł",
157 | QAR: "﷼",
158 | RON: "lei",
159 | RUB: "руб",
160 | SHP: "£",
161 | SAR: "﷼",
162 | RSD: "Дин.",
163 | SCR: "₨",
164 | SGD: "$",
165 | SBD: "$",
166 | SOS: "S",
167 | ZAR: "R",
168 | LKR: "₨",
169 | SEK: "kr",
170 | CHF: "CHF",
171 | SRD: "$",
172 | SYP: "£",
173 | TWD: "NT$",
174 | THB: "฿",
175 | TTD: "$",
176 | TRY: "₤",
177 | TRL: "₤",
178 | TVD: "$",
179 | UAH: "₴",
180 | GBP: "£",
181 | USD: "$",
182 | UYU: "$U",
183 | UZS: "лв",
184 | VEF: "Bs",
185 | VND: "₫",
186 | YER: "﷼",
187 | ZWD: "Z$",
188 | };
189 |
--------------------------------------------------------------------------------
/components/shared/AllAnswers.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import Image from "next/image";
3 |
4 | import { SignedIn } from "@clerk/nextjs";
5 |
6 | import Filter from "@/components/shared/Filter";
7 | import ParseHTML from "@/components/shared/ParseHTML";
8 | import Votes from "@/components/shared/Votes";
9 | import Pagination from "@/components/shared/Pagination";
10 | import EditDeleteAction from "@/components/shared/EditDeleteAction";
11 |
12 | import { getAnswers } from "@/lib/actions/answer.action";
13 | import { getTimestamp } from "@/lib/utils";
14 |
15 | import { AnswerFilters } from "@/constants/filters";
16 |
17 | import type {
18 | QuestionId,
19 | UserId,
20 | OptionalPage,
21 | OptionalFilter,
22 | } from "@/lib/actions/shared.types";
23 |
24 | interface Props extends QuestionId, UserId, OptionalPage, OptionalFilter {
25 | totalAnswers: number;
26 | }
27 |
28 | const AllAnswers = async ({
29 | userId,
30 | questionId,
31 | totalAnswers,
32 | filter,
33 | page,
34 | }: Props) => {
35 | const result = await getAnswers({
36 | questionId,
37 | sortBy: filter,
38 | page,
39 | });
40 |
41 | return (
42 |
43 |
44 |
{totalAnswers} Answers
45 |
46 |
47 |
48 | {result.answers.map((answer: any) => {
49 | const showActionButtons =
50 | JSON.stringify(userId) === JSON.stringify(answer.author._id);
51 |
52 | return (
53 |
54 |
55 |
59 |
66 |
67 |
68 | {answer.author.name}
69 |
70 |
71 | • answered
72 | {getTimestamp(answer.createdAt)}
73 |
74 |
75 |
76 |
77 |
86 |
87 |
88 |
89 |
90 |
91 | {showActionButtons && (
92 |
96 | )}
97 |
98 |
99 | );
100 | })}
101 |
102 |
103 |
106 |
107 | );
108 | };
109 |
110 | export default AllAnswers;
111 |
--------------------------------------------------------------------------------
/app/(root)/(home)/page.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | import { auth } from "@clerk/nextjs";
4 |
5 | import { Button } from "@/components/ui/button";
6 | import LocalSearchbar from "@/components/shared/search/LocalSearchbar";
7 | import Filter from "@/components/shared/Filter";
8 | import NoResult from "@/components/shared/NoResult";
9 | import Pagination from "@/components/shared/Pagination";
10 | import HomeFilters from "@/components/shared/Filters";
11 | import QuestionCard from "@/components/cards/QuestionCard";
12 |
13 | import {
14 | getQuestions,
15 | getRecommendedQuestions,
16 | } from "@/lib/actions/question.action";
17 |
18 | import { HomePageFilters } from "@/constants/filters";
19 |
20 | import type { SearchParamsProps } from "@/types";
21 | import type { Metadata } from "next";
22 |
23 | export const metadata: Metadata = {
24 | title: "Home — DevOverflow",
25 | };
26 |
27 | export default async function Home({ searchParams }: SearchParamsProps) {
28 | const { userId: clerkId } = auth();
29 |
30 | let result;
31 |
32 | if (searchParams?.filter === "recommended") {
33 | if (clerkId) {
34 | result = await getRecommendedQuestions({
35 | userId: clerkId,
36 | searchQuery: searchParams.q,
37 | page: searchParams.page ? +searchParams.page : 1,
38 | });
39 | } else {
40 | result = {
41 | questions: [],
42 | isNext: false,
43 | };
44 | }
45 | } else {
46 | result = await getQuestions({
47 | searchQuery: searchParams.q,
48 | filter: searchParams.filter,
49 | page: searchParams.page ? +searchParams.page : 1,
50 | });
51 | }
52 |
53 | return (
54 | <>
55 |
56 |
All Questions
57 |
58 |
59 |
60 | Ask a Question
61 |
62 |
63 |
64 |
65 |
66 |
73 |
74 |
79 |
80 |
81 |
82 |
83 |
84 | {result.questions.length > 0 ? (
85 | result.questions.map((question: any) => (
86 |
98 | ))
99 | ) : (
100 |
108 | )}
109 |
110 |
111 |
117 | >
118 | );
119 | }
120 |
--------------------------------------------------------------------------------
/components/shared/search/GlobalResult.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 | import Link from "next/link";
5 | import Image from "next/image";
6 | import { useSearchParams } from "next/navigation";
7 |
8 | import { ReloadIcon } from "@radix-ui/react-icons";
9 |
10 | import GlobalFilters from "@/components/shared/search/GlobalFilters";
11 |
12 | import { globalSearch } from "@/lib/actions/general.action";
13 |
14 | const GlobalResult = () => {
15 | const searchParams = useSearchParams();
16 |
17 | const [isLoading, setIsLoading] = useState(false);
18 | const [result, setResult] = useState([]);
19 |
20 | const global = searchParams.get("global");
21 | const type = searchParams.get("type");
22 |
23 | useEffect(() => {
24 | const fetchResult = async () => {
25 | setResult([]);
26 | setIsLoading(true);
27 |
28 | try {
29 | const response = await globalSearch({ query: global, type });
30 |
31 | setResult(JSON.parse(response));
32 | } catch (error) {
33 | console.error(error);
34 | throw error;
35 | } finally {
36 | setIsLoading(false);
37 | }
38 | };
39 |
40 | if (global) {
41 | fetchResult();
42 | }
43 | }, [global, type]);
44 |
45 | const renderLink = (type: string, id: string) => {
46 | switch (type) {
47 | case "question":
48 | case "answer":
49 | return `/question/${Array.isArray(id) ? id[0] : id}`;
50 | // case "answer":
51 | // return `/question/${id[0]}#${id[1]}`;
52 | case "user":
53 | return `/profile/${id}`;
54 | case "tag":
55 | return `/tags/${id}`;
56 | default:
57 | return "/";
58 | }
59 | };
60 |
61 | return (
62 |
63 |
64 |
65 |
66 |
67 | Top Match
68 |
69 |
70 |
71 | {isLoading ? (
72 |
73 |
74 |
75 | Browsing the entire database...
76 |
77 |
78 | ) : (
79 |
80 | {result.length > 0 ? (
81 | result.map((item: any, index: number) => (
82 |
87 |
94 |
95 |
96 |
97 | {item.title}
98 |
99 |
100 | {item.type}
101 |
102 |
103 |
104 | ))
105 | ) : (
106 |
107 |
108 |
109 | No results found.
110 |
111 |
112 |
113 | )}
114 |
115 | )}
116 |
117 | );
118 | };
119 |
120 | export default GlobalResult;
121 |
--------------------------------------------------------------------------------