Skip to content

Commit

Permalink
Merge pull request #622 from depromeet/develop
Browse files Browse the repository at this point in the history
240304 배포
  • Loading branch information
hyesungoh committed Mar 4, 2024
2 parents ceee2da + 76bf1cd commit c049907
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @SenseCodeValue @hyesungoh @ddarkr @positiveko
* @SenseCodeValue @hyesungoh @ddarkr
1 change: 0 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,4 @@
혜성 : hyesungoh
도현 : ddarkr
대윤 : SenseCodeValue
은정 : positiveko
-->
Binary file removed public/InspirationEmpty.png
Binary file not shown.
Binary file added public/insp-empty.webp
Binary file not shown.
11 changes: 7 additions & 4 deletions src/components/common/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<DialogProps>) {
const theme = useTheme();
const [isSSR, setIsSSR] = useState(true);

useEffect(() => {
Expand All @@ -34,7 +37,7 @@ export default function Dialog({
animate="animate"
exit="exit"
>
<motion.div css={dialogCss} variants={defaultFadeInUpVariants}>
<motion.div css={dialogCss(theme, dialogWidth)} variants={defaultFadeInUpVariants}>
<div css={dialogContentWrapperCss}>{children}</div>
<div css={dialogButtonWrapperCss}>{actionButtons}</div>
</motion.div>
Expand All @@ -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};
Expand Down
3 changes: 2 additions & 1 deletion src/components/my/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ const MenuCss = (theme: Theme) => css`
height: 54px;
padding: 16px 0;
border-bottom: solid 1px ${theme.color.gray01};
cursor: pointer;
`;

const menuTitleCss = css`
font-size: 12px;
font-size: 14px;
`;

const hiddenCss = css`
Expand Down
2 changes: 1 addition & 1 deletion src/constants/assets.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const INSPIRATION_EMPTY_IMAGE_SRC = '/InspirationEmpty.png';
export const INSPIRATION_EMPTY_IMAGE_SRC = '/insp-empty.webp';
export const INSPIRATION_EMPTY_TEXT_IMAGE_SRC = '/InspirationEmptyText.png';
export const USER_PROFILE_IMAGE_SRC = '/UserProfile.png';

Expand Down
5 changes: 3 additions & 2 deletions src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ export const MODAL_TYPE = {
};

export const POLICY_URL = {
PRIVACY: 'https://gifted-puffin-352.notion.site/94ac34de4c97467fb1f21a8bbed26eab',
PRIVACY: 'https://slashpage.com/ygtang/7vgjr4m1rkpdk2dwpy86',
// NOTE: Terms of service 줄여서 TOS라고 많이 쓴다고하네요!
TOS: 'https://gifted-puffin-352.notion.site/e75b7f51da7944508f37071f5345cc46',
TOS: 'https://slashpage.com/ygtang/ndvwx728g8vdxm3z6jpg ',
FEEDBACK: 'https://slashpage.com/ygtang/ndvwx728gewqgm3z6jpg',
};

export const WEBVIEW_MESSAGE_TYPE = {
Expand Down
5 changes: 5 additions & 0 deletions src/constants/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion src/libs/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
223 changes: 169 additions & 54 deletions src/pages/login/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,15 +18,99 @@ import { recordEvent } from '~/utils/analytics';
import { validator } from '~/utils/validator';

export default function Login() {
const { fireToast } = useToast();
const email = useInput({ useDebounce: true });
const password = useInput({ useDebounce: true });
const [isPending, setIsPending] = useState(false);
const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');
const { userLogin } = useUser();
const { push } = useInternalRouter();

const { handleFormSubmitEvent, email, password, emailError, passwordError } = useLoginPage({
setIsPending,
});
const {
canExtensionLogin,
handleExtensionLogin,
setUserCancelExtensionLogin,
userCancelExtensionLogin,
} = useExtensionAuth({ setIsPending });

return (
<>
<SEO title="로그인" />
<article css={loginCss}>
<div css={navMockupCss} />
<div css={loginIntroCardCss}></div>

<form css={loginFieldSetCss} onSubmit={handleFormSubmitEvent}>
<TextField
type="email"
label={'이메일 아이디'}
placeholder={'이메일을 입력해주세요'}
feedback={email.debouncedValue !== '' ? emailError || <>&nbsp;</> : <>&nbsp;</>}
isSuccess={email.debouncedValue.length > 0 && emailError === ''}
value={email.value}
onChange={email.onChange}
required
alertWhenFocused
/>
<TextField
type="password"
label={'비밀번호'}
placeholder={'영문, 숫자 포함 6자 이상의 비밀번호'}
feedback={password.debouncedValue !== '' ? passwordError || <>&nbsp;</> : <>&nbsp;</>}
isSuccess={password.debouncedValue.length > 0 && passwordError === ''}
value={password.value}
onChange={password.onChange}
required
alertWhenFocused
/>
<CTAButton type={'submit'} disabled={isPending}>
로그인
</CTAButton>
</form>
<GhostButton onClick={() => push('/password')}>비밀번호 찾기</GhostButton>
<div css={signUpTextWrapperCss}>
계정이 없으신가요?{' '}
<GhostButton size={'small'} onClick={() => push('/signup')}>
빠르게 가입하기
</GhostButton>
</div>
<Dialog
isShowing={!userCancelExtensionLogin && canExtensionLogin}
dialogWidth={300}
actionButtons={
<>
<FilledButton
colorType="light"
onClick={() => setUserCancelExtensionLogin(true)}
disabled={isPending}
>
다른 계정
</FilledButton>
<div css={dialogLongButtonCss}>
<FilledButton colorType="dark" onClick={handleExtensionLogin} disabled={isPending}>
익스텐션 계정
</FilledButton>
</div>
</>
}
>
영감탱 익스텐션에 로그인되어 있습니다.
<br />
익스텐션 계정으로 로그인할까요?
</Dialog>
</article>
</>
);
}

function useLoginPage({ setIsPending }: { setIsPending: (value: boolean) => void }) {
const { fireToast } = useToast();
const email = useInput({});
const password = useInput({});
const { push } = useInternalRouter();
const { getRedirect, goRedirect } = useLoginRedirect();
const { userLogin } = useUser();

const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');

const {
mutate: loginMutate,
Expand All @@ -47,20 +134,20 @@ export default function Login() {
};

useDidUpdate(() => {
if (!validator({ type: 'email', value: email.debouncedValue })) {
if (!validator({ type: 'email', value: email.value })) {
setEmailError('올바른 이메일을 입력해주세요.');
} else {
setEmailError('');
}
}, [email.debouncedValue]);
}, [email.value]);

useDidUpdate(() => {
if (password.debouncedValue.length >= 6) {
if (password.value.length >= 6) {
setPasswordError('');
} else {
setPasswordError('비밀번호는 6자리 이상이여야 합니다.');
}
}, [password.debouncedValue]);
}, [password.value]);

useDidUpdate(() => {
if (loginMutationData && loginMutationData.data) {
Expand All @@ -84,52 +171,75 @@ export default function Login() {
setIsPending(false);
fireToast({ content: loginMutationError.message ?? '알 수 없는 오류가 발생했습니다.' });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fireToast, loginMutationError]);

return (
<>
<SEO title="로그인" />
<article css={loginCss}>
<div css={navMockupCss} />
<div css={loginIntroCardCss}></div>
return { handleFormSubmitEvent, email, password, emailError, passwordError };
}

<form css={loginFieldSetCss} onSubmit={handleFormSubmitEvent}>
<TextField
type="email"
label={'이메일 아이디'}
placeholder={'이메일을 입력해주세요'}
feedback={email.debouncedValue !== '' ? emailError || <>&nbsp;</> : <>&nbsp;</>}
isSuccess={email.debouncedValue.length > 0 && emailError === ''}
value={email.value}
onChange={email.onChange}
required
alertWhenFocused
/>
<TextField
type="password"
label={'비밀번호'}
placeholder={'영문, 숫자 포함 6자 이상의 비밀번호'}
feedback={password.debouncedValue !== '' ? passwordError || <>&nbsp;</> : <>&nbsp;</>}
isSuccess={password.debouncedValue.length > 0 && passwordError === ''}
value={password.value}
onChange={password.onChange}
required
alertWhenFocused
/>
<CTAButton type={'submit'} disabled={isPending}>
로그인
</CTAButton>
</form>
<GhostButton onClick={() => push('/password')}>비밀번호 찾기</GhostButton>
<div css={signUpTextWrapperCss}>
계정이 없으신가요?{' '}
<GhostButton size={'small'} onClick={() => push('/signup')}>
빠르게 가입하기
</GhostButton>
</div>
</article>
</>
);
function useExtensionAuth({ setIsPending }: { setIsPending: (value: boolean) => void }) {
const { getRedirect, goRedirect } = useLoginRedirect();
const { push } = useInternalRouter();
const { fireToast } = useToast();
const { userLogin } = useUser();
const [canExtensionLogin, setCanExtensionLogin] = useState(false);
const [userCancelExtensionLogin, setUserCancelExtensionLogin] = useState(false);

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 handleExtensionLogin = () => {
setIsPending(true);
const token = localStorage.getItem(localStorageExtensionKeys.refreshToken);
if (token) {
reissueMutate({ refreshToken: token });
}
};

useEffect(() => {
const checkLoginAvailable = () => {
if (userCancelExtensionLogin) {
return;
}
if (localStorage.getItem(localStorageExtensionKeys.refreshToken)) {
setCanExtensionLogin(true);
} else {
setCanExtensionLogin(false);
}
};
checkLoginAvailable();
const interval = setInterval(checkLoginAvailable, 3000);
return () => {
clearInterval(interval);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return {
canExtensionLogin,
handleExtensionLogin,
setUserCancelExtensionLogin,
userCancelExtensionLogin,
};
}

const navMockupCss = css`
Expand Down Expand Up @@ -166,3 +276,8 @@ const signUpTextWrapperCss = (theme: Theme) => css`
font-size: 10px;
line-height: 150%;
`;

const dialogLongButtonCss = css`
width: 160px;
flex-shrink: 0;
`;
3 changes: 2 additions & 1 deletion src/pages/my/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ export default function MyPage() {
<ul css={menuListCss}>
<Menu label="내 계정" internalHref="/my/account" />
<Menu label="태그관리" internalHref="/my/tag" />
<Menu label="영감탱에 피드백 보내기" externalHref={POLICY_URL.FEEDBACK} />
<Menu label="이용약관" externalHref={POLICY_URL.TOS} />
<Menu label="개인정보 정책" externalHref={POLICY_URL.PRIVACY} />
<Menu
css={initializeMenuCss}
label="정보초기화"
label="정보 초기화"
onClick={() => {
setIsInitializeConfirmModalOpen(true);
}}
Expand Down

0 comments on commit c049907

Please sign in to comment.