Skip to content

Commit

Permalink
Merge pull request #58 from kkkkkSE/feat/manage-account
Browse files Browse the repository at this point in the history
[feat] 비밀번호 변경 페이지 구현 및 테스트 코드 작성
  • Loading branch information
kkkkkSE authored Sep 5, 2023
2 parents e014923 + 7ca1a4b commit c008ebf
Show file tree
Hide file tree
Showing 11 changed files with 547 additions and 14 deletions.
35 changes: 35 additions & 0 deletions src/components/account/PasswordChangeForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';

import { render } from '../../test-helper';

import PasswordChangeForm from './PasswordChangeForm';

const context = describe;

const store = {
changePassword: jest.fn(),
};

jest.mock('../../hooks/usePasswordChangeFormStore', () => () => [{}, store]);

describe('<PasswordChangeForm />', () => {
it('render change password form', () => {
render(<PasswordChangeForm />);

screen.getByLabelText(/새 비밀번호/);

screen.getByRole('button', { name: /변경하기/ });
});

context('when submitting form', () => {
it('execute changePassword function in store', async () => {
render(<PasswordChangeForm />);

fireEvent.submit(screen.getByTestId('password-change-form'));

await waitFor(() => {
expect(store.changePassword).toHaveBeenCalled();
});
});
});
});
86 changes: 86 additions & 0 deletions src/components/account/PasswordChangeForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Controller, useForm } from 'react-hook-form';

import useLoginUserStore from '../../hooks/useLoginUserStore';
import usePasswordChangeFormStore from '../../hooks/usePasswordChangeFormStore';

import OperationButtons from '../ui/OperationButtons';
import ErrorMessage from '../ui/ErrorMessage';

import TextBox from '../ui/TextBox';

export default function PasswordChangeForm() {
const [{ userType }] = useLoginUserStore();

const [{ errorMessage }, store] = usePasswordChangeFormStore();

interface FormValues {
password: string;
newPassword: string;
confirmNewPassword: string;
}

const { control, handleSubmit } = useForm<FormValues>();

const onSubmit = (data: FormValues) => {
store.changePassword({
type: userType,
...data,
});
};

return (
<form
onSubmit={handleSubmit(onSubmit)}
data-testid="password-change-form"
>
<Controller
control={control}
name="password"
render={({ field: { value, onChange } }) => (
<TextBox
type="password"
label="기존 비밀번호"
value={value}
onChange={onChange}
/>
)}
/>

<Controller
control={control}
name="newPassword"
render={({ field: { value, onChange } }) => (
<TextBox
type="password"
label="새 비밀번호"
placeholder="8~40자 영문, 숫자 포함"
value={value}
onChange={onChange}
/>
)}
/>

<Controller
control={control}
name="confirmNewPassword"
render={({ field: { value, onChange } }) => (
<TextBox
type="password"
label="비밀번호 확인"
value={value}
onChange={onChange}
/>
)}
/>

<OperationButtons
primaryName="변경하기"
primaryType="submit"
/>

{errorMessage && (
<ErrorMessage>{errorMessage}</ErrorMessage>
)}
</form>
);
}
13 changes: 13 additions & 0 deletions src/hooks/usePasswordChangeFormStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { container } from 'tsyringe';

import { useStore } from 'usestore-ts';

import PasswordChangeFormStore from '../stores/PasswordChangeFormStore';

const usePasswordChangeFormStore = () => {
const store = container.resolve(PasswordChangeFormStore);

return useStore(store);
};

export default usePasswordChangeFormStore;
76 changes: 76 additions & 0 deletions src/pages/AccountPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Link } from 'react-router-dom';

import styled from 'styled-components';

import ContentLayout from '../components/layout/ContentLayout';

import passwordIcon from '../assets/image/icon/password-icon.png';
import withdrawalIcon from '../assets/image/icon/withdrawal-icon.png';

export default function AccountPage() {
return (
<ContentLayout
pageHeader="계정 관리"
testId="account-management"
>
<ContainerList>
<li>
<Link to="change-password">
<img src={passwordIcon} alt="" />
<span>비밀번호 변경</span>
</Link>
</li>
<li>
<Link to="delete-password">
<img src={withdrawalIcon} alt="" />
<span>계정 탈퇴</span>
</Link>
</li>
</ContainerList>
</ContentLayout>
);
}

const ContainerList = styled.ul`
li {
padding: 1rem 1.4rem;
margin-bottom: .4rem;
border-radius: 1rem;
cursor: pointer;
:hover {
background-color: ${(props) => props.theme.colors.gray2.default};
}
> a {
display: flex;
align-items: center;
flex-wrap: nowrap;
width: 100%;
}
img {
width: 3rem;
margin-right: 2rem;
}
span {
${(props) => props.theme.texts.bold.subTitle}
}
}
@media screen and (${(props) => props.theme.breakPoint.mobile}){
li {
padding: 1rem 1.2rem;
img {
width: 2.6rem;
margin-right: 1.4rem;
}
span {
${(props) => props.theme.texts.bold.boldText}
}
}
}
`;
37 changes: 37 additions & 0 deletions src/pages/PasswordChangePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useEffect } from 'react';

import { useNavigate } from 'react-router-dom';

import usePasswordChangeFormStore from '../hooks/usePasswordChangeFormStore';

import { STATIC_ROUTES } from '../constants/routes';

import ContentLayout from '../components/layout/ContentLayout';
import PasswordChangeForm from '../components/account/PasswordChangeForm';

export default function PasswordChangePage() {
const navigate = useNavigate();

const [{ done }, store] = usePasswordChangeFormStore();

useEffect(() => {
store.reset();
}, []);

useEffect(() => {
if (done) {
store.reset();

navigate(STATIC_ROUTES.ACCOUNT);
}
}, [done]);

return (
<ContentLayout
pageHeader="비밀번호 변경"
testId="password-change"
>
<PasswordChangeForm />
</ContentLayout>
);
}
6 changes: 5 additions & 1 deletion src/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import AutoReplyNewPage from './AutoReplyNewPage';
import AutoReplyEditPage from './AutoReplyEditPage';
import OpenProfileListPage from './OpenProfileListPage';
import OpenProfilePage from './OpenProfilePage';
import AccountPage from './AccountPage';
import PasswordChangePage from './PasswordChangePage';

export default {
export {
ChatListPage,
ChatRoomPage,
HomePage,
Expand All @@ -24,4 +26,6 @@ export default {
AutoReplyEditPage,
OpenProfileListPage,
OpenProfilePage,
AccountPage,
PasswordChangePage,
};
16 changes: 16 additions & 0 deletions src/routes.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,21 @@ describe('routes', () => {
screen.getByTestId(/open-profile/);
});
});

context('when the current path is "/account"', () => {
it('renders <AccountPage />', () => {
setupRouterProvider(STATIC_ROUTES.ACCOUNT);

screen.getByTestId(/account-management/);
});
});

context('when the current path is "/change-password"', () => {
it('renders <PasswordChangePage />', () => {
setupRouterProvider(STATIC_ROUTES.CHANGE_PASSWORD);

screen.getByTestId('password-change');
});
});
});
});
28 changes: 15 additions & 13 deletions src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,30 @@ import PreLoginLayout from './components/layout/PreLoginLayout';

import { STATIC_ROUTES } from './constants/routes';

import pages from './pages';
import * as Pages from './pages';

const routes = [
{
element: <PreLoginLayout />,
children: [
{ path: STATIC_ROUTES.HOME, element: <pages.HomePage /> },
{ path: STATIC_ROUTES.LOGIN, element: <pages.LoginPage /> },
{ path: STATIC_ROUTES.SIGN_UP, element: <pages.SignUpPage /> },
{ path: STATIC_ROUTES.HOME, element: <Pages.HomePage /> },
{ path: STATIC_ROUTES.LOGIN, element: <Pages.LoginPage /> },
{ path: STATIC_ROUTES.SIGN_UP, element: <Pages.SignUpPage /> },
],
}, {
element: <PostLoginLayout />,
children: [
{ path: STATIC_ROUTES.CHATROOMS, element: <pages.ChatListPage /> },
{ path: `${STATIC_ROUTES.CHATROOMS}/:id`, element: <pages.ChatRoomPage /> },
{ path: STATIC_ROUTES.MY_PROFILE, element: <pages.MyProfilePage /> },
{ path: STATIC_ROUTES.MY_PROFILE_EDIT, element: <pages.ProfileEditPage /> },
{ path: STATIC_ROUTES.AUTO_REPLIES, element: <pages.AutoReplyAdminPage /> },
{ path: STATIC_ROUTES.AUTO_REPLIES_NEW, element: <pages.AutoReplyNewPage /> },
{ path: `${STATIC_ROUTES.AUTO_REPLIES}/:id`, element: <pages.AutoReplyEditPage /> },
{ path: STATIC_ROUTES.OPEN_PROFILES, element: <pages.OpenProfileListPage /> },
{ path: `${STATIC_ROUTES.OPEN_PROFILES}/:id`, element: <pages.OpenProfilePage /> },
{ path: STATIC_ROUTES.CHATROOMS, element: <Pages.ChatListPage /> },
{ path: `${STATIC_ROUTES.CHATROOMS}/:id`, element: <Pages.ChatRoomPage /> },
{ path: STATIC_ROUTES.MY_PROFILE, element: <Pages.MyProfilePage /> },
{ path: STATIC_ROUTES.MY_PROFILE_EDIT, element: <Pages.ProfileEditPage /> },
{ path: STATIC_ROUTES.AUTO_REPLIES, element: <Pages.AutoReplyAdminPage /> },
{ path: STATIC_ROUTES.AUTO_REPLIES_NEW, element: <Pages.AutoReplyNewPage /> },
{ path: `${STATIC_ROUTES.AUTO_REPLIES}/:id`, element: <Pages.AutoReplyEditPage /> },
{ path: STATIC_ROUTES.OPEN_PROFILES, element: <Pages.OpenProfileListPage /> },
{ path: `${STATIC_ROUTES.OPEN_PROFILES}/:id`, element: <Pages.OpenProfilePage /> },
{ path: STATIC_ROUTES.ACCOUNT, element: <Pages.AccountPage /> },
{ path: STATIC_ROUTES.CHANGE_PASSWORD, element: <Pages.PasswordChangePage /> },
],
},
];
Expand Down
18 changes: 18 additions & 0 deletions src/services/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ export default class ApiService {
return accessToken;
}

async changePassword({
type, password, newPassword, confirmNewPassword,
} : {
type: string;
password: string;
newPassword: string;
confirmNewPassword: string;
}) {
await this.instance.patch(
DYNAMIC_API_PATHS.CHANGE_PASSWORD(type),
{
password,
newPassword,
confirmNewPassword,
},
);
}

async logout() {
await this.instance.delete(STATIC_API_PATHS.LOGOUT);
}
Expand Down
Loading

0 comments on commit c008ebf

Please sign in to comment.