-
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.
- Loading branch information
Showing
6 changed files
with
282 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Button } from '@mantine/core'; | ||
|
||
interface UserDeleteButtonProps { | ||
id: number; | ||
handleDeleteUserButtonClick: (id: number) => void; | ||
} | ||
|
||
const UserDeleteButton = ({ | ||
id, | ||
handleDeleteUserButtonClick, | ||
}: UserDeleteButtonProps) => { | ||
return ( | ||
<Button | ||
color="red" | ||
variant="light" | ||
bd="solid 2px" | ||
onClick={() => handleDeleteUserButtonClick(id)} | ||
> | ||
削除 | ||
</Button> | ||
); | ||
}; | ||
|
||
export default UserDeleteButton; |
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,55 @@ | ||
import { Container, Stack } from '@mantine/core'; | ||
import UsersListTitle from './UsersListTitle'; | ||
import ContentsHeader from '../common/pagination/ContentsHeader'; | ||
import type { PaginationProps } from '~/types/paginatiion'; | ||
import PaginationComponent from '../common/pagination/PaginationComponent'; | ||
import ErrorComponent from '../common/error/ErrorComponent'; | ||
import { getUsersResponse } from 'client/client'; | ||
import UsersListTable from './UsersListTable'; | ||
|
||
interface UsersListComponentProps { | ||
paginationProps: PaginationProps; | ||
usersResponse: getUsersResponse; | ||
handleDeleteUserButtonClick: (id: number) => void; | ||
} | ||
|
||
const UsersListComponent = ({ | ||
paginationProps, | ||
usersResponse, | ||
handleDeleteUserButtonClick, | ||
}: UsersListComponentProps) => { | ||
return ( | ||
<Container> | ||
<Stack | ||
bg="var(--mantine-color-body)" | ||
align="center" | ||
justify="center" | ||
maw="100%" | ||
> | ||
<UsersListTitle /> | ||
<ContentsHeader | ||
page={paginationProps.page} | ||
limit={paginationProps.limit} | ||
total={paginationProps.totalNum} | ||
handleLimitChange={paginationProps.handleLimitChange} | ||
/> | ||
{usersResponse.status === 200 ? ( | ||
<UsersListTable | ||
users={usersResponse.data.users} | ||
handleDeleteUserButtonClick={handleDeleteUserButtonClick} | ||
/> | ||
) : ( | ||
<ErrorComponent message={'ユーザー情報を取得できませんでした。'} /> | ||
)} | ||
<PaginationComponent | ||
totalNum={paginationProps.totalNum} | ||
page={paginationProps.page} | ||
limit={paginationProps.limit} | ||
handlePaginationChange={paginationProps.handlePaginationChange} | ||
/> | ||
</Stack> | ||
</Container> | ||
); | ||
}; | ||
|
||
export default UsersListComponent; |
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,37 @@ | ||
import { rem, Table } from '@mantine/core'; | ||
import { GetUsers200UsersItem } from 'client/client.schemas'; | ||
import UserDeleteButton from './UserDeleteButton'; | ||
|
||
interface UsersTableProps { | ||
users: GetUsers200UsersItem[]; | ||
handleDeleteUserButtonClick: (id: number) => void; | ||
} | ||
|
||
const UsersListTable = ({ | ||
users, | ||
handleDeleteUserButtonClick, | ||
}: UsersTableProps) => { | ||
return ( | ||
<Table striped maw="50%"> | ||
<Table.Tbody> | ||
{users.map((user) => ( | ||
<Table.Tr key={user.id}> | ||
{user.id && ( | ||
<> | ||
<Table.Td>{user.name}</Table.Td> | ||
<Table.Td maw={rem(5)}> | ||
<UserDeleteButton | ||
id={user.id} | ||
handleDeleteUserButtonClick={handleDeleteUserButtonClick} | ||
/> | ||
</Table.Td> | ||
</> | ||
)} | ||
</Table.Tr> | ||
))} | ||
</Table.Tbody> | ||
</Table> | ||
); | ||
}; | ||
|
||
export default UsersListTable; |
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,15 @@ | ||
import { Center, Group, Title } from '@mantine/core'; | ||
import { FaUsers } from 'react-icons/fa'; | ||
|
||
const UsersListTitle = () => { | ||
return ( | ||
<Center> | ||
<Group justify="center" align="center"> | ||
<FaUsers size="3.5ch" /> | ||
<Title order={1}>ユーザー一覧</Title> | ||
</Group> | ||
</Center> | ||
); | ||
}; | ||
|
||
export default UsersListTitle; |
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,144 @@ | ||
import { | ||
ActionFunctionArgs, | ||
json, | ||
LoaderFunctionArgs, | ||
redirect, | ||
} from '@remix-run/cloudflare'; | ||
import { useFetcher, useLoaderData, useNavigate } from '@remix-run/react'; | ||
import { deleteUser, getUsers, getUsersResponse } from 'client/client'; | ||
import UsersListComponent from '~/components/users/UsersListComponent'; | ||
import { commitSession, getSession } from '~/services/session.server'; | ||
import { ActionResponse } from '~/types/response'; | ||
|
||
interface LoaderData { | ||
usersResponse: getUsersResponse; | ||
condition: { | ||
page?: string; | ||
limit?: string; | ||
}; | ||
} | ||
|
||
export const loader = async ({ request }: LoaderFunctionArgs) => { | ||
// 検索条件を取得する | ||
const url = new URL(request.url); | ||
const page = url.searchParams.get('page') ?? undefined; | ||
const limit = url.searchParams.get('limit') ?? undefined; | ||
// ユーザー情報を取得する | ||
const response = await getUsers({ page: page, limit: limit }); | ||
|
||
return json<LoaderData>({ | ||
usersResponse: response, | ||
condition: { | ||
page: page, | ||
limit: limit, | ||
}, | ||
}); | ||
}; | ||
|
||
export const action = async ({ request }: ActionFunctionArgs) => { | ||
const session = await getSession(request.headers.get('Cookie')); | ||
const formData = await request.formData(); | ||
const userId = String(formData.get('userId')); | ||
|
||
// 未ログインの場合 | ||
if (!session.has('user')) { | ||
session.flash('error', 'ログインしてください'); | ||
return redirect('/login', { | ||
headers: { | ||
'Set-Cookie': await commitSession(session), | ||
}, | ||
}); | ||
} | ||
|
||
const cookieHeader = [ | ||
`__Secure-user_id=${session.get('user')?.id};`, | ||
`__Secure-session_token=${session.get('user')?.sessionToken}`, | ||
].join('; '); | ||
|
||
const response = await deleteUser(userId, { | ||
headers: { Cookie: cookieHeader }, | ||
}); | ||
|
||
switch (response.status) { | ||
case 204: | ||
session.flash('success', 'ユーザーを削除しました'); | ||
if (Number(userId) === session.get('user')?.id) { | ||
session.unset('user'); | ||
session.flash('success', 'ログアウトしました'); | ||
return redirect('/home', { | ||
headers: { | ||
'Set-Cookie': await commitSession(session), | ||
}, | ||
}); | ||
} | ||
break; | ||
case 401: | ||
session.flash('error', 'ログインしてください'); | ||
return redirect('/login', { | ||
headers: { | ||
'Set-Cookie': await commitSession(session), | ||
}, | ||
}); | ||
case 404: | ||
session.flash('error', 'ユーザーが見つかりませんでした'); | ||
break; | ||
case 500: | ||
session.flash('error', 'サーバーエラーが発生しました'); | ||
break; | ||
} | ||
return json<ActionResponse>( | ||
{ method: 'DELETE', status: response.status }, | ||
{ | ||
headers: { | ||
'Set-Cookie': await commitSession(session), | ||
}, | ||
}, | ||
); | ||
}; | ||
|
||
const UsersListPage = () => { | ||
const { usersResponse, condition } = useLoaderData<typeof loader>(); | ||
const { page, limit } = condition; | ||
const navigate = useNavigate(); | ||
const fetcher = useFetcher(); | ||
const handlePaginationChange = (newPage: number) => { | ||
let url = '/home/users'; | ||
let initial = true; | ||
if (limit) { | ||
url = | ||
initial === true ? `${url}?limit=${limit}` : `${url}&limit=${limit}`; | ||
initial = false; | ||
} | ||
url = | ||
initial === true ? `${url}?page=${newPage}` : `${url}&page=${newPage}`; | ||
navigate(url); | ||
}; | ||
|
||
const handleLimitChange = (newLimit: number) => | ||
navigate(`/home/users?limit=${newLimit}`); | ||
|
||
const handleDeleteUserButtonClick = (id: number) => { | ||
fetcher.submit( | ||
{ userId: id }, | ||
{ | ||
method: 'DELETE', | ||
}, | ||
); | ||
}; | ||
|
||
return ( | ||
<UsersListComponent | ||
paginationProps={{ | ||
handlePaginationChange: handlePaginationChange, | ||
handleLimitChange: handleLimitChange, | ||
page: page ? Number(page) : undefined, | ||
limit: limit ? Number(limit) : undefined, | ||
totalNum: usersResponse.data.totalUser, | ||
}} | ||
usersResponse={usersResponse} | ||
handleDeleteUserButtonClick={handleDeleteUserButtonClick} | ||
/> | ||
); | ||
}; | ||
|
||
export default UsersListPage; |
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,7 @@ | ||
import { Outlet } from '@remix-run/react'; | ||
|
||
const UsersLayout = () => { | ||
return <Outlet />; | ||
}; | ||
|
||
export default UsersLayout; |