Skip to content

Commit

Permalink
feat: 관리페이지 배너 섹션 수정 및 비밀번호 재전송 기능 구현 (#246)
Browse files Browse the repository at this point in the history
* feat(member): swap incorrect view `BannerSection`

* feat(member): implement member password resending api/mutate functions

* feat(member): separate permission change logic into modal and add password resend feature

* style(member): fix issue with `Select` label being center-aligned at specific sizes

* style(member): adjust `Modal` background z-index to prevent toast from being hidden behind it

* style(member): change password resend label text at `MemberSettingModal`

* style(member): adjust `MemberSettingModal` open button to be centered in the cell

* refactor(member): rename `addMember`, `resendMemberPassword` functions

* Create silent-cooks-know.md

* refactor(member): reset input fields upon successful account creation

* chore(member): remove `.ts`, `.tsx` from import paths

* refactor(member): separate modals for password resend and permission change features

* refactor(member): add dependency at `useMemberPasswordResendModal` & apply `useCallback`

---------

Co-authored-by: Gwansik Kim <[email protected]>
  • Loading branch information
SWARVY and gwansikk authored Sep 23, 2024
1 parent 3e8d86c commit 7a62395
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/silent-cooks-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clab-platforms/member": patch
---

feat: 관리페이지 배너 섹션 수정 및 비밀번호 재전송 기능 구현
13 changes: 12 additions & 1 deletion apps/member/src/api/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,22 @@ export async function patchMemberRole({
/**
* 멤버 추가
*/
export async function addMember(body: AddMemberRequestType) {
export async function postAddMember(body: AddMemberRequestType) {
const { data } = await server.post<
AddMemberRequestType,
BaseResponse<string>
>({ url: END_POINT.MEMBER_ADD, body });

return data;
}

/**
* 멤버 비밀번호 재전송
*/
export async function postResendMemberPassword(memberId: string) {
const { data } = await server.post<string, BaseResponse<string>>({
url: END_POINT.MEMBER_PASSWORD_RESEND(memberId),
});

return data;
}
2 changes: 1 addition & 1 deletion apps/member/src/components/common/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const Modal = ({ children }: PropsWithChildren) => {

return (
<div
className="fixed inset-0 z-40"
className="fixed inset-0 z-30"
aria-labelledby="modalTitle"
aria-modal="true"
role="dialog"
Expand Down
2 changes: 1 addition & 1 deletion apps/member/src/components/common/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const Select = forwardRef<HTMLSelectElement, Props>(
return (
<div className={cn('flex flex-col', className)}>
{label && (
<label htmlFor={id} className="mb-1 ml-1 text-xs">
<label htmlFor={id} className="mb-1 ml-1 text-left text-xs">
{label}
</label>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { ChangeEvent, FormEvent, useState } from 'react';

import { Button, Input } from '@clab-platforms/design-system';

import Select from '@components/common/Select/Select.tsx';
import Select from '@components/common/Select/Select';

import { SELECT_OPTIONS } from '@constants/select.ts';
import { useMemberAddMutation } from '@hooks/queries/member/useMemberAddMutation.ts';
import { SELECT_OPTIONS } from '@constants/select';
import { useMemberAddMutation } from '@hooks/queries/member/useMemberAddMutation';

import { AddMemberRequestType } from '@type/manage.ts';
import { AddMemberRequestType } from '@type/manage';

const defaultMemberInfo: AddMemberRequestType = {
id: '',
Expand All @@ -28,7 +28,9 @@ const defaultMemberInfo: AddMemberRequestType = {
const AddMemberForm = () => {
const [userInput, setUserInput] =
useState<AddMemberRequestType>(defaultMemberInfo);
const { memberAddMutation } = useMemberAddMutation();
const { memberAddMutation } = useMemberAddMutation({
reset: () => setUserInput(defaultMemberInfo),
});

const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
Expand All @@ -53,6 +55,7 @@ const AddMemberForm = () => {
label="학번"
id="학번"
name="id"
value={userInput.id}
placeholder="학번을 입력해주세요."
onChange={handleInputChange}
required
Expand All @@ -61,6 +64,7 @@ const AddMemberForm = () => {
label="이름"
id="이름"
name="name"
value={userInput.name}
placeholder="이름을 입력해주세요."
onChange={handleInputChange}
required
Expand All @@ -70,6 +74,7 @@ const AddMemberForm = () => {
id="전화번호"
name="contact"
type="tel"
value={userInput.contact}
placeholder="전화번호를 입력해주세요."
onChange={handleInputChange}
required
Expand All @@ -79,6 +84,7 @@ const AddMemberForm = () => {
id="이메일"
name="email"
type="email"
value={userInput.email}
placeholder="이메일을 입력해주세요."
onChange={handleInputChange}
required
Expand All @@ -87,21 +93,24 @@ const AddMemberForm = () => {
label="학과"
id="학과"
name="department"
value={userInput.department}
placeholder="학과를 입력해주세요."
onChange={handleInputChange}
required
/>
<Select
label="구분"
label="학년"
options={SELECT_OPTIONS.GRADE}
name="studentStatus"
value={userInput.grade}
name="grade"
onChange={handleInputChange}
/>
<Input
label="생년월일"
id="생년월일"
name="birth"
type="date"
value={userInput.birth}
placeholder="생년월일을 입력해주세요."
onChange={handleInputChange}
required
Expand All @@ -110,26 +119,30 @@ const AddMemberForm = () => {
label="주소"
id="주소"
name="address"
value={userInput.address}
placeholder="주소를 입력해주세요."
onChange={handleInputChange}
required
/>
<Select
label="분야"
options={SELECT_OPTIONS.MY_FIELD}
value={userInput.interests}
name="interests"
onChange={handleInputChange}
/>
<Input
label="깃허브 주소"
id="깃허브 주소"
name="githubUrl"
value={userInput.githubUrl}
placeholder="깃허브 주소를 입력해주세요."
onChange={handleInputChange}
/>
<Select
label="구분"
options={SELECT_OPTIONS.STUDENT_STATUS}
value={userInput.studentStatus}
name="studentStatus"
onChange={handleInputChange}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,29 @@ import { useNavigate } from 'react-router-dom';
import {
Badge,
BadgeColorVariant,
Button,
Input,
Table,
} from '@clab-platforms/design-system';
import { SearchOutline } from '@clab-platforms/icon';

import Pagination from '@components/common/Pagination/Pagination.tsx';
import Select from '@components/common/Select/Select.tsx';
import ActionButton from '@components/common/ActionButton/ActionButton';
import Pagination from '@components/common/Pagination/Pagination';

import { TABLE_HEAD, TABLE_HEAD_ACTION } from '@constants/head.ts';
import { ROLE_LEVEL } from '@constants/state.ts';
import { usePagination } from '@hooks/common/usePagination.ts';
import useToast from '@hooks/common/useToast.ts';
import { useMemberRole, useMemberRoleMutation } from '@hooks/queries/member';
import { isNumeric } from '@utils/member.ts';
import { toKoreaMemberLevel } from '@utils/string.ts';
import { TABLE_HEAD, TABLE_HEAD_ACTION } from '@constants/head';
import { usePagination } from '@hooks/common/usePagination';
import { useMemberRole } from '@hooks/queries/member';
import { useMemberPasswordResendModal } from '@pages/ManagePage/hooks/useMemberPasswordResendModal';
import { useMemberPermissionSettingModal } from '@pages/ManagePage/hooks/useMemberPermissionSettingModal';
import { isNumeric } from '@utils/member';
import { toKoreaMemberLevel } from '@utils/string';

import { Role } from '@type/manage.ts';
import type { RoleLevelKey } from '@type/member.ts';
import { Role } from '@type/manage';
import type { RoleLevelKey } from '@type/member';

interface RoleEditViewProps {
role: Role;
}

const roleOptions = Object.keys(ROLE_LEVEL).map((key) => ({
name: toKoreaMemberLevel(key as RoleLevelKey),
value: key as RoleLevelKey,
}));

const roleColors: Record<RoleLevelKey, BadgeColorVariant> = {
SUPER: 'red',
ADMIN: 'blue',
Expand All @@ -41,13 +35,11 @@ const roleColors: Record<RoleLevelKey, BadgeColorVariant> = {

const RoleEditView = ({ role }: RoleEditViewProps) => {
const navigation = useNavigate();
const toast = useToast();
const { page, size, handlePageChange } = usePagination({
defaultSize: 6,
sectionName: 'level',
});
const [searchWords, setSearchWords] = useState<string>('');
const [changeRole, setChangeRole] = useState(roleOptions[0].value);

const { data, refetch } = useMemberRole({
page: page,
Expand All @@ -58,24 +50,14 @@ const RoleEditView = ({ role }: RoleEditViewProps) => {
role: role || undefined,
});

const { memberRoleMutation } = useMemberRoleMutation();
const { open: permissionSettingModalOpen } =
useMemberPermissionSettingModal();

const { open: passwordResendModalOpen } = useMemberPasswordResendModal();

const handleSearchClick = () => {
refetch();
};
const handleLevelChangeButtonClick = (memberId: string) => {
if (!changeRole) {
return toast({
state: 'error',
message: '권한을 선택해주세요',
});
} else {
memberRoleMutation({
memberId: memberId,
body: { role: changeRole },
});
}
};

useEffect(() => {
navigation('/manage');
Expand Down Expand Up @@ -109,22 +91,22 @@ const RoleEditView = ({ role }: RoleEditViewProps) => {
<Table.Cell>
<Badge color={roleColors[role]}>{toKoreaMemberLevel(role)}</Badge>
</Table.Cell>
<Table.Cell>
<div className="mx-auto flex items-center justify-center gap-2">
<Select
id="changeRole"
options={roleOptions}
onChange={(e) =>
setChangeRole(e.target.value as RoleLevelKey)
}
/>
<Button
size="sm"
onClick={() => handleLevelChangeButtonClick(id)}
>
변경하기
</Button>
</div>
<Table.Cell className="flex items-center justify-center gap-x-2">
<ActionButton
type="button"
onClick={() =>
permissionSettingModalOpen({ memberId: id, role })
}
>
권한 변경
</ActionButton>
<ActionButton
type="button"
color="red"
onClick={() => passwordResendModalOpen({ memberId: id, name })}
>
비밀번호 재전송
</ActionButton>
</Table.Cell>
</Table.Row>
))}
Expand Down
2 changes: 2 additions & 0 deletions apps/member/src/constants/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const END_POINT = {
MEMBER_LEVEL: `/v1/members/roles`,
MEMBER_LEVEL_EDIT: (memberId: string) => `/v1/members/${memberId}/roles`,
MEMBER_ADD: `v1/members`,
MEMBER_PASSWORD_RESEND: (memberId: string) =>
`/v1/members/password/${memberId}/resend`,
// -- 커뮤니티
BOARDS: `/v1/boards`,
BOARDS_LIST: `/v1/boards/category`,
Expand Down
15 changes: 10 additions & 5 deletions apps/member/src/hooks/queries/member/useMemberAddMutation.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { addMember } from '@api/member';
import { postAddMember } from '@api/member';
import { MEMBER_QUERY_KEY } from '@constants/key';
import useToast from '@hooks/common/useToast';

import { AddMemberRequestType } from '@type/manage.ts';
import { AddMemberRequestType } from '@type/manage';

/**
* 멤버를 추가합니다.
*/
export const useMemberAddMutation = () => {
interface Params {
reset: () => void;
}

export function useMemberAddMutation({ reset }: Params) {
const queryClient = useQueryClient();
const toast = useToast();

const mutation = useMutation({
mutationFn: (body: AddMemberRequestType) => addMember(body),
mutationFn: (body: AddMemberRequestType) => postAddMember(body),
onSuccess: (data) => {
if (data) {
queryClient.invalidateQueries({
Expand All @@ -24,6 +28,7 @@ export const useMemberAddMutation = () => {
state: 'success',
message: '계정이 생성되었어요.',
});
reset();
} else {
toast({
state: 'error',
Expand All @@ -34,4 +39,4 @@ export const useMemberAddMutation = () => {
});

return { memberAddMutation: mutation.mutate };
};
}
4 changes: 2 additions & 2 deletions apps/member/src/pages/ManagePage/components/BannerSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export function BannerSection() {
};

const renderView = {
VIEW: (
VIEW: <ActivityPhotoBanner />,
ADD: (
<form onSubmit={handleOnSubmit} className="space-y-2">
<Input
id="title"
Expand All @@ -88,7 +89,6 @@ export function BannerSection() {
<Button className="w-full">활동 사진 변경하기</Button>
</form>
),
ADD: <ActivityPhotoBanner />,
}[view];

return (
Expand Down
Loading

0 comments on commit 7a62395

Please sign in to comment.