Skip to content

Commit 2e39a8f

Browse files
committed
feat: 회원가입 약관동의 화면 이동 구현
1 parent fec6ac3 commit 2e39a8f

File tree

7 files changed

+301
-16
lines changed

7 files changed

+301
-16
lines changed

src/components/Join/JoinButton.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ const JoinButton = () => {
7575
phone,
7676
email,
7777
password,
78-
pwCheck: '',
79-
useTermsCheck: false,
80-
privacyTermsCheck: false,
78+
pwCheck: pwCheck,
79+
useTermsCheck: true,
80+
privacyTermsCheck: true,
8181
},
8282
{
8383
onError: (error) => {
@@ -95,7 +95,7 @@ const JoinButton = () => {
9595

9696
return (
9797
<>
98-
<button type={'submit'} onClick={onClick} className="btn btn-outline btn-primary w-full">
98+
<button type={'button'} onClick={onClick} className="btn btn-outline btn-primary w-full">
9999
회원가입 하기
100100
</button>
101101
<Dialog ref={dialogRef} desc={dialogMessage}></Dialog>

src/components/Join/TermsCheckInput.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,43 @@
1-
import { useJoinState } from '../../stores/joinStore.ts';
1+
import { useJoinState } from '@/stores/joinStore';
2+
import { useEffect } from 'react';
3+
import { useLocation } from 'react-router-dom';
24

35
const TermsCheckInput = () => {
46
const { useTermsCheck, privacyTermsCheck, useTermsCheckHandler, privacyTermsCheckHandler } = useJoinState();
7+
const { privacyTerms, useTerms } = useLocation().state || {};
8+
9+
useEffect(() => {
10+
if (privacyTerms) privacyTermsCheckHandler;
11+
if (useTerms) useTermsCheckHandler;
12+
}, [privacyTerms, privacyTermsCheckHandler, useTerms, useTermsCheckHandler]);
513

614
return (
715
<label className="form-control mb-9 mt-5 w-full">
816
<div className="flex flex-col">
917
<label className="font-bold">약관 동의</label>
1018
</div>
1119
<div className="mt-2 flex items-center justify-between">
12-
<label htmlFor="termsOne">이용약관 동의 (필수)</label>
20+
<label className="hover:underline" htmlFor="termsOne">
21+
<a href="/policy/usecondition">이용약관 동의 (필수)</a>
22+
</label>
1323
<input
1424
type="radio"
1525
className="radio-primary radio"
1626
checked={useTermsCheck}
17-
onClick={useTermsCheckHandler}
27+
aria-label={'join-useTermsCheck-input'}
1828
readOnly
1929
/>
2030
</div>
2131
{!useTermsCheck ? <span className="text-red-500">이용약관에 동의해 주세요.</span> : <span className="h-6"></span>}
2232
<div className="mt-2 flex items-center justify-between">
23-
<label htmlFor="termsTwo">개인정보 수집, 이용 동의 (필수)</label>
33+
<label className="hover:underline" htmlFor="termsTwo">
34+
<a href="/policy/personalInfo">개인정보 수집, 이용 동의 (필수)</a>
35+
</label>
2436
<input
2537
type="radio"
2638
className="radio-primary radio"
2739
checked={privacyTermsCheck}
28-
onClick={privacyTermsCheckHandler}
40+
aria-label={'join-privacyTermsCheck-input'}
2941
readOnly
3042
/>
3143
</div>
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { usePolicyState } from '../../stores/policyStore';
1+
import { useNavigate } from 'react-router-dom';
22

3-
const PolicyButton = () => {
4-
const { check } = usePolicyState();
3+
const PolicyPersonalButton = () => {
4+
const navigate = useNavigate();
55

66
const onClick = () => {
7-
console.log(check);
7+
navigate('/join', { replace: true, state: { privacyTerms: true } });
88
};
99

1010
return (
@@ -16,4 +16,4 @@ const PolicyButton = () => {
1616
);
1717
};
1818

19-
export default PolicyButton;
19+
export default PolicyPersonalButton;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useNavigate } from 'react-router-dom';
2+
3+
const PolicyUseConditionButton = () => {
4+
const navigate = useNavigate();
5+
6+
const onClick = () => {
7+
navigate('/join', { replace: true, state: { useTerms: true } });
8+
};
9+
10+
return (
11+
<div className="absolute bottom-12 w-10/12 md:w-11/12">
12+
<button type={'submit'} onClick={onClick} className="btn btn-outline btn-primary m-auto block w-full max-w-md">
13+
동의하기
14+
</button>
15+
</div>
16+
);
17+
};
18+
19+
export default PolicyUseConditionButton;

src/layouts/PolicyPageLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FC, ReactNode } from 'react';
2-
import PolicyButton from '../components/Policy/PolicyButton';
2+
import PolicyPersonalButton from '../components/Policy/PolicyPersonalButton';
33
import HistoryBackButton from '../components/common/HistoryBackButton';
44

55
interface Props {
@@ -14,7 +14,7 @@ const PolicyPageLayout: FC<Props> = ({ children, title }) => {
1414
<h2 className="my-5 text-xl font-semibold">{title}</h2>
1515
{/* <div className="card h-5/6 max-w-5xl bg-white">{children}</div> */}
1616
<div className="card h-3/4 max-w-5xl bg-white">{children}</div>
17-
<PolicyButton />
17+
<PolicyPersonalButton />
1818
</main>
1919
);
2020
};

src/tests/join.test.tsx

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
3+
import { JoinPage } from '../pages';
4+
import wrapper from './helpers/wrapper';
5+
import supabase from '@/supabase';
6+
import '@testing-library/jest-dom';
7+
8+
vi.mock('@/supabase', () => ({
9+
default: {
10+
auth: {
11+
signUp: vi.fn(),
12+
},
13+
},
14+
}));
15+
16+
describe('Join test', () => {
17+
beforeEach(() => {
18+
render(<JoinPage />, {
19+
wrapper,
20+
});
21+
window.HTMLDialogElement.prototype.showModal = vi.fn();
22+
window.HTMLDialogElement.prototype.close = vi.fn();
23+
});
24+
25+
afterEach(() => {
26+
vi.resetAllMocks();
27+
cleanup();
28+
});
29+
30+
it('이름이 유효하지 않다면 에러 텍스트를 보여줄 수 있어야 한다.', async () => {
31+
const nameInput = screen.getByLabelText(/join-name/i) as HTMLInputElement;
32+
fireEvent.change(nameInput, {
33+
target: {
34+
value: '1',
35+
},
36+
});
37+
await waitFor(() => {});
38+
const errorNameText = screen.getByText(/ /);
39+
expect(errorNameText).toBeInTheDocument();
40+
});
41+
42+
it('닉네임이 유효하지 않다면 에러 텍스트를 보여줄 수 있어야 한다.', async () => {
43+
const nickNameInput = screen.getByLabelText(/nickName/i) as HTMLInputElement;
44+
fireEvent.change(nickNameInput, {
45+
target: {
46+
value: '★☆★파이리☆★☆',
47+
},
48+
});
49+
await waitFor(() => {});
50+
const errorNameText = screen.getByText(/ 2~12 /);
51+
expect(errorNameText).toBeInTheDocument();
52+
});
53+
54+
it('휴대폰 번호가 유효하지 않다면 에러 텍스트를 보여줄 수 있어야 한다.', async () => {
55+
const phoneInput = screen.getByLabelText(/phone/i) as HTMLInputElement;
56+
fireEvent.change(phoneInput, {
57+
target: {
58+
value: '010111',
59+
},
60+
});
61+
await waitFor(() => {});
62+
const errorNameText = screen.getByText(/11/);
63+
expect(errorNameText).toBeInTheDocument();
64+
});
65+
66+
it('이메일이 유효하지 않다면 에러 텍스트를 보여줄 수 있어야 한다.', async () => {
67+
const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement;
68+
fireEvent.change(emailInput, {
69+
target: {
70+
value: 'asdfgh.com',
71+
},
72+
});
73+
await waitFor(() => {});
74+
const errorNameText = screen.getByText(/ /);
75+
expect(errorNameText).toBeInTheDocument();
76+
});
77+
78+
it('비밀번호가 유효하지 않다면 에러 텍스트를 보여줄 수 있어야 한다.', async () => {
79+
const passwordInput = screen.getByLabelText(/password/i) as HTMLInputElement;
80+
fireEvent.change(passwordInput, {
81+
target: {
82+
value: '123',
83+
},
84+
});
85+
await waitFor(() => {});
86+
const errorNameText = screen.getByText(/ 6 /);
87+
expect(errorNameText).toBeInTheDocument();
88+
});
89+
90+
it('비밀번호 value와 비밀번호 확인 value가 일치하지 않다면 에러 텍스트를 보여줄 수 있어야 한다.', async () => {
91+
const passwordInput = screen.getByLabelText(/password/i) as HTMLInputElement;
92+
const pwCheckInput = screen.getByLabelText(/pwCheck/i) as HTMLInputElement;
93+
fireEvent.change(passwordInput, {
94+
target: {
95+
value: '123456',
96+
},
97+
});
98+
fireEvent.change(pwCheckInput, {
99+
target: {
100+
value: '123123',
101+
},
102+
});
103+
await waitFor(() => {});
104+
expect(screen.getByText(/ /)).toBeInTheDocument();
105+
});
106+
107+
it('이용약관 및 개인정보 관련 체크를 하지 않으면 에러 텍스트를 보여줄 수 있어야 한다.', async () => {
108+
const useTermsCheckInput = screen.getByLabelText(/useTermsCheck/i) as HTMLInputElement;
109+
const privacyTermsCheckInput = screen.getByLabelText(/privacyTermsCheck/i) as HTMLInputElement;
110+
fireEvent.change(useTermsCheckInput, {
111+
target: {
112+
checked: false,
113+
},
114+
});
115+
fireEvent.change(privacyTermsCheckInput, {
116+
target: {
117+
checked: false,
118+
},
119+
});
120+
await waitFor(() => {});
121+
expect(screen.getByText(/ /)).toBeInTheDocument();
122+
expect(screen.getByText(/ , /)).toBeInTheDocument();
123+
});
124+
125+
it('회원가입 입력정보가 유효하면 회원가입 버튼을 통해 회원가입을 할 수 있어야 한다.', async () => {
126+
const nameInput = screen.getByLabelText(/join-name/i) as HTMLInputElement;
127+
const nickNameInput = screen.getByLabelText(/nickName/i) as HTMLInputElement;
128+
const phoneInput = screen.getByLabelText(/phone/i) as HTMLInputElement;
129+
const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement;
130+
const passwordInput = screen.getByLabelText(/password/i) as HTMLInputElement;
131+
const pwCheckInput = screen.getByLabelText(/pwCheck/i) as HTMLInputElement;
132+
const useTermsCheckInput = screen.getByLabelText(/useTermsCheck/i) as HTMLInputElement;
133+
const privacyTermsCheckInput = screen.getByLabelText(/privacyTermsCheck/i) as HTMLInputElement;
134+
135+
fireEvent.change(nameInput, {
136+
target: {
137+
value: '방시혁',
138+
},
139+
});
140+
fireEvent.change(nickNameInput, {
141+
target: {
142+
value: 'BangTS',
143+
},
144+
});
145+
fireEvent.change(phoneInput, {
146+
target: {
147+
value: '010-1234-1234',
148+
},
149+
});
150+
fireEvent.change(emailInput, {
151+
target: {
152+
153+
},
154+
});
155+
fireEvent.change(passwordInput, {
156+
target: {
157+
value: '111111',
158+
},
159+
});
160+
fireEvent.change(pwCheckInput, {
161+
target: {
162+
value: '111111',
163+
},
164+
});
165+
fireEvent.click(useTermsCheckInput, {
166+
target: {
167+
checked: true,
168+
},
169+
});
170+
fireEvent.click(privacyTermsCheckInput, {
171+
target: {
172+
checked: true,
173+
},
174+
});
175+
await waitFor(() => {});
176+
const submitButton = screen.getByText('회원가입 하기') as HTMLButtonElement;
177+
fireEvent.click(submitButton);
178+
await waitFor(() => {});
179+
expect(supabase.auth.signUp).toBeCalled();
180+
});
181+
182+
it('회원가입 입력정보가 유효하지 않으면 회원가입 버튼을 통해 회원가입을 할 수 없어야 한다.', async () => {
183+
const nameInput = screen.getByLabelText(/join-name/i) as HTMLInputElement;
184+
const nickNameInput = screen.getByLabelText(/nickName/i) as HTMLInputElement;
185+
const phoneInput = screen.getByLabelText(/phone/i) as HTMLInputElement;
186+
const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement;
187+
const passwordInput = screen.getByLabelText(/password/i) as HTMLInputElement;
188+
const pwCheckInput = screen.getByLabelText(/pwCheck/i) as HTMLInputElement;
189+
const useTermsCheckInput = screen.getByLabelText(/useTermsCheck/i) as HTMLInputElement;
190+
const privacyTermsCheckInput = screen.getByLabelText(/privacyTermsCheck/i) as HTMLInputElement;
191+
192+
fireEvent.change(nameInput, {
193+
target: {
194+
value: '방시혁',
195+
},
196+
});
197+
fireEvent.change(nickNameInput, {
198+
target: {
199+
value: 'BangTS',
200+
},
201+
});
202+
fireEvent.change(phoneInput, {
203+
target: {
204+
value: '010-1234-1234',
205+
},
206+
});
207+
fireEvent.change(emailInput, {
208+
target: {
209+
210+
},
211+
});
212+
fireEvent.change(passwordInput, {
213+
target: {
214+
value: '123456',
215+
},
216+
});
217+
fireEvent.change(pwCheckInput, {
218+
target: {
219+
value: '123456',
220+
},
221+
});
222+
fireEvent.change(useTermsCheckInput, {
223+
target: {
224+
checked: true,
225+
},
226+
});
227+
fireEvent.change(privacyTermsCheckInput, {
228+
target: {
229+
checked: true,
230+
},
231+
});
232+
await waitFor(() => {});
233+
const submitButton = screen.getByText('회원가입 하기') as HTMLButtonElement;
234+
fireEvent.click(submitButton);
235+
await waitFor(() => expect(supabase.auth.signUp).toBeCalledTimes(0));
236+
});
237+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// vite.config.ts
2+
import { defineConfig } from "file:///C:/Users/difbf/DateLeaf/node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected]/node_modules/vitest/dist/config.js";
3+
import react from "file:///C:/Users/difbf/DateLeaf/node_modules/.pnpm/@[email protected][email protected]_@[email protected][email protected]_/node_modules/@vitejs/plugin-react-swc/index.mjs";
4+
import { VitePWA } from "file:///C:/Users/difbf/DateLeaf/node_modules/.pnpm/[email protected][email protected]_@[email protected][email protected][email protected]_@ty_2h6ab4jsmzuloqccx4qgq4jgne/node_modules/vite-plugin-pwa/dist/index.js";
5+
import tsConfigPaths from "file:///C:/Users/difbf/DateLeaf/node_modules/.pnpm/[email protected][email protected][email protected]_@[email protected][email protected]_/node_modules/vite-tsconfig-paths/dist/index.mjs";
6+
var vite_config_default = defineConfig({
7+
test: {
8+
environment: "jsdom",
9+
setupFiles: "./src/tests/setup.ts",
10+
globals: true
11+
},
12+
plugins: [react(), VitePWA({ registerType: "autoUpdate" }), tsConfigPaths()]
13+
});
14+
export {
15+
vite_config_default as default
16+
};
17+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFxkaWZiZlxcXFxEYXRlTGVhZlwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiQzpcXFxcVXNlcnNcXFxcZGlmYmZcXFxcRGF0ZUxlYWZcXFxcdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0M6L1VzZXJzL2RpZmJmL0RhdGVMZWFmL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZXN0L2NvbmZpZyc7XG5pbXBvcnQgcmVhY3QgZnJvbSAnQHZpdGVqcy9wbHVnaW4tcmVhY3Qtc3djJztcbmltcG9ydCB7IFZpdGVQV0EgfSBmcm9tICd2aXRlLXBsdWdpbi1wd2EnO1xuaW1wb3J0IHRzQ29uZmlnUGF0aHMgZnJvbSAndml0ZS10c2NvbmZpZy1wYXRocyc7XG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHRlc3Q6IHtcbiAgICBlbnZpcm9ubWVudDogJ2pzZG9tJyxcbiAgICBzZXR1cEZpbGVzOiAnLi9zcmMvdGVzdHMvc2V0dXAudHMnLFxuICAgIGdsb2JhbHM6IHRydWUsXG4gIH0sXG4gIHBsdWdpbnM6IFtyZWFjdCgpLCBWaXRlUFdBKHsgcmVnaXN0ZXJUeXBlOiAnYXV0b1VwZGF0ZScgfSksIHRzQ29uZmlnUGF0aHMoKV0sXG59KTtcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBK1AsU0FBUyxvQkFBb0I7QUFDNVIsT0FBTyxXQUFXO0FBQ2xCLFNBQVMsZUFBZTtBQUN4QixPQUFPLG1CQUFtQjtBQUUxQixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixNQUFNO0FBQUEsSUFDSixhQUFhO0FBQUEsSUFDYixZQUFZO0FBQUEsSUFDWixTQUFTO0FBQUEsRUFDWDtBQUFBLEVBQ0EsU0FBUyxDQUFDLE1BQU0sR0FBRyxRQUFRLEVBQUUsY0FBYyxhQUFhLENBQUMsR0FBRyxjQUFjLENBQUM7QUFDN0UsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K

0 commit comments

Comments
 (0)