From b4ac842f2ef6f675dd26c797d6cd31db8fe6a845 Mon Sep 17 00:00:00 2001 From: HongBeen Date: Sun, 21 Aug 2022 19:36:23 +0900 Subject: [PATCH 1/6] WIP:: adding login --- apps/zoopi-web/hooks/index.ts | 0 apps/zoopi-web/services/auth.service.ts | 53 +++++++++++++++++++++++++ apps/zoopi-web/services/index.ts | 2 + apps/zoopi-web/utils/index.ts | 1 + 4 files changed, 56 insertions(+) create mode 100644 apps/zoopi-web/hooks/index.ts create mode 100644 apps/zoopi-web/services/auth.service.ts create mode 100644 apps/zoopi-web/services/index.ts create mode 100644 apps/zoopi-web/utils/index.ts diff --git a/apps/zoopi-web/hooks/index.ts b/apps/zoopi-web/hooks/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/zoopi-web/services/auth.service.ts b/apps/zoopi-web/services/auth.service.ts new file mode 100644 index 0000000..a7c2f91 --- /dev/null +++ b/apps/zoopi-web/services/auth.service.ts @@ -0,0 +1,53 @@ +import { RequestService } from './request.service'; +import { Token } from '@web/utils'; + +class AuthService { + static async signin(username: string, password: string) { + const res = await RequestService.postRequest< + { + username: string; + password: string; + }, + { + data: { + accessToken: string; + refreshToken: string; + }; + } + >('/auth/signin', { + username, + password, + }); + + Token.setAccessToken(res.data.accessToken); + Token.setRefreshToken(res.data.refreshToken); + } + + static async signup(authenticationKey, password, phone, username) { + const res = await RequestService.postRequest< + { + authenticationKey: string; + password: string; + passwordCheck: string; + phone: string; + username: string; + }, + { + data: { + accessToken: string; + refreshToken: string; + }; + } + >('auth/signup', { + authenticationKey, + password, + passwordCheck: password, + phone, + username, + }); + + // TODO: signup 명세서 업데이트되면 수정 예정 + } +} + +export default AuthService; diff --git a/apps/zoopi-web/services/index.ts b/apps/zoopi-web/services/index.ts new file mode 100644 index 0000000..b5803ac --- /dev/null +++ b/apps/zoopi-web/services/index.ts @@ -0,0 +1,2 @@ +export { default as AuthService } from './auth.service'; +export { default as RequestService } from './request.service'; diff --git a/apps/zoopi-web/utils/index.ts b/apps/zoopi-web/utils/index.ts new file mode 100644 index 0000000..e249269 --- /dev/null +++ b/apps/zoopi-web/utils/index.ts @@ -0,0 +1 @@ +export { default as Token } from './token.util'; From 24a620212dff412ded45e454555f6c5354a4a1b5 Mon Sep 17 00:00:00 2001 From: HongBeen Date: Mon, 22 Aug 2022 00:48:30 +0900 Subject: [PATCH 2/6] feat: add libraries --- apps/zoopi-web/.gitignore | 3 +- .../components/ModalLogin/ModalLogin.tsx | 44 ++++++++++++---- .../components/TextInput/TextInput.tsx | 32 ++++++------ apps/zoopi-web/hooks/index.ts | 2 + apps/zoopi-web/hooks/useRequest.tsx | 14 ++++++ apps/zoopi-web/hooks/useSignin.tsx | 4 ++ apps/zoopi-web/pages/_app.page.tsx | 13 +++-- apps/zoopi-web/services/auth.service.ts | 14 ++---- apps/zoopi-web/services/request.service.ts | 50 +++++++++++++++++++ apps/zoopi-web/utils/token.util.ts | 25 ++++++++++ package.json | 3 ++ yarn.lock | 34 +++++++++++++ 12 files changed, 198 insertions(+), 40 deletions(-) create mode 100644 apps/zoopi-web/hooks/useRequest.tsx create mode 100644 apps/zoopi-web/hooks/useSignin.tsx create mode 100644 apps/zoopi-web/services/request.service.ts create mode 100644 apps/zoopi-web/utils/token.util.ts diff --git a/apps/zoopi-web/.gitignore b/apps/zoopi-web/.gitignore index 26b4fb0..6826199 100644 --- a/apps/zoopi-web/.gitignore +++ b/apps/zoopi-web/.gitignore @@ -1,2 +1,3 @@ .storybook/dist -*.tsbuildinfo \ No newline at end of file +*.tsbuildinfo +.env \ No newline at end of file diff --git a/apps/zoopi-web/components/ModalLogin/ModalLogin.tsx b/apps/zoopi-web/components/ModalLogin/ModalLogin.tsx index 9eddb3f..e322cce 100644 --- a/apps/zoopi-web/components/ModalLogin/ModalLogin.tsx +++ b/apps/zoopi-web/components/ModalLogin/ModalLogin.tsx @@ -17,8 +17,7 @@ import { TextInputPassword, TextInputPasswordProps, } from '@web/components/TextInputPassword'; - -const BUTTON_WIDTH = 400; +import { useSignin } from '@web/hooks'; export type ModalLoginProps = { onClose: () => void; @@ -26,18 +25,37 @@ export type ModalLoginProps = { export const ModalLogin = ({ onClose }: ModalLoginProps) => { const theme = useTheme(); + const { mutate } = useSignin(); + const emailRef = useRef(null); const passwordRef = useRef(null); + const BUTTON_WIDTH = 400; - const handleSubmitLogin: FormEventHandler = useCallback((event) => { - event.preventDefault(); + const handleSubmitLogin: FormEventHandler = useCallback( + (event) => { + event.preventDefault(); - const email = emailRef?.current?.value; - const password = passwordRef?.current?.value; + const email = emailRef?.current?.value; + const password = passwordRef?.current?.value; - // eslint-disable-next-line no-console - console.log(email, password); - }, []); + // eslint-disable-next-line no-console + console.log(email, password); + mutate( + { username: email, password }, + { + onSuccess: (data) => { + // eslint-disable-next-line no-console + console.log(`로그인 성공!!: ${data}`); + }, + onError: (error) => { + // eslint-disable-next-line no-console + console.log(`로그인 실패: ${error}`); + }, + } + ); + }, + [mutate] + ); const ForwardedComponent = ( Component: ElementType, @@ -56,6 +74,7 @@ export const ModalLogin = ({ onClose }: ModalLoginProps) => { { label: '이메일', placeholder: 'sample@example.co.kr', type: 'email' }, 'EmailInput' ); + const PasswordField = ForwardedComponent( TextInputPassword, {}, @@ -110,7 +129,12 @@ export const ModalLogin = ({ onClose }: ModalLoginProps) => {
- diff --git a/apps/zoopi-web/components/TextInput/TextInput.tsx b/apps/zoopi-web/components/TextInput/TextInput.tsx index 609dc86..c958cd3 100644 --- a/apps/zoopi-web/components/TextInput/TextInput.tsx +++ b/apps/zoopi-web/components/TextInput/TextInput.tsx @@ -8,6 +8,7 @@ import { useMemo, useId, useState, + MutableRefObject, } from 'react'; import { Icon } from '@web/components/Icon'; import { Css, CssObject } from '@web/styles/theme'; @@ -23,22 +24,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) => { +export const TextInput = ( + { + children, + onChange, + onBlur, + onFocus, + value, + label, + clearDisabled = false, + right, + type = 'text', + ...rest + }: TextInputProps, + ref: MutableRefObject +) => { const theme = useTheme(); const componentId = useId(); @@ -120,7 +122,7 @@ export const TextInput = ({ onChange={handleChange} onBlur={handleBlur} onFocus={handleFocus} - ref={forwardedRef} + ref={ref} css={css} />
diff --git a/apps/zoopi-web/hooks/index.ts b/apps/zoopi-web/hooks/index.ts index e69de29..9a0e19f 100644 --- a/apps/zoopi-web/hooks/index.ts +++ b/apps/zoopi-web/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './useSignin'; +export * from './useRequest'; diff --git a/apps/zoopi-web/hooks/useRequest.tsx b/apps/zoopi-web/hooks/useRequest.tsx new file mode 100644 index 0000000..a640772 --- /dev/null +++ b/apps/zoopi-web/hooks/useRequest.tsx @@ -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) => + useMutation(queryFn); diff --git a/apps/zoopi-web/hooks/useSignin.tsx b/apps/zoopi-web/hooks/useSignin.tsx new file mode 100644 index 0000000..00dd37c --- /dev/null +++ b/apps/zoopi-web/hooks/useSignin.tsx @@ -0,0 +1,4 @@ +import { useMutation } from '@tanstack/react-query'; +import { AuthService } from '@web/services'; + +export const useSignin = () => useMutation(AuthService.signin); diff --git a/apps/zoopi-web/pages/_app.page.tsx b/apps/zoopi-web/pages/_app.page.tsx index a4d77e8..d0c4cd9 100644 --- a/apps/zoopi-web/pages/_app.page.tsx +++ b/apps/zoopi-web/pages/_app.page.tsx @@ -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) => ( - - - - + + + + + + ); export default CustomApp; diff --git a/apps/zoopi-web/services/auth.service.ts b/apps/zoopi-web/services/auth.service.ts index a7c2f91..c5a1a31 100644 --- a/apps/zoopi-web/services/auth.service.ts +++ b/apps/zoopi-web/services/auth.service.ts @@ -2,29 +2,23 @@ import { RequestService } from './request.service'; import { Token } from '@web/utils'; class AuthService { - static async signin(username: string, password: string) { + static async signin(user: { username: string; password: string }) { const res = await RequestService.postRequest< - { - username: string; - password: string; - }, + typeof user, { data: { accessToken: string; refreshToken: string; }; } - >('/auth/signin', { - username, - password, - }); + >('/auth/signin', user); Token.setAccessToken(res.data.accessToken); Token.setRefreshToken(res.data.refreshToken); } static async signup(authenticationKey, password, phone, username) { - const res = await RequestService.postRequest< + await RequestService.postRequest< { authenticationKey: string; password: string; diff --git a/apps/zoopi-web/services/request.service.ts b/apps/zoopi-web/services/request.service.ts new file mode 100644 index 0000000..20dbcd5 --- /dev/null +++ b/apps/zoopi-web/services/request.service.ts @@ -0,0 +1,50 @@ +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( + route: string, + token?: string, + customHeader?: AxiosRequestConfig['headers'] + ) { + const res = await axios.get( + this.baseUrl + route, + token + ? { + headers: { + ...customHeader, + Authorization: `Bearer ${token}`, + }, + } + : customHeader + ); + return res; + } + + static async postRequest( + route: string, + body: any, + token?: string, + customHeader?: AxiosRequestConfig['headers'] + ) { + const res = await axios.post( + this.baseUrl + route, + body, + token + ? { + headers: { + Authorization: `Bearer ${token}`, + }, + } + : customHeader + ); + return res; + } +} + +export default RequestService; diff --git a/apps/zoopi-web/utils/token.util.ts b/apps/zoopi-web/utils/token.util.ts new file mode 100644 index 0000000..64a2691 --- /dev/null +++ b/apps/zoopi-web/utils/token.util.ts @@ -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; diff --git a/package.json b/package.json index bdf14fb..92bb54b 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,10 @@ "@emotion/server": "11.4.0", "@emotion/styled": "11.8.1", "@nrwl/next": "14.1.9", + "@tanstack/react-query": "^4.2.1", "core-js": "^3.6.5", "emotion-reset": "^3.0.1", + "js-cookie": "^3.0.1", "next": "12.1.5", "react": "18.1.0", "react-dom": "18.1.0", @@ -54,6 +56,7 @@ "@storybook/testing-library": "^0.0.12", "@testing-library/react": "13.1.1", "@types/jest": "27.4.1", + "@types/js-cookie": "^3.0.2", "@types/node": "16.11.7", "@types/react": "18.0.8", "@types/react-dom": "18.0.3", diff --git a/yarn.lock b/yarn.lock index 60aa8b9..ed4de78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3371,6 +3371,20 @@ content-type "^1.0.4" tslib "^2.4.0" +"@tanstack/query-core@^4.0.0-beta.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.2.1.tgz#21ff3a33f27bf038c990ea53af89cf7c7e8078fc" + integrity sha512-UOyOhHKLS/5i9qG2iUnZNVV3R9riJJmG9eG+hnMFIPT/oRh5UzAfjxCtBneNgPQZLDuP8y6YtRYs/n4qVAD5Ng== + +"@tanstack/react-query@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.2.1.tgz#1f00f03573b35a353e62fa64f904bbb0286a1808" + integrity sha512-w02oTOYpoxoBzD/onAGRQNeLAvggLn7WZjS811cT05WAE/4Q3br0PTp388M7tnmyYGbgOOhFq0MkhH0wIfAKqA== + dependencies: + "@tanstack/query-core" "^4.0.0-beta.1" + "@types/use-sync-external-store" "^0.0.3" + use-sync-external-store "^1.2.0" + "@testing-library/dom@^8.3.0", "@testing-library/dom@^8.5.0": version "8.13.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5" @@ -3597,6 +3611,11 @@ jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" +"@types/js-cookie@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.2.tgz#451eaeece64c6bdac8b2dde0caab23b085899e0d" + integrity sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA== + "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -3817,6 +3836,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + "@types/webpack-env@^1.16.0", "@types/webpack-env@^1.17.0": version "1.17.0" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.17.0.tgz#f99ce359f1bfd87da90cc4a57cab0a18f34a48d0" @@ -10070,6 +10094,11 @@ jest@27.5.1: import-local "^3.0.2" jest-cli "^27.5.1" +js-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414" + integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw== + js-string-escape@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" @@ -14925,6 +14954,11 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" From 051ee7b2e05b0e4ada31e4b988df8b795846ec6b Mon Sep 17 00:00:00 2001 From: HongBeen Date: Sun, 4 Sep 2022 16:13:21 +0900 Subject: [PATCH 3/6] feat:: change how to forward Ref --- .../components/ModalLogin/ModalLogin.tsx | 37 ++----------------- .../TextInput/TextInput.stories.tsx | 2 +- .../components/TextInput/TextInput.tsx | 5 ++- apps/zoopi-web/components/TextInput/index.ts | 1 + 4 files changed, 9 insertions(+), 36 deletions(-) diff --git a/apps/zoopi-web/components/ModalLogin/ModalLogin.tsx b/apps/zoopi-web/components/ModalLogin/ModalLogin.tsx index d5dedc6..b884c5d 100644 --- a/apps/zoopi-web/components/ModalLogin/ModalLogin.tsx +++ b/apps/zoopi-web/components/ModalLogin/ModalLogin.tsx @@ -4,19 +4,12 @@ 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'; +import { TextInput } from '@web/components/TextInput'; import { useSignin } from '@web/hooks'; export type ModalLoginProps = { @@ -57,30 +50,6 @@ export const ModalLogin = ({ onClose }: ModalLoginProps) => { [mutate] ); - const ForwardedComponent = ( - Component: ElementType, - props: T, - displayName: string - ) => { - const forwardedRef = (_, ref: MutableRefObject) => ( - - ); - forwardedRef.displayName = displayName; - return forwardRef(forwardedRef); - }; - - const EmailField = ForwardedComponent( - TextInput, - { label: '이메일', placeholder: 'sample@example.co.kr', type: 'email' }, - 'EmailInput' - ); - - const PasswordField = ForwardedComponent( - TextInputPassword, - {}, - 'PasswordInput' - ); - return (
{ }} >
- +
- +