From b5928019bf02a2222008d22e1a14ef2f35cdbf88 Mon Sep 17 00:00:00 2001 From: doda Date: Sun, 18 Feb 2024 22:48:33 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=98=81=EA=B0=90=ED=83=B1=20=EC=9D=B5?= =?UTF-8?q?=EC=8A=A4=ED=85=90=EC=85=98=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Dialog.tsx | 11 +++-- src/constants/localStorage.ts | 5 +++ src/libs/api/client.ts | 2 +- src/pages/login/index.tsx | 74 +++++++++++++++++++++++++++++++- 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/components/common/Dialog.tsx b/src/components/common/Dialog.tsx index 75ee3e1c..2af42ea0 100644 --- a/src/components/common/Dialog.tsx +++ b/src/components/common/Dialog.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren, ReactNode, useEffect, useState } from 'react'; -import { css, Theme } from '@emotion/react'; +import { css, Theme, useTheme } from '@emotion/react'; import { motion } from 'framer-motion'; import PortalWrapper from '~/components/common/PortalWrapper'; @@ -10,13 +10,16 @@ import { dimBackdropCss } from './styles'; export interface DialogProps { isShowing?: boolean; actionButtons: ReactNode; + dialogWidth?: number; } export default function Dialog({ isShowing, children, actionButtons, + dialogWidth, }: PropsWithChildren) { + const theme = useTheme(); const [isSSR, setIsSSR] = useState(true); useEffect(() => { @@ -34,7 +37,7 @@ export default function Dialog({ animate="animate" exit="exit" > - +
{children}
{actionButtons}
@@ -54,14 +57,14 @@ const dimBackdropLayoutCss = (theme: Theme) => css` ${dimBackdropCss(theme)} `; -const dialogCss = (theme: Theme) => css` +const dialogCss = (theme: Theme, width = 311) => css` position: relative; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; - width: 311px; + width: ${width}px; min-height: 200px; background-color: ${theme.color.background}; diff --git a/src/constants/localStorage.ts b/src/constants/localStorage.ts index d872d6c6..15109fcd 100644 --- a/src/constants/localStorage.ts +++ b/src/constants/localStorage.ts @@ -2,3 +2,8 @@ export const localStorageUserTokenKeys = { accessToken: 'ygtlsat', refreshToken: 'ygtrfhtk', } as const; + +export const localStorageExtensionKeys = { + use: 'use-ygtang-extension', + refreshToken: 'ygte-refresh', +} as const; diff --git a/src/libs/api/client.ts b/src/libs/api/client.ts index ce82c774..5e33f072 100644 --- a/src/libs/api/client.ts +++ b/src/libs/api/client.ts @@ -6,7 +6,7 @@ import CustomException from '~/exceptions/CustomException'; import { errorMessage } from '~/exceptions/messages'; import { ApiErrorScheme } from '~/exceptions/type'; -const DEVELOPMENT_API_URL = 'https://api.ygtang.xyz/api'; +const DEVELOPMENT_API_URL = 'https://ygtang.kr/api'; // TODO: 개발 서버 사망에 따른 개발 버전에서도 프로덕션 사용 const PRODUCTION_API_URL = 'https://ygtang.kr/api'; export const instance = axios.create({ diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 85b92dcb..13f50211 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -1,10 +1,13 @@ import { FormEvent, useEffect, useState } from 'react'; import { css, Theme } from '@emotion/react'; -import { CTAButton, GhostButton } from '~/components/common/Button'; +import { CTAButton, FilledButton, GhostButton } from '~/components/common/Button'; +import Dialog from '~/components/common/Dialog'; import SEO from '~/components/common/SEO'; import TextField from '~/components/common/TextField'; +import { localStorageExtensionKeys } from '~/constants/localStorage'; import useMemberLoginMutation from '~/hooks/api/member/useMemberLoginMutation'; +import useReissueMutation from '~/hooks/api/reissue/useReissueMutation'; import useDidUpdate from '~/hooks/common/useDidUpdate'; import useInput from '~/hooks/common/useInput'; import useInternalRouter from '~/hooks/common/useInternalRouter'; @@ -19,6 +22,7 @@ export default function Login() { const email = useInput({}); const password = useInput({}); const [isPending, setIsPending] = useState(false); + const [canExtensionLogin, setCanExtensionLogin] = useState(false); const [emailError, setEmailError] = useState(''); const [passwordError, setPasswordError] = useState(''); const { userLogin } = useUser(); @@ -30,6 +34,27 @@ export default function Login() { data: loginMutationData, error: loginMutationError, } = useMemberLoginMutation(); + const { mutate: reissueMutate } = useReissueMutation({ + onSuccess: ({ data }) => { + userLogin({ + accessToken: data.accessToken, + refreshToken: data.refreshToken, + }); + setIsPending(false); + recordEvent({ action: 'Login', value: '로그인 화면에서 익스텐션 계정으로 로그인' }); + + if (getRedirect()) { + goRedirect(); + } else { + push('/'); + } + }, + onError: () => { + fireToast({ content: '익스텐션 계정으로 로그인하는데 실패했습니다.' }); + setCanExtensionLogin(false); + setIsPending(false); + }, + }); const handleFormSubmitEvent = (e: FormEvent) => { e.preventDefault(); @@ -46,6 +71,14 @@ export default function Login() { }); }; + const handleExtensionLogin = () => { + setIsPending(true); + const token = localStorage.getItem(localStorageExtensionKeys.refreshToken); + if (token) { + reissueMutate({ refreshToken: token }); + } + }; + useDidUpdate(() => { if (!validator({ type: 'email', value: email.value })) { setEmailError('올바른 이메일을 입력해주세요.'); @@ -86,6 +119,16 @@ export default function Login() { } }, [fireToast, loginMutationError]); + useEffect(() => { + if ( + localStorage && + localStorage.getItem(localStorageExtensionKeys.use) && + localStorage.getItem(localStorageExtensionKeys.refreshToken) + ) { + setCanExtensionLogin(true); + } + }, []); + return ( <> @@ -127,6 +170,30 @@ export default function Login() { 빠르게 가입하기 + + setCanExtensionLogin(false)} + disabled={isPending} + > + 다른 계정 + +
+ + 익스텐션 계정 + +
+ + } + > + 영감탱 익스텐션에 로그인되어 있습니다. +
+ 익스텐션 계정으로 로그인할까요? +
); @@ -166,3 +233,8 @@ const signUpTextWrapperCss = (theme: Theme) => css` font-size: 10px; line-height: 150%; `; + +const dialogLongButtonCss = css` + width: 160px; + flex-shrink: 0; +`;