-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(frontend): add query params to blog page
- Loading branch information
Showing
18 changed files
with
394 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { posts } from '@frontend/test/__mocks__/post'; | ||
import render from '@frontend/test/render'; | ||
import { screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { | ||
ReadonlyURLSearchParams, | ||
useRouter, | ||
useSearchParams, | ||
} from 'next/navigation'; | ||
import PostsClient from './page.client'; | ||
|
||
jest.mock('next/navigation'); | ||
|
||
const mockUseSearchParams = jest.mocked(useSearchParams); | ||
const mockUseRouter = jest.mocked(useRouter); | ||
|
||
describe('PostsClient', () => { | ||
test('renders posts', async () => { | ||
mockUseSearchParams.mockReturnValue(new ReadonlyURLSearchParams('/blog')); | ||
|
||
render(<PostsClient posts={posts} />); | ||
|
||
expect(screen.getByText(posts[0].title)).toBeInTheDocument(); | ||
expect(screen.getByText(posts[0].intro)).toBeInTheDocument(); | ||
|
||
expect(screen.getByText(posts[1].title)).toBeInTheDocument(); | ||
expect(screen.getByText(posts[1].intro)).toBeInTheDocument(); | ||
|
||
expect(screen.getByText(posts[2].title)).toBeInTheDocument(); | ||
expect(screen.getByText(posts[2].intro)).toBeInTheDocument(); | ||
}); | ||
|
||
test('typing in input adds to query param and filters posts', async () => { | ||
const push = jest.fn(); | ||
|
||
// @ts-expect-error - we don't need to mock all the properties but TS isn't happy about that | ||
mockUseRouter.mockReturnValue({ push }); | ||
|
||
mockUseSearchParams.mockReturnValue(new ReadonlyURLSearchParams('/blog')); | ||
|
||
render(<PostsClient posts={posts} />); | ||
|
||
expect(screen.getByText(posts[0].title)).toBeInTheDocument(); | ||
expect(screen.getByText(posts[0].intro)).toBeInTheDocument(); | ||
|
||
expect(screen.getByText(posts[1].title)).toBeInTheDocument(); | ||
expect(screen.getByText(posts[1].intro)).toBeInTheDocument(); | ||
|
||
expect(screen.getByText(posts[2].title)).toBeInTheDocument(); | ||
expect(screen.getByText(posts[2].intro)).toBeInTheDocument(); | ||
|
||
await userEvent.type(screen.getByRole('textbox'), 'vault'); | ||
|
||
// vault post | ||
expect(screen.queryByText(posts[2].title)).toBeInTheDocument(); | ||
expect(screen.queryByText(posts[2].intro)).toBeInTheDocument(); | ||
|
||
// rest of posts | ||
expect(screen.queryByText(posts[0].title)).not.toBeInTheDocument(); | ||
expect(screen.queryByText(posts[0].intro)).not.toBeInTheDocument(); | ||
|
||
expect(screen.queryByText(posts[1].title)).not.toBeInTheDocument(); | ||
expect(screen.queryByText(posts[1].intro)).not.toBeInTheDocument(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
'use client'; | ||
|
||
import Box from '@frontend/components/Box'; | ||
import Heading from '@frontend/components/Heading'; | ||
import Input from '@frontend/components/Input'; | ||
import PostItem from '@frontend/components/PostItem'; | ||
import Spacer from '@frontend/components/Spacer'; | ||
import { Post } from '@frontend/types/sanity'; | ||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'; | ||
import { ChangeEvent, useCallback, useState } from 'react'; | ||
|
||
interface Props { | ||
posts: Post[]; | ||
} | ||
|
||
export default function PostsClient({ posts }: Props) { | ||
const searchParams = useSearchParams(); | ||
const router = useRouter(); | ||
const pathname = usePathname(); | ||
const [query, setQuery] = useState({ | ||
title: searchParams.get('title') || '', | ||
}); | ||
|
||
const createQueryString = useCallback( | ||
(name: string, value: string) => { | ||
const params = new URLSearchParams(searchParams.toString()); | ||
if (value) { | ||
params.set(name, value); | ||
} else { | ||
params.delete(name); | ||
} | ||
|
||
return params.toString(); | ||
}, | ||
[searchParams], | ||
); | ||
|
||
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => { | ||
const { name, value } = e.target; | ||
|
||
setQuery(prevState => ({ | ||
...prevState, | ||
[name]: value, | ||
})); | ||
|
||
const queryString = createQueryString(name, value); | ||
|
||
router.push(`${pathname}?${queryString}`); | ||
}; | ||
|
||
const filteredPosts = posts | ||
.filter(post => { | ||
return post.title.toLowerCase().includes(query.title.toLowerCase()); | ||
}) | ||
.sort((a, b) => { | ||
if (a.publishedAt < b.publishedAt) { | ||
return 1; | ||
} | ||
|
||
if (a.publishedAt > b.publishedAt) { | ||
return -1; | ||
} | ||
|
||
return 0; | ||
}); | ||
|
||
const postsByYear: Record<string, Post[]> = {}; | ||
|
||
filteredPosts.forEach(post => { | ||
const year = new Date(post.publishedAt).getFullYear(); | ||
|
||
if (!postsByYear[year]) { | ||
postsByYear[year] = []; | ||
} | ||
|
||
postsByYear[year].push(post); | ||
}); | ||
|
||
const sortedYears = Object.keys(postsByYear).sort( | ||
(a, b) => Number(b) - Number(a), | ||
); | ||
|
||
return ( | ||
<> | ||
<Box> | ||
<Input | ||
onChange={handleInputChange} | ||
placeholder="Search" | ||
value={query.title} | ||
type="text" | ||
id="title" | ||
name="title" | ||
/> | ||
</Box> | ||
<Spacer height="xxxl" /> | ||
|
||
<Box as="section"> | ||
{sortedYears.map(year => ( | ||
<Box key={year} marginBottom="xxxl"> | ||
<Heading fontSize="xl" as="h2" color="foregroundNeutral"> | ||
{year} | ||
</Heading> | ||
<Spacer height="xl" /> | ||
{postsByYear[year].map(post => ( | ||
<PostItem post={post} key={post._id} /> | ||
))} | ||
</Box> | ||
))} | ||
</Box> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { variables } from '@frontend/styles/variables.css'; | ||
import { globalStyle, style } from '@vanilla-extract/css'; | ||
|
||
export const root = style({ | ||
width: '50%', | ||
padding: variables.spacing.sm, | ||
border: '1px solid', | ||
borderColor: variables.color.border, | ||
backgroundColor: variables.color.surface, | ||
borderRadius: variables.radii.md, | ||
':focus': { | ||
outline: 'transparent', | ||
}, | ||
':focus-visible': { | ||
outlineWidth: '2px', | ||
outlineStyle: 'solid', | ||
outlineOffset: '2px', | ||
outlineColor: variables.color.outline, | ||
}, | ||
}); | ||
|
||
globalStyle(`${root}::placeholder`, { | ||
color: variables.color.foregroundNeutral, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { InputHTMLAttributes } from 'react'; | ||
import * as styles from './Input.css'; | ||
|
||
type InputProps = InputHTMLAttributes<HTMLInputElement>; | ||
|
||
export default function Input(props: InputProps) { | ||
return <input {...props} className={styles.root} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.