Skip to content

Commit

Permalink
feat: 설정 팝업 관련 UI 구현 및 API 연동 (#135)
Browse files Browse the repository at this point in the history
* feat: 설정 팝업 UI 관련 작업

* feat: 계정 설정 팝업 API 연동 및 회원 탈퇴 API 연동 (카카오 로그인 시에만 회원 탈퇴 가능)

* style: lint 실행
  • Loading branch information
Puterism authored Jul 30, 2024
1 parent 4d664c7 commit cefb493
Show file tree
Hide file tree
Showing 24 changed files with 781 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { mq_lg } from '@boolti/ui';
import styled from '@emotion/styled';

const AccountDeleteForm = styled.form`
padding: 24px 0;
${mq_lg} {
padding: 0;
}
`;

const AccountDeleteFormDescription = styled.p`
${({ theme }) => theme.typo.b3};
color: ${({ theme }) => theme.palette.grey.g90};
margin-bottom: 12px;
line-height: 24px;
`;

const AccountDeleteFormTextArea = styled.textarea`
width: 100%;
padding: 12px;
border: 1px solid ${({ theme }) => theme.palette.grey.g20};
border-radius: 4px;
background-color: ${({ theme }) => theme.palette.grey.w};
color: ${({ theme }) => theme.palette.grey.g90};
${({ theme }) => theme.typo.b3};
&:placeholder-shown {
border: 1px solid ${({ theme }) => theme.palette.grey.g20};
color: ${({ theme }) => theme.palette.grey.g30};
}
&:focus {
border: 1px solid ${({ theme }) => theme.palette.grey.g90};
}
&:disabled {
background: ${({ theme }) => theme.palette.grey.g10};
border: 1px solid ${({ theme }) => theme.palette.grey.g20};
color: ${({ theme }) => theme.palette.grey.g40};
}
&::placeholder {
color: ${({ theme }) => theme.palette.grey.g30};
}
`;

const AccountDeleteFormButtonWrapper = styled.div`
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 32px;
`;

export default {
AccountDeleteForm,
AccountDeleteFormDescription,
AccountDeleteFormTextArea,
AccountDeleteFormButtonWrapper,
};
86 changes: 86 additions & 0 deletions apps/admin/src/components/AccountDeleteForm/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Button } from '@boolti/ui';
import Styled from './AccountDeleteForm.styles';
import { useForm } from 'react-hook-form';
import { useDeleteMe, useLogout } from '@boolti/api';
import { useAuthAtom } from '~/atoms/useAuthAtom';
import { useNavigate } from 'react-router-dom';
import { PATH } from '~/constants/routes';

export interface AccountDeleteFormInputs {
reason: string;
}

interface AccountDeleteFormProps {
oauthType?: 'KAKAO' | 'APPLE';
onClose: () => void;
}

const AccountDeleteForm = ({ oauthType, onClose }: AccountDeleteFormProps) => {
const navigate = useNavigate();

const deleteMeMutation = useDeleteMe();
const { removeToken } = useAuthAtom();
const logoutMutation = useLogout({
onSuccess: () => {
removeToken();
},
});
const {
register,
handleSubmit,
reset,
formState: { isValid },
} = useForm<AccountDeleteFormInputs>();

const submitHandler = async (data: AccountDeleteFormInputs) => {
let appleIdAuthorizationCode: string | undefined = undefined;

// TODO: 애플 로그인 시 탈퇴 로직 작성
if (oauthType === 'APPLE') {
const appleAuthData = await window.AppleID?.auth.signIn();

appleIdAuthorizationCode = appleAuthData?.authorization.code;
}

await deleteMeMutation.mutateAsync({
reason: data.reason,
appleIdAuthorizationCode,
});
await logoutMutation.mutateAsync();

onClose();
navigate(PATH.INDEX);
};

return (
<Styled.AccountDeleteForm onSubmit={handleSubmit(submitHandler)}>
<Styled.AccountDeleteFormDescription>
삭제 이유를 알려 주세요. 주신 의견 참고하여 더 나은 서비스를 제공하는 불티가 되겠습니다.
</Styled.AccountDeleteFormDescription>
<Styled.AccountDeleteFormTextArea
placeholder="예) 계정 삭제 후 재 가입할게요"
rows={3}
autoFocus
{...register('reason', { required: true })}
/>
<Styled.AccountDeleteFormButtonWrapper>
<Button
type="button"
colorTheme="line"
size="medium"
onClick={() => {
onClose();
reset();
}}
>
취소하기
</Button>
<Button type="submit" colorTheme="primary" size="medium" disabled={!isValid}>
삭제하기
</Button>
</Styled.AccountDeleteFormButtonWrapper>
</Styled.AccountDeleteForm>
);
};

export default AccountDeleteForm;
1 change: 1 addition & 0 deletions apps/admin/src/components/Layout/Layout.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const HeaderContainer = styled.div`
`;

const Header = styled.header`
background-color: ${({ theme }) => theme.palette.grey.w};
max-width: ${({ theme }) => theme.breakpoint.desktop};
margin: 0 auto;
Expand Down
11 changes: 10 additions & 1 deletion apps/admin/src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@ import Styled from './Layout.styles';
interface LayoutProps {
children: React.ReactNode;
header?: React.ReactNode;
headerMenu?: React.ReactNode;
banner?: React.ReactNode;
layoutStyle?: React.CSSProperties;
headerContainerStyle?: React.CSSProperties;
}

const Layout = ({ children, header, banner, layoutStyle, headerContainerStyle }: LayoutProps) => {
const Layout = ({
children,
header,
headerMenu,
banner,
layoutStyle,
headerContainerStyle,
}: LayoutProps) => {
return (
<Styled.Layout style={layoutStyle}>
{header && (
<Styled.HeaderContainer style={headerContainerStyle}>
<Styled.Header>{header}</Styled.Header>
{headerMenu}
</Styled.HeaderContainer>
)}
{banner && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { mq_lg } from '@boolti/ui';
import styled from '@emotion/styled';

const DropdownContainer = styled.div`
width: 66px;
height: 36px;
display: flex;
align-items: center;
cursor: pointer;
position: relative;
user-select: none;
`;

const UserProfileImageWrapper = styled.div`
width: 36px;
height: 36px;
width: 28px;
height: 28px;
margin-right: 6px;
${mq_lg} {
width: 36px;
height: 36px;
}
`;

const UserProfileImage = styled.img`
Expand All @@ -26,20 +32,36 @@ const DropdownMenuWrapper = styled.div`
position: absolute;
top: 42px;
right: 0;
width: 96px;
height: 48px;
width: 112px;
background-color: ${({ theme }) => theme.palette.grey.w};
box-shadow: 0px 8px 14px 0px #8b8b8b26;
border-radius: 4px;
border: 1px solid transparent;
border-radius: 6px;
border: 1px solid ${({ theme }) => theme.palette.grey.g20};
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
z-index: 1;
`;

const DropdownMenuItemButton = styled.button`
${({ theme }) => theme.typo.b1};
color: ${({ theme }) => theme.palette.grey.g70};
height: 36px;
padding: 0 12px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
&:hover {
background-color: ${({ theme }) => theme.palette.grey.g10};
}
`;

export default {
DropdownContainer,
UserProfileImageWrapper,
UserProfileImage,
DropdownMenuWrapper,
DropdownMenuItemButton,
};
61 changes: 47 additions & 14 deletions apps/admin/src/components/ProfileDropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { useLogout } from '@boolti/api';
import { ChevronDownIcon, ChevronUpIcon } from '@boolti/icon';
import { TextButton } from '@boolti/ui';
import { useDropdown } from '@boolti/ui/src/hooks';
import { ChevronDownIcon, ChevronUpIcon, LogoutIcon, SettingIcon } from '@boolti/icon';
import { useConfirm, useDialog, useDropdown } from '@boolti/ui/src/hooks';
import { useNavigate } from 'react-router-dom';

import { PATH } from '~/constants/routes';

import Styled from './ProfileDropdown.styles';
import { useAuthAtom } from '~/atoms/useAuthAtom';
import SettingDialogContent from '../SettingDialogContent';

interface ProfileDropdownProps {
image?: string;
open?: boolean;
disabledDropdown?: boolean;
onClick?: () => void;
}

// TODO: UserProfile svg 공통화
Expand All @@ -33,7 +36,7 @@ const ProfileSVG = () => (
</svg>
);

const ProfileDropdown = ({ image }: ProfileDropdownProps) => {
const ProfileDropdown = ({ image, open, disabledDropdown, onClick }: ProfileDropdownProps) => {
const { isOpen, toggleDropdown } = useDropdown();
const { removeToken } = useAuthAtom();
const logoutMutation = useLogout({
Expand All @@ -42,26 +45,56 @@ const ProfileDropdown = ({ image }: ProfileDropdownProps) => {
},
});
const navigate = useNavigate();
const confirm = useConfirm();
const settingDialog = useDialog();

const dropdownOpen = open ?? isOpen;

return (
<Styled.DropdownContainer onClick={() => toggleDropdown()}>
<Styled.DropdownContainer
onClick={() => {
onClick?.();

if (disabledDropdown) return;

toggleDropdown();
}}
>
<Styled.UserProfileImageWrapper>
{image ? <Styled.UserProfileImage src={image} alt="유저 프로필 이미지" /> : <ProfileSVG />}
</Styled.UserProfileImageWrapper>
{isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
{isOpen && (
{dropdownOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
{dropdownOpen && !disabledDropdown && (
<Styled.DropdownMenuWrapper>
<TextButton
<Styled.DropdownMenuItemButton
type="button"
onClick={() => {
settingDialog.open({
title: '설정',
content: <SettingDialogContent />,
isAuto: true,
contentPadding: '0',
mobileType: 'fullPage',
});
}}
>
<SettingIcon /> 설정
</Styled.DropdownMenuItemButton>
<Styled.DropdownMenuItemButton
type="button"
size="regular"
colorTheme="netural"
onClick={async () => {
await logoutMutation.mutateAsync();
navigate(PATH.INDEX, { replace: true });
const result = await confirm('로그아웃 할까요?', {
cancel: '취소하기',
confirm: '로그아웃',
});
if (result) {
await logoutMutation.mutateAsync();
navigate(PATH.INDEX, { replace: true });
}
}}
>
로그아웃
</TextButton>
<LogoutIcon /> 로그아웃
</Styled.DropdownMenuItemButton>
</Styled.DropdownMenuWrapper>
)}
</Styled.DropdownContainer>
Expand Down
Loading

0 comments on commit cefb493

Please sign in to comment.