Skip to content

Commit 336da2e

Browse files
committed
add search
1 parent 640d7f2 commit 336da2e

File tree

31 files changed

+169
-29
lines changed

31 files changed

+169
-29
lines changed

exercises/01.create-router/01.problem.create-router/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/01.create-router/01.solution.create-router/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/01.create-router/02.problem.router-elements/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/01.create-router/02.solution.router-elements/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/02.linking/01.problem.achors/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/02.linking/01.solution.achors/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/02.linking/02.problem.links/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/02.linking/02.solution.links/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/02.linking/03.problem.custom-link/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/02.linking/03.solution.custom-link/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/03.nested-routes/01.problem.nested-routes/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/03.nested-routes/01.solution.nested-routes/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/03.nested-routes/02.problem.nested-path/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/03.nested-routes/02.solution.nested-path/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/03.nested-routes/03.problem.nested-layout/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/03.nested-routes/03.solution.nested-layout/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/03.nested-routes/03.solution.nested-layout/src/routes/app/layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ButtonLink } from '#src/components/button.tsx'
33

44
export function AppLayout() {
55
return (
6-
<div className="bg-background flex h-screen min-h-[700px] flex-col">
6+
<div className="bg-background flex h-screen min-h-[800px] flex-col">
77
<header className="bg-background-alt px-4 py-3">
88
<div className="container mx-auto flex max-w-6xl items-center justify-between">
99
<Link

exercises/03.nested-routes/04.problem.unnested-route/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/03.nested-routes/04.problem.unnested-route/src/routes/app/layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ButtonLink } from '#src/components/button.tsx'
33

44
export function AppLayout() {
55
return (
6-
<div className="bg-background flex h-screen min-h-[700px] flex-col">
6+
<div className="bg-background flex h-screen min-h-[800px] flex-col">
77
<header className="bg-background-alt px-4 py-3">
88
<div className="container mx-auto flex max-w-6xl items-center justify-between">
99
<Link

exercises/03.nested-routes/04.solution.unnested-route/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/03.nested-routes/04.solution.unnested-route/src/routes/app/layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ButtonLink } from '#src/components/button.tsx'
33

44
export function AppLayout() {
55
return (
6-
<div className="bg-background flex h-screen min-h-[700px] flex-col">
6+
<div className="bg-background flex h-screen min-h-[800px] flex-col">
77
<header className="bg-background-alt px-4 py-3">
88
<div className="container mx-auto flex max-w-6xl items-center justify-between">
99
<Link

exercises/04.route-paths/01.problem.dynamic-segments/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/04.route-paths/01.problem.dynamic-segments/src/routes/app/layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ButtonLink } from '#src/components/button.tsx'
33

44
export function AppLayout() {
55
return (
6-
<div className="bg-background flex h-screen min-h-[700px] flex-col">
6+
<div className="bg-background flex h-screen min-h-[800px] flex-col">
77
<header className="bg-background-alt px-4 py-3">
88
<div className="container mx-auto flex max-w-6xl items-center justify-between">
99
<Link

exercises/04.route-paths/01.solution.dynamic-segments/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/04.route-paths/01.solution.dynamic-segments/src/routes/app/layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ButtonLink } from '#src/components/button.tsx'
33

44
export function AppLayout() {
55
return (
6-
<div className="bg-background flex h-screen min-h-[700px] flex-col">
6+
<div className="bg-background flex h-screen min-h-[800px] flex-col">
77
<header className="bg-background-alt px-4 py-3">
88
<div className="container mx-auto flex max-w-6xl items-center justify-between">
99
<Link

exercises/99.final/01.solution.final/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"class-variance-authority": "^0.7.1",
1818
"clsx": "^2.1.1",
1919
"cron-parser": "^5.0.4",
20+
"match-sorter": "^8.0.0",
2021
"react": "^19.0.0",
2122
"react-dom": "^19.0.0",
2223
"react-router": "^7.2.0",

exercises/99.final/01.solution.final/src/components/icons.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,17 @@ export function ExclamationCircleIcon({ title, ...props }: IconProps) {
189189
</svg>
190190
)
191191
}
192+
193+
export function SearchIcon({ title, ...props }: IconProps) {
194+
return (
195+
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" {...props}>
196+
{title ? <title>{title}</title> : null}
197+
<path
198+
strokeLinecap="round"
199+
strokeLinejoin="round"
200+
strokeWidth={2}
201+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
202+
/>
203+
</svg>
204+
)
205+
}

exercises/99.final/01.solution.final/src/routes/app/layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ButtonLink } from '#src/components/button.tsx'
33

44
export function AppLayout() {
55
return (
6-
<div className="bg-background flex h-screen min-h-[700px] flex-col">
6+
<div className="bg-background flex h-screen min-h-[800px] flex-col">
77
<header className="bg-background-alt px-4 py-3">
88
<div className="container mx-auto flex max-w-6xl items-center justify-between">
99
<Link

exercises/99.final/01.solution.final/src/routes/app/recipients/$id.tsx

+68-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useParams, useRouteError } from 'react-router'
1+
import { matchSorter } from 'match-sorter'
2+
import { useParams, useRouteError, useSearchParams } from 'react-router'
23
import { Button, ButtonLink } from '#src/components/button.tsx'
34
import { Icon } from '#src/components/icon.tsx'
45
import { recipients } from '#src/data.ts'
@@ -15,10 +16,26 @@ const DAY_NAMES = [
1516

1617
export function RecipientRoute() {
1718
const { id } = useParams()
19+
const [searchParams, setSearchParams] = useSearchParams()
20+
const searchQuery = searchParams.get('q') ?? ''
1821
const recipient = recipients.find((r) => r.id === id)
1922

2023
if (!recipient) throw new Error(`Recipient with ID of "${id}" not found`)
2124

25+
const filteredMessages = searchQuery
26+
? matchSorter(recipient.messages, searchQuery, {
27+
keys: ['content'],
28+
})
29+
: recipient.messages
30+
31+
function handleSearch(value: string) {
32+
if (value) {
33+
setSearchParams({ q: value })
34+
} else {
35+
setSearchParams({})
36+
}
37+
}
38+
2239
return (
2340
<div className="flex min-h-0 flex-grow flex-col">
2441
{/* Left sidebar (header on mobile) */}
@@ -31,26 +48,57 @@ export function RecipientRoute() {
3148
</ButtonLink>
3249
</div>
3350

34-
<div className="mt-4 flex justify-between gap-4">
35-
<div className="flex items-center gap-2">
36-
<Icon name="Phone">
37-
<span className="font-mono">
38-
{recipient.countryCode} {recipient.phone}
39-
</span>
40-
</Icon>
41-
</div>
51+
<div className="mt-4 flex flex-col gap-4">
52+
<div className="flex justify-between gap-4">
53+
<div className="flex items-center gap-2">
54+
<Icon name="Phone">
55+
<span className="font-mono">
56+
{recipient.countryCode} {recipient.phone}
57+
</span>
58+
</Icon>
59+
</div>
4260

43-
<div className="flex items-center gap-2">
44-
<Icon name="Clock">
45-
<span>
46-
{recipient.schedule ? (
47-
`Every ${DAY_NAMES[recipient.nextScheduledAt?.getDay() ?? 0]} at ${recipient.nextScheduledAt?.getHours()}:${recipient.nextScheduledAt?.getMinutes()?.toString().padStart(2, '0')}`
48-
) : (
49-
<span className="text-foreground-alt">Schedule paused</span>
50-
)}
51-
</span>
52-
</Icon>
61+
<div className="flex items-center gap-2">
62+
<Icon name="Clock">
63+
<span>
64+
{recipient.schedule ? (
65+
`Every ${DAY_NAMES[recipient.nextScheduledAt?.getDay() ?? 0]} at ${recipient.nextScheduledAt?.getHours()}:${recipient.nextScheduledAt?.getMinutes()?.toString().padStart(2, '0')}`
66+
) : (
67+
<span className="text-foreground-alt">Schedule paused</span>
68+
)}
69+
</span>
70+
</Icon>
71+
</div>
5372
</div>
73+
74+
<form
75+
onSubmit={(e) => {
76+
e.preventDefault()
77+
const formData = new FormData(e.currentTarget)
78+
const query = formData.get('q')
79+
handleSearch(query?.toString() ?? '')
80+
}}
81+
className="flex items-center gap-2"
82+
>
83+
<div className="relative flex-1">
84+
<input
85+
type="search"
86+
name="q"
87+
placeholder="Search messages..."
88+
className="text-foreground-alt placeholder:text-foreground-alt/60 h-10 w-full rounded-lg py-2 pr-12 pl-4"
89+
value={searchQuery}
90+
onChange={(e) => handleSearch(e.currentTarget.value)}
91+
/>
92+
<Button
93+
type="submit"
94+
icon
95+
variant="borderless"
96+
className="absolute top-1/2 right-2 -translate-y-1/2"
97+
>
98+
<Icon name="Search" title="Search messages" />
99+
</Button>
100+
</div>
101+
</form>
54102
</div>
55103
</div>
56104

@@ -62,7 +110,7 @@ export function RecipientRoute() {
62110
node?.scrollTo({ top: node.scrollHeight, behavior: 'auto' })
63111
}}
64112
>
65-
{recipient.messages.map((message) => {
113+
{filteredMessages.map((message) => {
66114
const status = message.sentAt ? 'sent' : 'scheduled'
67115
const nextScheduledTime =
68116
status === 'scheduled' ? message.scheduledAt : null

exercises/99.final/01.solution.final/tests/router.test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,21 @@ test('404 error boundary should be shown for non-existent recipient', async ({
7272
const errorMessage = page.getByText(/recipient.*not found/i)
7373
await expect(errorMessage).toBeVisible({ timeout: 500 })
7474
})
75+
76+
test('should show search results when a search query is provided', async ({
77+
page,
78+
}) => {
79+
await page.goto('/recipients')
80+
81+
const summary = page.getByText(/select recipient/i)
82+
await summary.click()
83+
84+
const bethanyLink = page.getByRole('link', { name: /bethany/i })
85+
await bethanyLink.click()
86+
87+
const searchInput = page.getByRole('searchbox', { name: /Search/i })
88+
await searchInput.fill('love')
89+
90+
// Verify the URL has been updated with the search parameter
91+
await expect(page).toHaveURL(/[/]recipients[/].*[?]q=love/)
92+
})

0 commit comments

Comments
 (0)