Skip to content

Commit

Permalink
Merge pull request #56 from kkkkkSE/feat/signup
Browse files Browse the repository at this point in the history
[feat] 회원가입 페이지 구현 및 테스트 코드 작성
  • Loading branch information
kkkkkSE authored Aug 27, 2023
2 parents e72daa8 + 70fa39c commit ac21c07
Show file tree
Hide file tree
Showing 10 changed files with 641 additions and 37 deletions.
61 changes: 61 additions & 0 deletions src/components/sign-up/SignUpForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';

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

import SignUpForm from './SignUpForm';

const context = describe;

const userType = 'company';

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

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

describe('<SignUpForm />', () => {
it('render sign up form', () => {
render(<SignUpForm
userType={userType}
/>);

screen.getByLabelText(/비밀번호 확인/);

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

context('user type is company', () => {
it('name label is "기업명"', () => {
render(<SignUpForm
userType="company"
/>);

screen.getByLabelText(/기업명/);
});
});

context('user type is customer', () => {
it('name label is "이름"', () => {
render(<SignUpForm
userType="customer"
/>);

screen.getByLabelText(/이름/);
});
});

context('when submitting form', () => {
it('execute signUp function in store', async () => {
render(<SignUpForm
userType={userType}
/>);

fireEvent.submit(screen.getByTestId('sign-up-form'));

await waitFor(() => {
expect(store.signUp).toHaveBeenCalled();
});
});
});
});
137 changes: 137 additions & 0 deletions src/components/sign-up/SignUpForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { useEffect } from 'react';

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

import { Controller, useForm } from 'react-hook-form';

import styled from 'styled-components';

import useSignUpFormStore from '../../hooks/useSignUpFormStore';

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

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

interface SignUpFormProps {
userType: string;
}

export default function SignUpForm({ userType }: SignUpFormProps) {
const navigate = useNavigate();

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

interface FormValues {
name: string;
username: string;
password: string;
confirmPassword: string;
}

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

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

navigate(`${STATIC_ROUTES.LOGIN}?type=${userType}`);
}
}, [done]);

const handleCheckSpace = (
inputValue: string,
onChange: (value: string) => void,
) => {
const changeValue = inputValue.includes(' ')
? inputValue.replaceAll(' ', '')
: inputValue;

onChange(changeValue);
};

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

return (
<Container>
<form onSubmit={handleSubmit(onSubmit)} data-testid="sign-up-form">
<Controller
control={control}
name="name"
render={({ field: { value, onChange } }) => (
<TextBox
label={userType === 'company' ? '기업명' : '이름'}
value={value}
onChange={onChange}
placeholder="20자 이하"
maxLength={20}
/>
)}
/>

<Controller
control={control}
name="username"
render={({ field: { value, onChange } }) => (
<TextBox
label="아이디"
value={value}
onChange={(inputValue) => handleCheckSpace(inputValue, onChange)}
placeholder="6~20자 영문 또는 숫자 조합"
maxLength={20}
/>
)}
/>

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

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

<Button marginTop type="submit">
가입하기
</Button>
</form>

{errorMessage && <ErrorMessage>{errorMessage}</ErrorMessage>}
</Container>
);
}

const Container = styled.div`
width: 100%;
input::placeholder{
color:black;
}
`;
7 changes: 5 additions & 2 deletions src/components/ui/TextBox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import React, { useState } from 'react';

import styled from 'styled-components';

Expand All @@ -8,6 +8,7 @@ type TextBoxProps = {
type?: 'text' | 'password';
value: string;
onChange?: (value: string) => void;
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
readOnly?: boolean;
maxLength?: number;
showLength?: boolean;
Expand All @@ -19,11 +20,12 @@ export default function TextBox({
type = 'text',
value,
onChange = undefined,
onKeyDown = undefined,
readOnly = false,
maxLength = undefined,
showLength = false,
}: TextBoxProps) {
const [textLength, setTextLength] = useState(value.length);
const [textLength, setTextLength] = useState(value?.length || 0);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = event.target.value;
Expand All @@ -46,6 +48,7 @@ export default function TextBox({
placeholder={placeholder}
value={value}
onChange={handleChange}
onKeyDown={onKeyDown}
readOnly={readOnly}
/>
</label>
Expand Down
13 changes: 13 additions & 0 deletions src/hooks/useSignUpFormStore.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 SignUpFormStore from '../stores/SignUpFormStore';

const useSignUpFormStore = () => {
const store = container.resolve(SignUpFormStore);

return useStore(store);
};

export default useSignUpFormStore;
35 changes: 21 additions & 14 deletions src/pages/SignUpPage.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { useSearchParams } from 'react-router-dom';
import { useEffect } from 'react';

import Button from '../components/ui/Button';
import TextBox from '../components/ui/TextBox';
import { useNavigate, useSearchParams } from 'react-router-dom';

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

import SignUpForm from '../components/sign-up/SignUpForm';

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

function SignUpPage() {
const [searchParams] = useSearchParams();

const userType = searchParams.get('type');
const userType = searchParams.get('type') || '';

useEffect(() => {
const validUserTypes = ['company', 'customer'];

if (!validUserTypes.includes(userType)) {
navigate(STATIC_ROUTES.HOME);
}
}, []);

return (
<div>
<TextBox label={userType === 'company' ? '기업명' : '이름'} value="" />
<TextBox label="아이디" value="" />
<TextBox label="비밀번호" value="" />
<TextBox label="비밀번호 확인" value="" />
<Button marginTop>가입하기</Button>
</div>
<SignUpForm
userType={userType}
/>
);
}

export default SignUpPage;
38 changes: 18 additions & 20 deletions src/routes.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,26 +85,24 @@ describe('routes', () => {
});
});

// TODO : 회원가입 구현 후 주석 해제

// describe('when the current path is "/sign-up"', () => {
// context('without user type', () => {
// it('redirect to home page', () => {
// setupRouterProvider(STATIC_ROUTES.SIGN_UP);

// screen.getByText(/회원 유형을 선택해주세요/);
// });
// });

// context('with user type', () => {
// it('renders <SignUpPage />', () => {
// setupRouterProvider(`${STATIC_ROUTES.SIGN_UP)}?type=${user.type}`);

// screen.getByLabelText(/비밀번호 확인/);
// screen.getByRole('button', { name: /가입하기/ });
// });
// });
// });
describe('when the current path is "/sign-up"', () => {
context('without user type', () => {
it('redirect to home page', () => {
setupRouterProvider(STATIC_ROUTES.SIGN_UP);

screen.getByText(/회원 유형을 선택해주세요/);
});
});

context('with user type', () => {
it('renders <SignUpPage />', () => {
setupRouterProvider(`${STATIC_ROUTES.SIGN_UP}?type=${user.type}`);

screen.getByLabelText(/비밀번호 확인/);
screen.getByRole('button', { name: /가입하기/ });
});
});
});
});

describe('after login', () => {
Expand Down
17 changes: 17 additions & 0 deletions src/services/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ export default class ApiService {
);
}

async signUp({
type, name, username, password, confirmPassword,
}: {
type: string;
name: string;
username: string;
password: string;
confirmPassword: string;
}) {
await this.instance.post(
DYNAMIC_API_PATHS.SIGN_UP(type),
{
name, username, password, confirmPassword,
},
);
}

async login({ type, username, password } : {
type: string;
username: string;
Expand Down
Loading

0 comments on commit ac21c07

Please sign in to comment.