Skip to content

Commit

Permalink
feat: implement search API and results page query
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeagleson committed Apr 11, 2022
1 parent 3c4cf38 commit f7321a2
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 70 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,29 @@ About.getLayout = (page) => {
};
```

Then update `_app.tsx` as follows:

`pages/_app.tsx`

```tsx
import type { AppProps } from 'next/app';
import './globals.css';
import { NextPageWithLayout } from './page';

interface AppPropsWithLayout extends AppProps {
Component: NextPageWithLayout;
}

function MyApp({ Component, pageProps }: AppPropsWithLayout) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page);

return getLayout(<Component {...pageProps} />);
}

export default MyApp;
```

Finally, in the `mocks` files I have updated `PrimaryLayout.mocks.ts` to use `children: '{{component}}'` as a placeholder value to show in Storybook where a component would go, and I have removed the mock props in `SidebarLayout.mocks.ts` (though I do not remove the file, so I have the interface ready to go in case I ever need to add props).

I have also changed the story titles from `templates/...` to `layouts/...`.
Expand Down
2 changes: 1 addition & 1 deletion components/navigation/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Header: React.FC<IHeader> = ({ className, ...headerProps }) => {
>
<div className="space-x-5 m-5">
<Link href="/">
<a className="hover:underline">About</a>
<a className="hover:underline">Home</a>
</Link>
<Link href="/">
<a className="hover:underline">Store</a>
Expand Down
7 changes: 2 additions & 5 deletions components/utility/search-result/SearchResult.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import Link from 'next/link';
import { ISearchData } from '../../../lib/search/types';

export interface ISearchResult extends React.ComponentPropsWithoutRef<'div'> {
url: string;
title: string;
text: string;
}
export type ISearchResult = ISearchData & React.ComponentPropsWithoutRef<'div'>;

const SearchResult: React.FC<ISearchResult> = ({
url,
Expand Down
11 changes: 8 additions & 3 deletions components/utility/search/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { useRouter } from 'next/router';
import { useState } from 'react';

export interface ISearch {}

const Search: React.FC<ISearch> = () => {
const [searchTerm, setSearchTerm] = useState<string>();
const router = useRouter();
const [searchTerm, setSearchTerm] = useState<string>('');

return (
<form
className="flex flex-col items-center gap-y-5"
onSubmit={(e) => {
e.preventDefault();
alert(`Action requested. Search for term: ${searchTerm}`);
router.push(`/results?search=${searchTerm}`);
}}
>
<input
Expand All @@ -23,7 +25,10 @@ const Search: React.FC<ISearch> = () => {
<button type="submit" className="btn-primary">
Google Search
</button>
<button type="submit" className="btn-primary">
<button
onClick={() => alert('FEATURE COMING SOON!')}
className="btn-primary"
>
I&apos;m Feeling Lucky
</button>
</div>
Expand Down
27 changes: 27 additions & 0 deletions lib/search/database.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[
{
"url": "https://en.wikipedia.org/wiki/Cat",
"title": "This is a link to a search result about cats",
"text": "Did you know their whiskers can sense vibrations in the air? Description of the search result. The description might be a bit long and it will tell you everything you need to know about the search result."
},
{
"url": "https://en.wikipedia.org/wiki/Dog",
"title": "This is a link to a search result about dogs",
"text": "They sure do love to bark. Description of the search result. The description might be a bit long and it will tell you everything you need to know about the search result."
},
{
"url": "https://en.wikipedia.org/wiki/Cats_%26_Dogs",
"title": "This is a link to a search result about both cats and dogs",
"text": "Both of them have tails. Description of the search result. The description might be a bit long and it will tell you everything you need to know about the search result."
},
{
"url": "https://en.wikipedia.org/wiki/Broccoli",
"title": "This is a link to a search result about broccoli",
"text": "Broccoli was invented by crossing cauliflower with pea seeds. Description of the search result. The description might be a bit long and it will tell you everything you need to know about the search result."
},
{
"url": "https://en.wikipedia.org/wiki/Cauliflower",
"title": "This is a link to a search result about cauliflower",
"text": "Who invented cauliflower? Description of the search result. The description might be a bit long and it will tell you everything you need to know about the search result."
}
]
5 changes: 5 additions & 0 deletions lib/search/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface ISearchData {
url: string;
title: string;
text: string;
}
39 changes: 0 additions & 39 deletions pages/api/auth/[...nextauth].ts

This file was deleted.

13 changes: 0 additions & 13 deletions pages/api/hello.ts

This file was deleted.

34 changes: 34 additions & 0 deletions pages/api/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import database from '../../../lib/search/database.json';
import { ISearchData } from '../../../lib/search/types';

interface IApiSearchRequest extends NextApiRequest {
body: { searchTerm?: string };
}

export type IApiSearchResponseData = ISearchData[];

export default function handler(
req: IApiSearchRequest,
res: NextApiResponse<IApiSearchResponseData>
) {
const {
body: { searchTerm },
} = req;

if (req.method === 'POST' && searchTerm && searchTerm.length > 0) {
// Creates a regex search pattern for a case insensitive match from the user's search term
const searchPattern = new RegExp(searchTerm, 'i');

const filteredResults = database.filter((result) => {
return (
// Check the user's search term again either the title or the text of the database entry
searchPattern.test(result.title) || searchPattern.test(result.text)
);
});
res.status(200).json(filteredResults);
} else {
res.status(400).json([]);
}
}
58 changes: 49 additions & 9 deletions pages/results/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,57 @@
import { GetServerSideProps } from 'next';
import PrimaryLayout from '../../components/layouts/primary/PrimaryLayout';
import SearchResult from '../../components/utility/search-result/SearchResult';
import { mockSearchResultProps } from '../../components/utility/search-result/SearchResult.mocks';
import { ISearchData } from '../../lib/search/types';
import { IApiSearchResponseData } from '../api/search';
import { NextPageWithLayout } from '../page';

const Results: NextPageWithLayout = () => {
export interface IResults {
searchResults: ISearchData[];
}

export const getServerSideProps: GetServerSideProps<IResults> = async ({
query,
}) => {
let searchResults: IApiSearchResponseData = [];
const searchTerm = query.search;

if (searchTerm && searchTerm.length > 0) {
const response = await fetch(`http://localhost:3000/api/search`, {
body: JSON.stringify({ searchTerm }),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});

searchResults = await response.json();
}

return {
props: {
// Will be passed to the page component as props
searchResults,
},
};
};

const Results: NextPageWithLayout<IResults> = ({ searchResults }) => {
const hasResults = searchResults.length > 0;

return (
<section className="flex flex-col items-center gap-y-5">
<div className={`flex flex-col space-y-8`}>
{[...new Array(6)].map((_, idx) => {
return <SearchResult key={idx} {...mockSearchResultProps.base} />;
})}
</div>
</section>
<>
<section className="flex flex-col items-center gap-y-5">
{hasResults ? (
<div className={`flex flex-col space-y-8`}>
{searchResults.map((result, idx) => {
return <SearchResult key={idx} {...result} />;
})}
</div>
) : (
<p>No results found.</p>
)}
</section>
</>
);
};

Expand Down

0 comments on commit f7321a2

Please sign in to comment.