Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/login logic #24

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/zoopi-web/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.storybook/dist
*.tsbuildinfo
*.tsbuildinfo
.env
86 changes: 44 additions & 42 deletions apps/zoopi-web/components/ModalLogin/ModalLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,59 @@ import {
useCallback,
FormEventHandler,
useRef,
forwardRef,
MutableRefObject,
ElementType,
} from 'react';
import { Button } from '@web/components/Button';
import { Icon } from '@web/components/Icon';
import { Logo } from '@web/components/Logo';
import { Modal } from '@web/components/Modal';
import { TextInput, TextInputProps } from '@web/components/TextInput';
import {
TextInputPassword,
TextInputPasswordProps,
} from '@web/components/TextInputPassword';

const BUTTON_WIDTH = 400;
import { TextInput } from '@web/components/TextInput';
import { useSignin } from '@web/hooks';
import { validateId, validatePassword } from '@web/utils';

export type ModalLoginProps = {
onClose: () => void;
};

export const ModalLogin = ({ onClose }: ModalLoginProps) => {
const theme = useTheme();
const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const { mutate } = useSignin();

const handleSubmitLogin: FormEventHandler = useCallback((event) => {
event.preventDefault();
const idRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const BUTTON_WIDTH = 400;

const email = emailRef?.current?.value;
const password = passwordRef?.current?.value;
const handleSubmitLogin: FormEventHandler = useCallback(
(event) => {
event.preventDefault();

// eslint-disable-next-line no-console
console.log(email, password);
}, []);
const id = idRef?.current?.value;
const password = passwordRef?.current?.value;

const ForwardedComponent = <T,>(
Component: ElementType,
props: T,
displayName: string
) => {
const forwardedRef = (_, ref: MutableRefObject<HTMLInputElement>) => (
<Component forwardedRef={ref} {...props} />
);
forwardedRef.displayName = displayName;
return forwardRef(forwardedRef);
};
if(!validateId(id)) {
alert('아이디는 6글자 이상 영문자(대/소)+숫자 조합이어야 합니다.');
return;
}
if(!validatePassword(password)){
// eslint-disable-next-line
alert(`비밀번호는 영문자(대/소)+숫자+특수문자 3가지 조합 10자리 이상이어야 합니다. ${"\n"}사용 가능한 특수문자: # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ${"] ^ _ ` { | } ~ \ "}`);
return;
}

const EmailField = ForwardedComponent<TextInputProps>(
TextInput,
{ label: '이메일', placeholder: 'sample@example.co.kr', type: 'email' },
'EmailInput'
);
const PasswordField = ForwardedComponent<TextInputPasswordProps>(
TextInputPassword,
{},
'PasswordInput'
mutate(
{ username: id, password },
{
onSettled: (data)=>{
const { code } = data;
if(code==='R-M006'){
onClose();
}else{
alert(`${data.message}`)
}
}
}
);
},
[mutate,onClose]
);

return (
Expand Down Expand Up @@ -104,13 +101,18 @@ export const ModalLogin = ({ onClose }: ModalLoginProps) => {
}}
>
<div css={{ marginBottom: 32 }}>
<EmailField ref={emailRef} />
<TextInput label='아이디' placeholder='sample@example.co.kr' type='text' ref={idRef} />
</div>

<div css={{ marginBottom: 32 }}>
<PasswordField ref={passwordRef} />
<TextInput label='비밀번호' type='password' ref={passwordRef} />
</div>
<Button color='main' appearance='filled' css={{ fontWeight: 700 }}>
<Button
type='submit'
color='main'
appearance='filled'
css={{ fontWeight: 700 }}
>
로그인
</Button>
</form>
Expand Down
2 changes: 1 addition & 1 deletion apps/zoopi-web/components/TextInput/TextInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';

import { TextInput } from './TextInput';
import { TextInput } from './index';

export default {
title: 'atoms/TextInput',
Expand Down
35 changes: 20 additions & 15 deletions apps/zoopi-web/components/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
useMemo,
useId,
useState,
MutableRefObject,
forwardRef,
} from 'react';
import { Icon } from '@web/components/Icon';
import { Css, CssObject } from '@web/styles/theme';
Expand All @@ -23,22 +25,23 @@ export type TextInputProps = {
type?: 'email' | 'password' | 'text';
clearDisabled?: boolean;
right?: ReactNode;
forwardedRef?: string;
};

export const TextInput = ({
children,
onChange,
onBlur,
onFocus,
value,
label,
clearDisabled = false,
right,
type = 'text',
forwardedRef,
...rest
}: TextInputProps) => {
const TextInput = (
{
children,
onChange,
onBlur,
onFocus,
value,
label,
clearDisabled = false,
right,
type = 'text',
...rest
}: TextInputProps,
ref?: MutableRefObject<HTMLInputElement>
) => {
const theme = useTheme();
const componentId = useId();

Expand Down Expand Up @@ -120,7 +123,7 @@ export const TextInput = ({
onChange={handleChange}
onBlur={handleBlur}
onFocus={handleFocus}
ref={forwardedRef}
ref={ref}
css={css}
/>
<div css={{ position: 'absolute', right: 0, bottom: 6 }}>
Expand All @@ -143,3 +146,5 @@ export const TextInput = ({
</label>
);
};

export default forwardRef<HTMLInputElement, TextInputProps>(TextInput)
1 change: 1 addition & 0 deletions apps/zoopi-web/components/TextInput/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './TextInput';
export { default as TextInput } from './TextInput';
2 changes: 2 additions & 0 deletions apps/zoopi-web/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './useSignin';
export * from './useRequest';
14 changes: 14 additions & 0 deletions apps/zoopi-web/hooks/useRequest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useMutation, useQuery } from '@tanstack/react-query';

type Options = {
refetchInterval?: number;
};

export const useGetRequest = (
queryName: string[],
queryFn: () => void,
options?: Options
) => useQuery(queryName, queryFn, { ...options });

export const usePostRequest = (queryFn: () => Promise<any>) =>
useMutation(queryFn);
4 changes: 4 additions & 0 deletions apps/zoopi-web/hooks/useSignin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { useMutation } from '@tanstack/react-query';
import { AuthService } from '@web/services';

export const useSignin = () => useMutation(AuthService.signin)
13 changes: 9 additions & 4 deletions apps/zoopi-web/pages/_app.page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AppProps } from 'next/app';
import { ThemeProvider, GlobalStyle } from '@web/styles';

const queryClient = new QueryClient();

const CustomApp = ({ Component, pageProps }: AppProps) => (
<ThemeProvider>
<GlobalStyle />
<Component {...pageProps} />
</ThemeProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<GlobalStyle />
<Component {...pageProps} />
</ThemeProvider>
</QueryClientProvider>
);

export default CustomApp;
55 changes: 55 additions & 0 deletions apps/zoopi-web/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { RequestService } from './request.service';
import { Token } from '@web/utils';

class AuthService {
static async signin(user: { username: string; password: string }) {
const { data } = await RequestService.postRequest<
typeof user,
{
data:{
code:string,
message: string,
status: string,
data: {
accessToken: string;
refreshToken: string;
}
}
}
>('/auth/signin', user);

Token.setAccessToken(data.data.accessToken);
Token.setRefreshToken(data.data.refreshToken);

return data;
}

static async signup(authenticationKey, password, phone, username) {
await RequestService.postRequest<
{
authenticationKey: string;
password: string;
passwordCheck: string;
phone: string;
username: string;
},
{
data: {
data: {
accessToken: string;
refreshToken: string;
}};
}
>('auth/signup', {
authenticationKey,
password,
passwordCheck: password,
phone,
username,
});

// TODO: signup 명세서 업데이트되면 수정 예정
}
}

export default AuthService;
2 changes: 2 additions & 0 deletions apps/zoopi-web/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as AuthService } from './auth.service';
export { default as RequestService } from './request.service';
47 changes: 47 additions & 0 deletions apps/zoopi-web/services/request.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import axios, { AxiosRequestConfig } from 'axios';

export class RequestService {
static baseUrl = process.env.NEXT_PUBLIC_API_HOST;

constructor(baseUrl?: string) {
if (baseUrl) RequestService.baseUrl = baseUrl;
}

static async getRequest<RequestT>(
route: string,
token?: string,
customHeader?: AxiosRequestConfig['headers']
) {
const res = await axios.get<RequestT>(
this.baseUrl + route,
{
headers: {
...customHeader,
Authorization: `Bearer ${token}`,
},
}
);
return res;
}

static async postRequest<RequestT, ResponseT>(
route: string,
body: any,
token?: string,
customHeader?: AxiosRequestConfig['headers']
) {
const res = await axios.post<RequestT, ResponseT>(
this.baseUrl + route,
body,
{
headers: {
...customHeader,
Authorization: `Bearer ${token}`,
},
}
);
return res;
}
}

export default RequestService;
2 changes: 2 additions & 0 deletions apps/zoopi-web/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as Token } from './token.util';
export * from './validate'
25 changes: 25 additions & 0 deletions apps/zoopi-web/utils/token.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import cookies from 'js-cookie';

class Token {
static ACCESS = 'access';

static REFRESH = 'refresh';

static getAccessToken(): string | undefined {
return cookies.get(this.ACCESS);
}

static getRefreshToken(): string | undefined {
return cookies.get(this.REFRESH);
}

static setAccessToken(access: string): void {
cookies.set(this.ACCESS, access, { expires: 1 });
}

static setRefreshToken(refresh: string): void {
cookies.set(this.REFRESH, refresh, { expires: 7 });
}
}

export default Token;
10 changes: 10 additions & 0 deletions apps/zoopi-web/utils/validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

export const validateId = (id:string) => {
const isValid = id.match(/^[A-Za-z0-9]{6,20}$/);
return isValid;
}

export const validatePassword = (password:string) => {
const isValid = password.match(/^.(?=^.{10,20}$)(?=.\d)(?=.[a-zA-Z])(?=.[`~!@#$%^&()+=]).$/);
return isValid;
}
Loading