Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/paginated.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from 'zod'

type Paginated<T> = {
export type Paginated<T> = {
data: T[]
pageInfo: {
page: number
Expand Down
14 changes: 14 additions & 0 deletions src/app/home.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@

background-color: transparent;
}

.searchResult {
padding: 2rem 0;

th,
td {
text-align: left;
padding: 0.5rem 1rem;
}
}

.searchResultTable {
padding-bottom: 1rem;
}
108 changes: 95 additions & 13 deletions src/app/home.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,98 @@
import type { FC } from 'react'
import { type FC, useRef, useState } from 'react'
import { Form } from 'react-router'
import { Paginated } from '@/api/paginated'
import { searchSparcsUsers } from '@/api/user-sparcs'
import { Pagination } from '@/components/pagination/pagination'
import styles from './home.module.scss'

export const Home: FC = () => (
<main className={styles.main}>
<Form action="/user">
<input
type="search"
placeholder="이름 or 닉네임"
name="q"
className={styles.search}
/>
</Form>
</main>
)
type SparcsUser = {
id: number
nickname: string
fullName: string
}

const PAGE_SIZE = 10
const INITIAL_PAGE = 0

export const Home: FC = () => {
const [paginatedSparcsUsers, setPaginatedSparcsUsers] = useState<
Paginated<SparcsUser> | undefined
>()
const formRef = useRef<HTMLFormElement | null>(null)

const handleSearchPage = async (
e?: React.FormEvent<HTMLFormElement>,
page: number = INITIAL_PAGE,
size: number = PAGE_SIZE,
) => {
if (e) {
e.preventDefault()
}

if (!formRef.current) {
return
}

const formData = new FormData(formRef.current)
const q = formData.get('q') as string

if (!q) {
setPaginatedSparcsUsers(undefined)
return
}

const paginatedSparcsUsers = await searchSparcsUsers({
q,
page,
size,
})

setPaginatedSparcsUsers(paginatedSparcsUsers)
}

return (
<main className={styles.main}>
<Form
action="/user"
onSubmit={(e) => handleSearchPage(e, INITIAL_PAGE)}
ref={formRef}
>
<input
type="search"
placeholder="이름 or 닉네임"
name="q"
className={styles.search}
/>
</Form>
<div className={styles.searchResult}>
{paginatedSparcsUsers?.data.length ? (
<>
<table className={styles.searchResultTable}>
<thead>
<tr>
<th>이름</th>
<th>닉네임</th>
</tr>
</thead>
<tbody>
{paginatedSparcsUsers?.data.map((user) => (
<tr key={user.id}>
<td>{user.fullName}</td>
<td>{user.nickname}</td>
</tr>
))}
</tbody>
</table>
<Pagination
currentPage={paginatedSparcsUsers.pageInfo.page}
totalPages={paginatedSparcsUsers.pageInfo.totalPages}
onClickPage={(page) => handleSearchPage(undefined, page)}
/>
</>
) : (
<span>검색 결과가 없습니다.</span>
)}
</div>
</main>
)
}
23 changes: 23 additions & 0 deletions src/components/pagination/pagination.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.paginationButton {
width: 3rem;
height: 3rem;
border-radius: 50%;

border: 1px solid var(--clr-button-border);
background-color: transparent;

&:hover {
background-color: var(--clr-button-hover-bg);
cursor: pointer;
}

&:active {
background-color: var(--clr-button-active-bg);
}
}

.paginationButtonSelected {
@extend .paginationButton;

background-color: var(--clr-button-selected-bg);
}
36 changes: 36 additions & 0 deletions src/components/pagination/pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { FC } from 'react'
import styles from './pagination.module.scss'

export const Pagination: FC<{
currentPage: number
totalPages: number
onClickPage: (page: number) => void
}> = ({ currentPage, totalPages, onClickPage }) => {
const currentPageIndex = currentPage + 1

return (
<div>
{currentPageIndex >= 2 && (
<button
className={styles.paginationButton}
onClick={() => onClickPage(currentPage - 1)}
>
{currentPageIndex - 1}
</button>
)}

<button className={styles.paginationButtonSelected}>
{currentPageIndex}
</button>

{currentPageIndex < totalPages && (
<button
className={styles.paginationButton}
onClick={() => onClickPage(currentPage + 1)}
>
{currentPageIndex + 1}
</button>
)}
</div>
)
}
7 changes: 6 additions & 1 deletion src/styles/theme/_dark-theme.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use "colors";
@use 'colors';

@media (prefers-color-scheme: dark) {
:root {
Expand All @@ -19,6 +19,11 @@

--clr-home-search-shadow: #{colors.$grayOpacity300};
--clr-home-search-border: #{colors.$whiteOpacity100};

--clr-button-selected-bg: #{colors.$inverseGray100};
--clr-button-border: #{colors.$inverseGray300};
--clr-button-hover-bg: #{colors.$inverseGray200};
--clr-button-active-bg: #{colors.$inverseGray300};
}

html {
Expand Down
7 changes: 6 additions & 1 deletion src/styles/theme/_light-theme.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use "colors";
@use 'colors';

:root {
--clr-bg: #{colors.$white};
Expand All @@ -18,4 +18,9 @@

--clr-home-search-shadow: #{colors.$grayOpacity200};
--clr-home-search-border: #{colors.$whiteOpacity50};

--clr-button-selected-bg: #{colors.$gray100};
--clr-button-border: #{colors.$gray300};
--clr-button-hover-bg: #{colors.$gray200};
--clr-button-active-bg: #{colors.$gray300};
}