diff --git a/package.json b/package.json index a546e5b6..7e3a27e0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "polished": "^4.2.2", "rc-slider": "^10.0.1", "react": "^18.1.0", - "react-apple-login": "^1.1.5", "react-content-loader": "^6.2.0", "react-dom": "^18.1.0", "react-icons": "^4.3.1", @@ -39,6 +38,7 @@ "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", "react-signature-canvas": "^1.0.6", + "react-spinners": "^0.13.4", "react-spring-bottom-sheet": "^3.4.1", "styled-components": "^5.3.5", "styled-reset": "^4.4.1", diff --git a/src/App.tsx b/src/App.tsx index d6651379..2144b985 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,9 +9,11 @@ import FinancialRouter from './pages/Financial'; import NotFound from './pages/NotFound'; import RequireAuth from '@components/auth/RequireAuth'; import PersistLogin from '@components/auth/PersistLogin'; -import SungwooTestPage from './pages/SungwooTestPage'; +import TestPage from './pages/Test/TestPage'; import { useQueryClient } from 'react-query'; import GroupLink from './components/mypage/GroupLink'; +import Setting from './pages/Setting/Setting'; +import SettingRouter from './pages/Setting'; function App() { const queryClient = useQueryClient(); @@ -37,10 +39,13 @@ function App() { } /> } /> } /> + + } /> } /> {/* } /> */} } /> {/* */} + {/* */} diff --git a/src/assets/icons/arrow-walking.svg b/src/assets/icons/arrow-walking.svg index 0881b603..8666d42b 100644 --- a/src/assets/icons/arrow-walking.svg +++ b/src/assets/icons/arrow-walking.svg @@ -1,8 +1,8 @@ - + - + diff --git a/src/assets/icons/setting.svg b/src/assets/icons/setting.svg new file mode 100644 index 00000000..0aaafc7c --- /dev/null +++ b/src/assets/icons/setting.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/components/auth/PersistLogin.tsx b/src/components/auth/PersistLogin.tsx index 2bc7b9c6..b91f7b2e 100644 --- a/src/components/auth/PersistLogin.tsx +++ b/src/components/auth/PersistLogin.tsx @@ -1,13 +1,13 @@ import { Outlet } from 'react-router-dom'; import { useState, useEffect } from 'react'; -import useRefreshToken from '@lib/hooks/auth/useRefreshToken'; +import useRefreshAccessToken from '@lib/hooks/auth/useRefreshAccessToken'; import { useAppSelector } from '@store/app/hooks'; import { selectAccessToken } from '@store/slices/authSlice'; import useLocalStorage from '@lib/hooks/auth/useLocalStorage'; function PersistLogin() { const [isLoading, setIsLoading] = useState(true); - const refresh = useRefreshToken(); + const refreshAccessToken = useRefreshAccessToken(); const accessToken = useAppSelector(selectAccessToken); const [persist] = useLocalStorage('persist', true); @@ -16,29 +16,44 @@ function PersistLogin() { let isMounted = true; const verifyRefreshToken = async () => { try { - await refresh(); + await refreshAccessToken(); } catch (err) { console.error(err); } finally { isMounted && setIsLoading(false); // escape memory leak } }; + // verify only on refresh - !accessToken && persist ? verifyRefreshToken() : setIsLoading(false); + if (!accessToken && persist) { + verifyRefreshToken(); + } else { + setIsLoading(false); + } + return () => (isMounted = false); }, []); - return ( - <> - {!persist ? ( - - ) : isLoading ? ( -

자동 로그인 처리중입니다...

- ) : ( - - )} - - ); + if (persist && isLoading) { + // TODO:

삭제 + return

자동 로그인 처리중입니다...

; + } else { + return ; + } } export default PersistLogin; + +// TODO: 테스트 후 주석 삭제 +// !accessToken && persist ? verifyRefreshToken() : setIsLoading(false); +// return ( +// <> +// {!persist ? ( +// +// ) : isLoading ? ( +//

자동 로그인 처리중입니다...

+// ) : ( +// +// )} +// +// ); diff --git a/src/components/auth/RequireAuth.tsx b/src/components/auth/RequireAuth.tsx index 4ab6693e..4f6baf11 100644 --- a/src/components/auth/RequireAuth.tsx +++ b/src/components/auth/RequireAuth.tsx @@ -6,8 +6,7 @@ function RequireAuth() { const accessToken = useAppSelector(selectAccessToken); const isKid = useAppSelector(selectIsKid); - console.log(accessToken); - console.log(isKid); + console.log('aT: ', accessToken); if (accessToken === '') { return ; } else if (isKid === null) { diff --git a/src/components/common/CustomSyncLoader.tsx b/src/components/common/CustomSyncLoader.tsx new file mode 100644 index 00000000..d67f34d2 --- /dev/null +++ b/src/components/common/CustomSyncLoader.tsx @@ -0,0 +1,26 @@ +import SyncLoader from 'react-spinners/SyncLoader'; +import styled from 'styled-components'; + +function CustomSyncLoader() { + return ( + + + + ); +} + +export default CustomSyncLoader; + +const Wrapper = styled.div` + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +`; diff --git a/src/components/common/modals/primaryModal/PrimaryModal.stories.tsx b/src/components/common/modals/primaryModal/PrimaryModal.stories.tsx index 2f0f5cce..43f8b899 100644 --- a/src/components/common/modals/primaryModal/PrimaryModal.stories.tsx +++ b/src/components/common/modals/primaryModal/PrimaryModal.stories.tsx @@ -13,8 +13,10 @@ const Template: ComponentStory = (args) => ( ); -export const 가족이_생겼어요 = Template.bind({}); -가족이_생겼어요.args = { - headerText: '가족이 생겼어요', - bodyText: '기획에서 워딩 생각해주세요', +export const 에어팟_사기 = Template.bind({}); +에어팟_사기.args = { + isKid: false, + isFemale: false, + headerText: '뱅키즈 첫 가입을 축하해요', + bodyText: '뱅키와 저금을 통해 돈길만 걸어요', }; diff --git a/src/components/common/modals/primaryModal/PrimaryModal.tsx b/src/components/common/modals/primaryModal/PrimaryModal.tsx index aa5db6cd..15442b2f 100644 --- a/src/components/common/modals/primaryModal/PrimaryModal.tsx +++ b/src/components/common/modals/primaryModal/PrimaryModal.tsx @@ -9,7 +9,7 @@ import { MODAL_CLOSE_TRANSITION_TIME, MODAL_SLIDE_FROM_POSITION, MODAL_SLIDE_TO_POSITION, -} from '@lib/constants'; +} from '@lib/constants/MODAL'; import useModals from '@lib/hooks/useModals'; import { modals } from '../Modals'; diff --git a/src/components/common/modals/receiptModal/DashedBorder.tsx b/src/components/common/modals/receiptModal/DashedBorder.tsx deleted file mode 100644 index 652ad01c..00000000 --- a/src/components/common/modals/receiptModal/DashedBorder.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { ReactComponent as HorizontalDashedBorder } from '@assets/borders/horizontal-dashed-border.svg'; -import { ReactComponent as VerticalDashedBorder } from '@assets/borders/vertical-dashed-border.svg'; -import { theme } from '@lib/styles/theme'; -import styled from 'styled-components'; -import { TReceiptModalVariant } from './TReceiptModalVariant'; - -interface DashedBorderProps { - variant: TReceiptModalVariant; -} - -function DashedBorder({ variant }: DashedBorderProps) { - return ( - <> - - - - - - - - - - - - - {variant === 'rejected' && ( - - - - )} - - ); -} - -export default DashedBorder; - -const VerticalDashedBorderWrapper = styled.div` - z-index: 700; - position: absolute; - left: 50%; - top: 167px; - transform: translate3d(-50%, -50%, 0); - - display: flex; - justify-content: center; - align-items: center; - - width: 2px; - height: 100px; -`; - -const FirstHorizontalDashedBorderWrapper = styled.div` - z-index: 700; - position: absolute; - left: 50%; - top: 115px; - transform: translate3d(-50%, -50%, 0); - - display: flex; - justify-content: center; - align-items: center; - - width: 100%; - height: 3px; - padding-left: 16px; - padding-right: 16px; -`; - -const SecondHorizontalDashedBorderWrapper = styled.div` - z-index: 700; - position: absolute; - left: 50%; - top: 216px; - transform: translate3d(-50%, -50%, 0); - - display: flex; - justify-content: center; - align-items: center; - - width: 100%; - height: 3px; - padding-left: 16px; - padding-right: 16px; -`; - -const ThirdHorizontalDashedBorderWrapper = styled.div` - z-index: 700; - position: absolute; - left: 50%; - top: 286px; - transform: translate3d(-50%, -50%, 0); - - display: flex; - justify-content: center; - align-items: center; - - width: 100%; - height: 3px; - padding-left: 16px; - padding-right: 16px; -`; - -const FourthHorizontalDashedBorderWrapper = styled.div` - z-index: 700; - position: absolute; - left: 50%; - top: 478px; // decreased 10px - transform: translate3d(-50%, -50%, 0); - - display: flex; - justify-content: center; - align-items: center; - - width: 100%; - height: 3px; - padding-left: 16px; - padding-right: 16px; -`; diff --git a/src/components/common/modals/receiptModal/ReceiptModal.tsx b/src/components/common/modals/receiptModal/ReceiptModal.tsx index e52844b2..fd4699d6 100644 --- a/src/components/common/modals/receiptModal/ReceiptModal.tsx +++ b/src/components/common/modals/receiptModal/ReceiptModal.tsx @@ -7,7 +7,6 @@ import getHeightByVariant from './getHeightByVariant'; import { IDongil } from '@lib/types/IDongil'; import PerforatedLineTop from './perforatedLines/PerforatedLineTop'; import PerforatedLineBottom from './perforatedLines/PerforatedLineBottom'; -import DashedBorder from './DashedBorder'; import TopContent from './contents/TopContent'; import BottomContent from './contents/BottomContent'; import CommentContent from './contents/CommentContent'; @@ -16,7 +15,7 @@ import { MODAL_CLOSE_TRANSITION_TIME, MODAL_SLIDE_FROM_POSITION, MODAL_SLIDE_TO_POSITION, -} from '@lib/constants'; +} from '@lib/constants/MODAL'; import useModals from '@lib/hooks/useModals'; import { modals } from '../Modals'; import { TReceiptModalVariant } from './TReceiptModalVariant'; @@ -118,7 +117,6 @@ function ReceiptModal({ // @ts-expect-error - ` justify-content: flex-start; align-items: space-between; position: relative; + + padding-left: 17px; + padding-right: 17px; `; const SignatureWrapper = styled.div` diff --git a/src/components/common/modals/receiptModal/contents/CommentContent.tsx b/src/components/common/modals/receiptModal/contents/CommentContent.tsx index 8edb058c..5e428540 100644 --- a/src/components/common/modals/receiptModal/contents/CommentContent.tsx +++ b/src/components/common/modals/receiptModal/contents/CommentContent.tsx @@ -6,8 +6,10 @@ interface CommentContentProps extends Pick {} function CommentContent({ comment }: CommentContentProps) { return ( -
부모님의 한줄평
-
{comment?.content}
+
+
부모님의 한줄평
+
{comment?.content}
+
); } @@ -15,6 +17,9 @@ function CommentContent({ comment }: CommentContentProps) { export default CommentContent; const Wrapper = styled.div` + padding-left: 17px; + padding-right: 17px; + border-top-left-radius: ${({ theme }) => theme.radius.medium}; border-top-right-radius: ${({ theme }) => theme.radius.medium}; width: 100%; @@ -27,12 +32,18 @@ const Wrapper = styled.div` justify-content: flex-start; align-items: flex-start; + .border { + width: 100%; + height: 100%; + z-index: 999; + border-top: ${({ theme }) => theme.border.receipt}; + } .header { width: 80px; height: 12px; ${({ theme }) => theme.typo.text.S_12_M}; color: ${({ theme }) => theme.palette.greyScale.grey500}; - margin-left: 24px; + margin-left: 7px; margin-top: 18px; } .body { @@ -40,7 +51,7 @@ const Wrapper = styled.div` height: 14px; ${({ theme }) => theme.typo.text.T_16_EB} color: ${({ theme }) => theme.palette.sementic.red300}; - margin-left: 24px; + margin-left: 7px; margin-top: 18px; margin-bottom: 32px; } diff --git a/src/components/common/modals/receiptModal/rows/FirstRow.tsx b/src/components/common/modals/receiptModal/rows/FirstRow.tsx index 5ca94888..3db717b6 100644 --- a/src/components/common/modals/receiptModal/rows/FirstRow.tsx +++ b/src/components/common/modals/receiptModal/rows/FirstRow.tsx @@ -41,14 +41,21 @@ const Wrapper = styled.div` justify-content: space-between; align-items: center; + padding-top: 16px; + padding-bottom: 16px; + border-top: ${({ theme }) => theme.border.receipt}; + border-bottom: ${({ theme }) => theme.border.receipt}; + .계약대상 { display: flex; justify-content: space-between; width: 50%; height: 100%; + border-right: ${({ theme }) => theme.border.receipt}; + .banki-illust { width: 60.98px; - margin-left: ${calcRatio(21, 162)}; + margin-left: ${calcRatio(14, 162)}; } .text-wrapper { width: 47px; diff --git a/src/components/common/modals/receiptModal/rows/SecondRow.tsx b/src/components/common/modals/receiptModal/rows/SecondRow.tsx index 86d87313..5fa52aef 100644 --- a/src/components/common/modals/receiptModal/rows/SecondRow.tsx +++ b/src/components/common/modals/receiptModal/rows/SecondRow.tsx @@ -33,6 +33,8 @@ const Wrapper = styled.div` justify-content: space-between; align-items: center; + border-bottom: ${({ theme }) => theme.border.receipt}; + div { width: 33.3%; height: 100%; @@ -40,7 +42,7 @@ const Wrapper = styled.div` flex-direction: column; justify-content: center; align-items: flex-start; - padding-left: ${calcRatio(24, 324)}; + padding-left: ${calcRatio(7, 290)}; .title { width: 100%; height: 12px; diff --git a/src/components/common/modals/receiptModal/rows/ThirdRow.tsx b/src/components/common/modals/receiptModal/rows/ThirdRow.tsx index 67642158..fbf11f15 100644 --- a/src/components/common/modals/receiptModal/rows/ThirdRow.tsx +++ b/src/components/common/modals/receiptModal/rows/ThirdRow.tsx @@ -40,9 +40,10 @@ const Wrapper = styled.div` height: 100%; display: flex; flex-direction: column; - justify-content: center; + justify-content: flex-start; align-items: flex-start; - padding-left: ${calcRatio(24, 324)}; + padding-top: 13px; + padding-left: ${calcRatio(7, 290)}; .title { height: 12px; ${({ theme }) => theme.typo.text.S_12_M}; @@ -60,12 +61,13 @@ const Wrapper = styled.div` .계약종료일 { width: 66.6%; - height: 70px; + height: 100%; display: flex; flex-direction: column; - justify-content: center; + justify-content: flex-start; align-items: flex-start; - padding-left: ${calcRatio(24, 324)}; + padding-top: 13px; + padding-left: ${calcRatio(7, 290)}; .text-wrapper { .title { height: 12px; diff --git a/src/components/common/modals/secondaryModal/SecondaryModal.tsx b/src/components/common/modals/secondaryModal/SecondaryModal.tsx index 975d0744..da063c60 100644 --- a/src/components/common/modals/secondaryModal/SecondaryModal.tsx +++ b/src/components/common/modals/secondaryModal/SecondaryModal.tsx @@ -9,7 +9,7 @@ import { MODAL_CLOSE_TRANSITION_TIME, MODAL_SLIDE_FROM_POSITION, MODAL_SLIDE_TO_POSITION, -} from '@lib/constants'; +} from '@lib/constants/MODAL'; import useModals from '@lib/hooks/useModals'; import { modals } from '../Modals'; diff --git a/src/components/common/modals/tertiaryModal/CloseButton.tsx b/src/components/common/modals/tertiaryModal/CloseButton.tsx index 5e5676cf..09216e29 100644 --- a/src/components/common/modals/tertiaryModal/CloseButton.tsx +++ b/src/components/common/modals/tertiaryModal/CloseButton.tsx @@ -1,7 +1,7 @@ import CloseButton from '@components/common/buttons/CloseButton'; import { SetStateAction } from 'react'; import styled from 'styled-components'; -import { MODAL_CLOSE_TRANSITION_TIME } from '@lib/constants'; +import { MODAL_CLOSE_TRANSITION_TIME } from '@lib/constants/MODAL'; interface CloseButtonProps { shouldCloseOnOverlayClick: boolean; diff --git a/src/components/common/modals/tertiaryModal/TertiaryModal.tsx b/src/components/common/modals/tertiaryModal/TertiaryModal.tsx index 00ea976f..27bd773f 100644 --- a/src/components/common/modals/tertiaryModal/TertiaryModal.tsx +++ b/src/components/common/modals/tertiaryModal/TertiaryModal.tsx @@ -9,7 +9,7 @@ import { MODAL_CLOSE_TRANSITION_TIME, MODAL_SLIDE_FROM_POSITION, MODAL_SLIDE_TO_POSITION, -} from '@lib/constants'; +} from '@lib/constants/MODAL'; import useModals from '@lib/hooks/useModals'; import { modals } from '../Modals'; diff --git a/src/components/common/skeletons/dongilItems/WalkingDongilItem.tsx b/src/components/common/skeletons/dongilItems/WalkingDongilItem.tsx index fea83dd9..38234856 100644 --- a/src/components/common/skeletons/dongilItems/WalkingDongilItem.tsx +++ b/src/components/common/skeletons/dongilItems/WalkingDongilItem.tsx @@ -49,5 +49,6 @@ const Wrapper = styled.div` display: flex; align-items: center; gap: 8px; + fill: ${({ theme }) => theme.palette.greyScale.grey200}; } `; diff --git a/src/components/home/create/steps/Step4.tsx b/src/components/home/create/steps/Step4.tsx index 8726c2ee..7a49a30f 100644 --- a/src/components/home/create/steps/Step4.tsx +++ b/src/components/home/create/steps/Step4.tsx @@ -12,7 +12,6 @@ import { selectStep4InitData, selectTotalPrice, } from '@store/slices/createChallengeSlice'; -import { ReactComponent as Divider } from '@assets/borders/create-challenge-dashed-divider.svg'; import { ReactComponent as Alert } from '@assets/icons/alert.svg'; import RangeInput from '@components/common/bottomSheets/contractSheet/RangeInput'; import useModals from '@lib/hooks/useModals'; @@ -175,8 +174,7 @@ function Step4({ currentStep }: { currentStep: number }) { /> - - +

@@ -258,10 +256,6 @@ const InputSection = styled.div` } `; -const StyledDivider = styled(Divider)` - margin: 8px 0; -`; - const Summary = styled.div<{ weekCost: number }>` display: flex; flex-direction: column; @@ -282,3 +276,8 @@ const Summary = styled.div<{ weekCost: number }>` color: ${({ theme }) => theme.palette.greyScale.grey600}; } `; + +const Divider = styled.div` + border-top: ${({ theme }) => theme.border.receipt}; + margin: 8px 0px; +`; diff --git a/src/components/home/detail/useTargetDongil.tsx b/src/components/home/detail/useTargetDongil.tsx index 38c0b18d..573eac10 100644 --- a/src/components/home/detail/useTargetDongil.tsx +++ b/src/components/home/detail/useTargetDongil.tsx @@ -15,14 +15,14 @@ function useTargetDongil(id: string) { (walkingDongil) => walkingDongil.id === parseInt(id!), )!; } else if (isKid === false) { - const getSelectedKidSThisWeekSDongils = (username: string) => { + const getSelectedKidSThisWeekSDongils = (kidId: number) => { const found = thisWeekSDongils?.find( - (thisWeekSDongil) => thisWeekSDongil.userName === username, + (thisWeekSDongil) => thisWeekSDongil.kidId === kidId, ); return found?.challengeList; }; const selectedKidSThisWeekSDongils = getSelectedKidSThisWeekSDongils( - selectedKid?.username!, + selectedKid?.kidId!, ); return selectedKidSThisWeekSDongils?.find( diff --git a/src/components/home/proposed/ProposedDongilSection.tsx b/src/components/home/proposed/ProposedDongilSection.tsx index a9252206..14a5581c 100644 --- a/src/components/home/proposed/ProposedDongilSection.tsx +++ b/src/components/home/proposed/ProposedDongilSection.tsx @@ -18,14 +18,14 @@ function ProposedDongilSection() { if (proposedDongilsStatus === 'loading') { content = ; } else if (proposedDongilsStatus === 'succeeded') { - const getSelectedKidSProposedDongils = (username: string) => { + const getSelectedKidSProposedDongils = (kidId: number) => { const found = proposedDongils?.find( - (proposedDongil) => proposedDongil.userName === username, + (proposedDongil) => proposedDongil.kidId === kidId, ); return found?.challengeList; }; const selectedKidSProposedDongils = getSelectedKidSProposedDongils( - selectedKid?.username!, + selectedKid?.kidId!, ); if (selectedKidSProposedDongils?.length === 0) { diff --git a/src/components/home/thisWeekS/ThisWeekSDongilSection.tsx b/src/components/home/thisWeekS/ThisWeekSDongilSection.tsx index 4091509d..7fa3043e 100644 --- a/src/components/home/thisWeekS/ThisWeekSDongilSection.tsx +++ b/src/components/home/thisWeekS/ThisWeekSDongilSection.tsx @@ -18,14 +18,14 @@ function ThisWeekSDongilSection() { if (thisWeekSDongilsStatus === 'loading') { content = ; } else if (thisWeekSDongilsStatus === 'succeeded') { - const getSelectedKidSThisWeekSDongils = (username: string) => { + const getSelectedKidSThisWeekSDongils = (kidId: number) => { const found = thisWeekSDongils?.find( - (thisWeekSDongil) => thisWeekSDongil.userName === username, + (thisWeekSDongil) => thisWeekSDongil.kidId === kidId, ); return found?.challengeList; }; const selectedKidSThisWeekSDongils = getSelectedKidSThisWeekSDongils( - selectedKid?.username!, + selectedKid?.kidId!, ); if (selectedKidSThisWeekSDongils?.length === 0) { diff --git a/src/components/mypage/FamilyList.tsx b/src/components/mypage/FamilyList.tsx index 327de432..b8accaa7 100644 --- a/src/components/mypage/FamilyList.tsx +++ b/src/components/mypage/FamilyList.tsx @@ -1,5 +1,5 @@ import { IGetUserResData } from '@lib/api/user/user.type'; -import { FAMILY, KID, USER } from '@lib/constants/queryKeys'; +import { FAMILY, KID, USER } from '@lib/constants/QUERY_KEY'; import { IFamilyState } from '@lib/types/IFamilyState'; import { useMutation, useQueryClient } from 'react-query'; import styled from 'styled-components'; diff --git a/src/components/mypage/GroupLink.tsx b/src/components/mypage/GroupLink.tsx index efddb328..7fdd833e 100644 --- a/src/components/mypage/GroupLink.tsx +++ b/src/components/mypage/GroupLink.tsx @@ -1,7 +1,7 @@ import useOpenGroupLinkSheets from '@components/mypage/useOpenGroupLinkSheets'; import useFamilyApi from '@lib/api/family/useFamilyApi'; import useUserApi from '@lib/api/user/useUserAPi'; -import { FAMILY, USER } from '@lib/constants/queryKeys'; +import { FAMILY, USER } from '@lib/constants/QUERY_KEY'; import useGlobalBottomSheet from '@lib/hooks/useGlobalBottomSheet'; import { decipher } from '@lib/utils/crypt'; import dayjs from 'dayjs'; diff --git a/src/components/mypage/OverView.tsx b/src/components/mypage/OverView.tsx index 44e3d1a8..48c99c65 100644 --- a/src/components/mypage/OverView.tsx +++ b/src/components/mypage/OverView.tsx @@ -3,7 +3,7 @@ import renderRoleIllust from '@lib/utils/render/renderRoleIllust'; import { IGetUserResData } from '@lib/api/user/user.type'; import OverViewContent from './OverViewContent'; import { useQueryClient } from 'react-query'; -import { KID } from '@lib/constants/queryKeys'; +import { KID } from '@lib/constants/QUERY_KEY'; import getPercentValue from '@lib/utils/get/getPercentValue'; import { IKidListDTO } from '@lib/api/family/family.type'; diff --git a/src/components/setting/notices/Notice.tsx b/src/components/setting/notices/Notice.tsx new file mode 100644 index 00000000..4ef2fdc0 --- /dev/null +++ b/src/components/setting/notices/Notice.tsx @@ -0,0 +1,15 @@ +import ForegroundTemplate from '@components/layout/ForegroundTemplate'; +import { useParams } from 'react-router-dom'; + +const Notice = () => { + const { id } = useParams(); + return ( + + <> +

{}

+ + + ); +}; + +export default Notice; diff --git a/src/components/setting/notices/noticeData.ts b/src/components/setting/notices/noticeData.ts new file mode 100644 index 00000000..a8851f25 --- /dev/null +++ b/src/components/setting/notices/noticeData.ts @@ -0,0 +1,21 @@ +const noticeData = [ + { + title: '뱅키즈 출시 알림', + date: '2022.08.10', + id: 0, + content: `안녕하세요, 뱅키즈입니다. + + 드디어 자녀와 부모가 함께 즐길 수 있는 저축 챌린지 + ‘뱅키즈’서비스를 출시하였습니다. + + 저희는 지난 5개월 간, 어린이들의 재밌는 금융 경험 제공과 올바른 금융 습관 형성을 목표로 서비스를 준비해왔습니다. + + 이에 저희의 많은 고민이 담긴 서비스에 많은 이용 부탁드리며, 무엇이든 문의사항이나 피드백을 남겨주시면 감사하겠습니다. + + 또한, 앞으로 추가될 소비관리, 앱 내 계좌연결 등 다양한 서비스에도 많은 관심 부탁드립니다. + + 감사합니다.`, + }, +]; + +export default noticeData; diff --git a/src/lib/api/axios.tsx b/src/lib/api/axios.tsx index 3ba4c144..b45a15ea 100644 --- a/src/lib/api/axios.tsx +++ b/src/lib/api/axios.tsx @@ -1,5 +1,5 @@ +import { BASE_URL } from '@lib/constants/BASE_URL'; import axios from 'axios'; -import { BASE_URL } from '@lib/constants'; export const axiosPublic = axios.create({ baseURL: BASE_URL, diff --git a/src/lib/constants/APPLE_AUTH_URL.ts b/src/lib/constants/APPLE_AUTH_URL.ts new file mode 100644 index 00000000..ae50e0af --- /dev/null +++ b/src/lib/constants/APPLE_AUTH_URL.ts @@ -0,0 +1,18 @@ +import { BASE_URL } from './BASE_URL'; + +const APPLE_LOGIN_CLIENT_ID = 'com.bankidz.bankidz-web'; +const APPLE_LOGIN_REDIRECT_URL = `${BASE_URL}/apple/login`; +const config = { + client_id: APPLE_LOGIN_CLIENT_ID, // This is the service ID we created. + redirect_uri: APPLE_LOGIN_REDIRECT_URL, // As registered along with our service ID + response_type: 'code id_token', + scope: 'name', // To tell apple we want the user name and emails fields in the response it sends us. + response_mode: 'form_post', + m: 11, + v: '1.5.4', + nonce: `${process.env.REACT_APP_APPLE_NONCE}`, +}; +const queryString = Object.entries(config) + .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) + .join('&'); +export const APPLE_AUTH_URL = `https://appleid.apple.com/auth/authorize?${queryString}`; diff --git a/src/lib/constants/AWS_S3_URL.ts b/src/lib/constants/AWS_S3_URL.ts new file mode 100644 index 00000000..5e23d963 --- /dev/null +++ b/src/lib/constants/AWS_S3_URL.ts @@ -0,0 +1 @@ +export const AWS_S3_URL = `${process.env.REACT_APP_AWS_S3_URL}`; diff --git a/src/lib/constants/BASE_URL.ts b/src/lib/constants/BASE_URL.ts new file mode 100644 index 00000000..270b8341 --- /dev/null +++ b/src/lib/constants/BASE_URL.ts @@ -0,0 +1,2 @@ +// export const BASE_URL = 'https://api.bankidz.com'; // 배포 환경 +export const BASE_URL = 'https://bankids.click'; // 테스트 환경 diff --git a/src/lib/constants/KAKAO_AUTH_URL.ts b/src/lib/constants/KAKAO_AUTH_URL.ts new file mode 100644 index 00000000..4cba6b60 --- /dev/null +++ b/src/lib/constants/KAKAO_AUTH_URL.ts @@ -0,0 +1,5 @@ +// const DOMAIN = 'https://bankidz.com'; // 배포 환경(고정) +const DOMAIN = 'http://localhost:3000'; // 테스트 환경 +const REST_API_KEY = `${process.env.REACT_APP_KAKAO_REST_API_KEY}`; +const REDIRECT_URI = `${DOMAIN}/auth/kakao/callback`; +export const KAKAO_AUTH_URL = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code`; diff --git a/src/lib/constants/MODAL.ts b/src/lib/constants/MODAL.ts new file mode 100644 index 00000000..897dcc7c --- /dev/null +++ b/src/lib/constants/MODAL.ts @@ -0,0 +1,3 @@ +export const MODAL_CLOSE_TRANSITION_TIME = 200; // milliseconds +export const MODAL_SLIDE_FROM_POSITION = '-10%'; +export const MODAL_SLIDE_TO_POSITION = '-50%'; diff --git a/src/lib/constants/queryKeys.ts b/src/lib/constants/QUERY_KEY.ts similarity index 100% rename from src/lib/constants/queryKeys.ts rename to src/lib/constants/QUERY_KEY.ts diff --git a/src/lib/constants/index.ts b/src/lib/constants/index.ts deleted file mode 100644 index ae887f39..00000000 --- a/src/lib/constants/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - ** ============================================================================= - ** API Base URL - ** ============================================================================= - */ -export const BASE_URL = 'https://api.bankidz.com'; // 배포 환경 -// export const BASE_URL = 'https://bankids.click'; // 테스트 환경 - -/* - ** ============================================================================= - ** AWS S3 URL - ** ============================================================================= - */ -export const AWS_S3_URL = `${process.env.REACT_APP_AWS_S3_URL}`; - -/* - ** ============================================================================= - ** Modal Animation - ** ============================================================================= - */ -export const MODAL_CLOSE_TRANSITION_TIME = 200; // milliseconds -export const MODAL_SLIDE_FROM_POSITION = '-10%'; -export const MODAL_SLIDE_TO_POSITION = '-50%'; - -/* - ** ============================================================================= - ** KAKAO AUTH URL - ** ============================================================================= - */ -const DOMAIN = 'https://bankidz.com'; // 배포 환경(고정) -// export const DOMAIN = 'http://localhost:3000'; // 테스트 환경 -const REST_API_KEY = `${process.env.REACT_APP_KAKAO_REST_API_KEY}`; -const REDIRECT_URI = `${DOMAIN}/auth/kakao/callback`; -export const KAKAO_AUTH_URL = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code`; - -/* - ** ============================================================================= - ** APPLE AUTH URL - ** ============================================================================= - */ -const APPLE_LOGIN_CLIENT_ID = 'com.bankidz.bankidz-web'; -const APPLE_LOGIN_REDIRECT_URL = 'https://api.bankidz.com/apple/login'; -const config = { - client_id: APPLE_LOGIN_CLIENT_ID, // This is the service ID we created. - redirect_uri: APPLE_LOGIN_REDIRECT_URL, // As registered along with our service ID - response_type: 'code id_token', - // state: 'origin:web', // Any string of your choice that you may use for some logic. It's optional and you may omit it. - // scope: 'name email', // To tell apple we want the user name and emails - scope: 'name', // To tell apple we want the user name and emails fields in the response it sends us. - response_mode: 'form_post', - m: 11, - v: '1.5.4', - nonce: 'hi', -}; -const queryString = Object.entries(config) - .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) - .join('&'); -export const APPLE_AUTH_URL = `https://appleid.apple.com/auth/authorize?${queryString}`; diff --git a/src/lib/hooks/auth/useAxiosPrivate.tsx b/src/lib/hooks/auth/useAxiosPrivate.tsx index 2dd26d33..01d2791c 100644 --- a/src/lib/hooks/auth/useAxiosPrivate.tsx +++ b/src/lib/hooks/auth/useAxiosPrivate.tsx @@ -1,12 +1,11 @@ import { useEffect } from 'react'; import { useAppSelector } from '@store/app/hooks'; -import useRefreshToken from '@lib/hooks/auth/useRefreshToken'; +import useRefreshAccessToken from '@lib/hooks/auth/useRefreshAccessToken'; import { selectAccessToken } from '@store/slices/authSlice'; import { axiosPrivateInstance } from '@lib/api/axios'; -// https://axios-http.com/kr/docs/interceptors function useAxiosPrivate() { - const refresh = useRefreshToken(); + const refreshAccessToken = useRefreshAccessToken(); const accessToken = useAppSelector(selectAccessToken); useEffect(() => { @@ -27,13 +26,13 @@ function useAxiosPrivate() { async (error) => { const prevRequest = error?.config; if (error?.response?.status === 401 && !prevRequest?.sent) { - // access token expired (401) -> refresh access token -> request prevRequest + // aT expired (401) -> refresh aT -> request prevRequest prevRequest.sent = true; - const newAccessToken = await refresh(); + const newAccessToken = await refreshAccessToken(); prevRequest.headers['X-AUTH-TOKEN'] = `${newAccessToken}`; return axiosPrivateInstance(prevRequest); } - return Promise.reject(error); // refresh token expired (again 401) -> navigate to loginPage + return Promise.reject(error); // rT expired (again 401) -> navigate to loginPage }, ); @@ -41,9 +40,11 @@ function useAxiosPrivate() { axiosPrivateInstance.interceptors.request.eject(requestIntercept); axiosPrivateInstance.interceptors.response.eject(responseIntercept); }; - }, [accessToken, refresh]); + }, [accessToken, refreshAccessToken]); - return axiosPrivateInstance; // return instance applied interceptors + return axiosPrivateInstance; // interceptors applied } export default useAxiosPrivate; + +// https://axios-http.com/kr/docs/interceptors diff --git a/src/lib/hooks/auth/useLocalStorage.tsx b/src/lib/hooks/auth/useLocalStorage.tsx index a317367f..d78ff022 100644 --- a/src/lib/hooks/auth/useLocalStorage.tsx +++ b/src/lib/hooks/auth/useLocalStorage.tsx @@ -1,14 +1,15 @@ import { useState, useEffect } from 'react'; function getLocalValue(key: string, initValue: any) { - //SSR Next.js + // SSR (Next.js) if (typeof window === 'undefined') return initValue; - // if a value is already store + // @ts-expect-error const localValue = JSON.parse(localStorage.getItem(key)); - if (localValue) return localValue; + if (localValue) return localValue; // if a value is already store // return result of a function if (initValue instanceof Function) return initValue(); + return initValue; } @@ -16,9 +17,11 @@ function useLocalStorage(key: string, initValue: any) { const [value, setValue] = useState(() => { return getLocalValue(key, initValue); }); + useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); + return [value, setValue]; } diff --git a/src/lib/hooks/auth/useLogout.tsx b/src/lib/hooks/auth/useLogout.tsx index ea07af81..88774994 100644 --- a/src/lib/hooks/auth/useLogout.tsx +++ b/src/lib/hooks/auth/useLogout.tsx @@ -2,7 +2,6 @@ import useAxiosPrivate from '@lib/hooks/auth/useAxiosPrivate'; import { resetCredentials } from '@store/slices/authSlice'; import { useDispatch } from 'react-redux'; -// TODO: 로그아웃 기능 추후 구현 예정 function useLogout() { const dispatch = useDispatch(); const axiosPrivate = useAxiosPrivate(); @@ -10,7 +9,7 @@ function useLogout() { const logout = async () => { dispatch(resetCredentials()); try { - const response = await axiosPrivate.get('/users/logout'); + await axiosPrivate.get('/users/logout'); } catch (error) { console.error(error); } diff --git a/src/lib/hooks/auth/useRefreshToken.tsx b/src/lib/hooks/auth/useRefreshAccessToken.tsx similarity index 50% rename from src/lib/hooks/auth/useRefreshToken.tsx rename to src/lib/hooks/auth/useRefreshAccessToken.tsx index d46a1659..79924b7d 100644 --- a/src/lib/hooks/auth/useRefreshToken.tsx +++ b/src/lib/hooks/auth/useRefreshAccessToken.tsx @@ -2,17 +2,16 @@ import { useAppDispatch } from '../../../store/app/hooks'; import { axiosPublic } from '../../api/axios'; import { setCredentials } from '@store/slices/authSlice'; -function useRefreshToken() { +function useRefreshAccessToken() { const dispatch = useAppDispatch(); - const refresh = async () => { + const refreshAccessToken = async () => { const response = await axiosPublic.patch('/user/refresh'); - const { accessToken, isKid, level } = response.data.data; - dispatch(setCredentials({ accessToken, isKid, level })); + const { accessToken, isKid, level, provider } = response.data.data; + dispatch(setCredentials({ accessToken, isKid, level, provider })); return accessToken; }; - - return refresh; + return refreshAccessToken; } -export default useRefreshToken; +export default useRefreshAccessToken; diff --git a/src/lib/hooks/useIsFetched.tsx b/src/lib/hooks/useIsFetched.tsx index b6f896a2..35126823 100644 --- a/src/lib/hooks/useIsFetched.tsx +++ b/src/lib/hooks/useIsFetched.tsx @@ -19,11 +19,11 @@ function useIsFetched( ); } else if (target === 'proposedDongils') { isFetched = proposedDongils?.find( - (proposedDongil) => proposedDongil.userName === selectedKid?.username, + (proposedDongil) => proposedDongil.kidId === selectedKid?.kidId, ); } else if (target === 'thisWeekSDongils') { isFetched = thisWeekSDongils?.find( - (thisWeekSDongil) => thisWeekSDongil.userName === selectedKid?.username, + (thisWeekSDongil) => thisWeekSDongil.kidId === selectedKid?.kidId, ); } return isFetched; diff --git a/src/lib/hooks/useLevel.tsx b/src/lib/hooks/useLevel.tsx index 555c6b3c..e0dfcd66 100644 --- a/src/lib/hooks/useLevel.tsx +++ b/src/lib/hooks/useLevel.tsx @@ -6,7 +6,8 @@ import { selectSelectedKid } from '@store/slices/kidsSlice'; function useLevel() { const isKid = useAppSelector(selectIsKid); const selectedKid = useAppSelector(selectSelectedKid); - let level = null; + + let level; const temp = useAppSelector(selectLevel)!; if (isKid === true) { level = temp; diff --git a/src/lib/hooks/useModals.tsx b/src/lib/hooks/useModals.tsx index 2700b2ad..5c70f01e 100644 --- a/src/lib/hooks/useModals.tsx +++ b/src/lib/hooks/useModals.tsx @@ -19,7 +19,9 @@ import { useModalsDispatch } from '../../components/common/modals/ModalsContext'; -// OPEN, CLOSE action 대한 dispatch 함수 사용 추상화 +/** + * OPEN, CLOSE action 대한 dispatch 함수 사용 추상화 + */ function useModals() { const dispatch = useModalsDispatch(); diff --git a/src/lib/hooks/usePreventGoBack.tsx b/src/lib/hooks/usePreventGoBack.tsx index b98d9088..71b3de49 100644 --- a/src/lib/hooks/usePreventGoBack.tsx +++ b/src/lib/hooks/usePreventGoBack.tsx @@ -1,9 +1,10 @@ import { useEffect } from 'react'; +function setState() { + history.pushState(null, '', location.href); +} + function usePreventGoBack() { - const setState = () => { - history.pushState(null, '', location.href); - }; useEffect(() => { history.pushState(null, '', location.href); window.addEventListener('popstate', setState); diff --git a/src/lib/hooks/useRegisterFCMToken.tsx b/src/lib/hooks/useRegisterFCMToken.tsx new file mode 100644 index 00000000..d5b839d7 --- /dev/null +++ b/src/lib/hooks/useRegisterFCMToken.tsx @@ -0,0 +1,33 @@ +import useAxiosPrivate from './auth/useAxiosPrivate'; + +function useRegisterFCMToken() { + const axiosPrivate = useAxiosPrivate(); + + let FCMToken = 'web'; + const eventListener = (event: any) => { + FCMToken = event.data; + alert(FCMToken); + }; + + const registerFCMToken = async () => { + if (window.ReactNativeWebView) { + document.addEventListener('message', eventListener); // AOS + window.addEventListener('message', eventListener); // iOS + } + + try { + // TODO: response, console.log 삭제 + alert(FCMToken); + const response = axiosPrivate.patch('/user/expo', { + expoToken: FCMToken, + }); + console.log('response: ', response); + } catch (error: any) { + console.error(error); + } + }; + + return registerFCMToken; +} + +export default useRegisterFCMToken; diff --git a/src/lib/styles/styled.d.ts b/src/lib/styles/styled.d.ts index bc9ba390..e5ab2726 100644 --- a/src/lib/styles/styled.d.ts +++ b/src/lib/styles/styled.d.ts @@ -41,6 +41,9 @@ declare module 'styled-components' { medium: '12px'; large: '24px'; }; + border: { + receipt: string; + }; transition: { inputFocus: string; kidSelect: string; diff --git a/src/lib/styles/theme.tsx b/src/lib/styles/theme.tsx index fcf178e0..0c2e5e8e 100644 --- a/src/lib/styles/theme.tsx +++ b/src/lib/styles/theme.tsx @@ -53,6 +53,9 @@ export const theme: DefaultTheme = { medium: '12px', large: '24px', }, + border: { + receipt: '#eaeaec dotted 2px', + }, transition: { inputFocus: '0.1s all ease-in', kidSelect: '0.125s all ease-in', diff --git a/src/pages/Mypage/Info.tsx b/src/pages/Mypage/Info.tsx deleted file mode 100644 index c6c981d7..00000000 --- a/src/pages/Mypage/Info.tsx +++ /dev/null @@ -1,5 +0,0 @@ -function CommonInfo() { - return <>공통-마이페이지 내정보 수정; -} - -export default CommonInfo; diff --git a/src/pages/Mypage/Mypage.tsx b/src/pages/Mypage/Mypage.tsx index d3ec36db..b0f09483 100644 --- a/src/pages/Mypage/Mypage.tsx +++ b/src/pages/Mypage/Mypage.tsx @@ -6,14 +6,17 @@ import MyLevel from '@components/mypage/MyLevel'; import OverView from '@components/mypage/OverView'; import useFamilyApi from '@lib/api/family/useFamilyApi'; import useUserApi from '@lib/api/user/useUserAPi'; -import { FAMILY, KID, USER } from '@lib/constants/queryKeys'; +import { ReactComponent as Setting } from '@assets/icons/setting.svg'; +import { FAMILY, KID, USER } from '@lib/constants/QUERY_KEY'; import useGlobalBottomSheet from '@lib/hooks/useGlobalBottomSheet'; import { darken } from 'polished'; import { useMutation, useQueries, useQuery, useQueryClient } from 'react-query'; import styled, { css } from 'styled-components'; +import { useNavigate } from 'react-router-dom'; function Mypage() { const queryClient = useQueryClient(); + const navigate = useNavigate(); const { setOpenBottomSheet } = useGlobalBottomSheet(); const { getFamily, createFamily } = useFamilyApi(); const { getUser } = useUserApi(); @@ -50,7 +53,10 @@ function Mypage() { return ( -
마이페이지
+
+ 마이페이지 + navigate('/setting')} /> +
{userStatus === 'success' ? ( @@ -102,7 +108,7 @@ const Header = styled.div` ${({ theme }) => theme.typo.fixed.TabName_T_21_EB} color: ${({ theme }) => theme.palette.greyScale.black}; height: 48px; - padding: 0px 16px; + padding: 0px 6px 0px 16px; box-sizing: border-box; display: flex; align-items: center; @@ -111,6 +117,10 @@ const Header = styled.div` top: 0px; width: 100%; z-index: 3; + justify-content: space-between; + svg { + cursor: pointer; + } `; const Section = styled.div<{ smallGap?: boolean }>` diff --git a/src/pages/Mypage/index.tsx b/src/pages/Mypage/index.tsx index 2cf7986a..7efb0fc3 100644 --- a/src/pages/Mypage/index.tsx +++ b/src/pages/Mypage/index.tsx @@ -1,5 +1,5 @@ import { Routes, Route } from 'react-router-dom'; -import Info from './Info'; +import Setting from '../Setting/Setting'; import BackgroundTemplate from '@components/layout/BackgroundTemplate'; import Mypage from './Mypage'; @@ -14,7 +14,6 @@ function MypageRouter() { } /> - } /> ); } diff --git a/src/pages/OnBoarding/APPLEAuthRedirectPage.tsx b/src/pages/OnBoarding/APPLEAuthRedirectPage.tsx index 6e61e286..4078ed64 100644 --- a/src/pages/OnBoarding/APPLEAuthRedirectPage.tsx +++ b/src/pages/OnBoarding/APPLEAuthRedirectPage.tsx @@ -4,24 +4,15 @@ import { useNavigate } from 'react-router-dom'; import { setCredentials } from '@store/slices/authSlice'; import { string } from 'prop-types'; import stringToBooleanOrNull from '@lib/utils/stringToBooleanOrNull'; +import useRegisterFCMToken from '@lib/hooks/useRegisterFCMToken'; +import CustomSyncLoader from '@components/common/CustomSyncLoader'; function APPLEAuthRedirectPage() { const dispatch = useAppDispatch(); const navigate = useNavigate(); + const registerFCMToken = useRegisterFCMToken(); useEffect(() => { - console.log('apple callback redirected'); - console.log('login API'); - - document.addEventListener('AppleIDSignInOnSuccess', (data) => { - console.log('handle success'); - console.log('data:', data); - }); - document.addEventListener('AppleIDSignInOnFailure', (error) => { - console.error('handle error'); - console.error('error: ', error); - }); - // @ts-expect-error const params = new URL(document.location).searchParams; const accessToken = params.get('aT'); @@ -29,25 +20,29 @@ function APPLEAuthRedirectPage() { params.get('isKid') && stringToBooleanOrNull(params.get('isKid')!); const level = params.get('level') && stringToBooleanOrNull(params.get('level')!); - - const error = params.get('error'); - if (!error) { - console.log(error); - } + const provider = params.get('provider'); console.log('accessToken: ', accessToken); console.log('isKid: ', isKid); console.log('level: ', level); - console.log('error: ', error); + console.log('provider: ', provider); - const canSetCredentials = accessToken && isKid && level; + const canSetCredentials = accessToken && isKid && level && provider; canSetCredentials && - dispatch(setCredentials({ accessToken, isKid, level })); - - // navigate('/'); + dispatch(setCredentials({ accessToken, isKid, level, provider })); + + async function proceedLogin() { + try { + await registerFCMToken(); + navigate('/'); + } catch (error: any) { + console.error(error); + } + } + proceedLogin(); }, []); - return

애플 로그인 처리중입니다...

; + return ; } export default APPLEAuthRedirectPage; diff --git a/src/pages/OnBoarding/KAKAOAuthRedirectPage.tsx b/src/pages/OnBoarding/KAKAOAuthRedirectPage.tsx index 966c865e..1c355b22 100644 --- a/src/pages/OnBoarding/KAKAOAuthRedirectPage.tsx +++ b/src/pages/OnBoarding/KAKAOAuthRedirectPage.tsx @@ -1,33 +1,52 @@ import { useEffect } from 'react'; import { useAppDispatch } from '@store/app/hooks'; import { login } from '@store/slices/authSlice'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; +import useRegisterFCMToken from '@lib/hooks/useRegisterFCMToken'; +import CustomSyncLoader from '@components/common/CustomSyncLoader'; function KAKAOAuthRedirectPage() { const dispatch = useAppDispatch(); const navigate = useNavigate(); - // const location = useLocation(); + const registerFCMToken = useRegisterFCMToken(); - // const href = window.location.href; // @ts-expect-error const params = new URL(document.location).searchParams; const code = params.get('code'); useEffect(() => { - async function dispatchLogin() { + async function proceedLogin() { try { code && (await dispatch(login({ code })).unwrap()); + await registerFCMToken(); navigate('/'); } catch (error: any) { console.error(error); } } - dispatchLogin(); + proceedLogin(); }, []); - return

카카오 로그인 처리중입니다...

; + return ; } export default KAKAOAuthRedirectPage; // https://velog.io/@he0_077/useEffect-%ED%9B%85%EC%97%90%EC%84%9C-async-await-%ED%95%A8%EC%88%98-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0 + +// TODO: 테스트 후 주석 삭제 +// const RNListener = () => { +// const listener = (event: any) => { +// alert(event.data); +// alert(typeof event.data); +// }; + +// if (window.ReactNativeWebView) { +// document.addEventListener('message', listener); // AOS +// window.addEventListener('message', listener); // iOS +// } +// }; + +// useEffect(() => { +// RNListener(); +// }, []); diff --git a/src/pages/OnBoarding/LoginPage.tsx b/src/pages/OnBoarding/LoginPage.tsx index c2312ed3..3412872f 100644 --- a/src/pages/OnBoarding/LoginPage.tsx +++ b/src/pages/OnBoarding/LoginPage.tsx @@ -1,8 +1,9 @@ import styled from 'styled-components'; import Button from '@components/common/buttons/Button'; import MarginTemplate from '@components/layout/MarginTemplate'; -import { APPLE_AUTH_URL, KAKAO_AUTH_URL } from '@lib/constants'; import { ReactComponent as Logo } from '@assets/icons/logo.svg'; +import { KAKAO_AUTH_URL } from '@lib/constants/KAKAO_AUTH_URL'; +import { APPLE_AUTH_URL } from '@lib/constants/APPLE_AUTH_URL'; function LoginPage() { return ( diff --git a/src/pages/Setting/Alerts.tsx b/src/pages/Setting/Alerts.tsx new file mode 100644 index 00000000..31a4d59d --- /dev/null +++ b/src/pages/Setting/Alerts.tsx @@ -0,0 +1,4 @@ +const Alerts = () => { + return <>; +}; +export default Alerts; diff --git a/src/pages/Setting/Faq.tsx b/src/pages/Setting/Faq.tsx new file mode 100644 index 00000000..16ed36d8 --- /dev/null +++ b/src/pages/Setting/Faq.tsx @@ -0,0 +1,5 @@ +const Faq = () => { + return <>; +}; + +export default Faq; diff --git a/src/pages/Setting/Features.tsx b/src/pages/Setting/Features.tsx new file mode 100644 index 00000000..009b8062 --- /dev/null +++ b/src/pages/Setting/Features.tsx @@ -0,0 +1,4 @@ +const Features = () => { + return <>; +}; +export default Features; diff --git a/src/pages/Setting/Guides.tsx b/src/pages/Setting/Guides.tsx new file mode 100644 index 00000000..aee323bd --- /dev/null +++ b/src/pages/Setting/Guides.tsx @@ -0,0 +1,4 @@ +const Guides = () => { + return <>; +}; +export default Guides; diff --git a/src/pages/Setting/Inquiry.tsx b/src/pages/Setting/Inquiry.tsx new file mode 100644 index 00000000..6e885f9b --- /dev/null +++ b/src/pages/Setting/Inquiry.tsx @@ -0,0 +1,5 @@ +const Inquiry = () => { + return <>; +}; + +export default Inquiry; diff --git a/src/pages/Setting/Notices.tsx b/src/pages/Setting/Notices.tsx new file mode 100644 index 00000000..b7a6b990 --- /dev/null +++ b/src/pages/Setting/Notices.tsx @@ -0,0 +1,47 @@ +import ForegroundTemplate from '@components/layout/ForegroundTemplate'; +import noticeData from '@components/setting/notices/noticeData'; +import { useNavigate } from 'react-router-dom'; +import styled from 'styled-components'; + +const Notices = () => { + const navigate = useNavigate(); + return ( + + <> + {noticeData.map((notice) => ( + { + navigate(`${notice.id}`); + }} + > +

{notice.title}

+

{notice.date}

+
+ ))} + +
+ ); +}; +export default Notices; + +const Item = styled.button` + padding: 20px 26px; + width: 100%; + height: 82px; + box-sizing: border-box; + border-bottom: 1px solid ${({ theme }) => theme.palette.greyScale.grey200}; + display: flex; + flex-direction: column; + justify-content: space-between; + &:first-child { + margin-top: 20px; + } + p:first-child { + ${({ theme }) => theme.typo.text.T_16_EB} + color: ${({ theme }) => theme.palette.greyScale.black}; + } + p:last-child { + ${({ theme }) => theme.typo.text.S_12_M} + color: ${({ theme }) => theme.palette.greyScale.grey600}; + } +`; diff --git a/src/pages/Setting/Privacy.tsx b/src/pages/Setting/Privacy.tsx new file mode 100644 index 00000000..fb9f1708 --- /dev/null +++ b/src/pages/Setting/Privacy.tsx @@ -0,0 +1,5 @@ +const Privacy = () => { + return <>; +}; + +export default Privacy; diff --git a/src/pages/Setting/Setting.tsx b/src/pages/Setting/Setting.tsx new file mode 100644 index 00000000..fa29e91f --- /dev/null +++ b/src/pages/Setting/Setting.tsx @@ -0,0 +1,62 @@ +import ForegroundTemplate from '@components/layout/ForegroundTemplate'; +import { Route, Routes, useNavigate } from 'react-router-dom'; +import styled from 'styled-components'; +import { ReactComponent as Arrow } from '@assets/icons/arrow-walking.svg'; +const contents = [ + { title: '공지사항', link: 'notices' }, + { title: '서비스 소개', link: 'features' }, + { title: '서비스 이용 방법', link: 'guides' }, + { title: '알림 설정', link: 'alerts' }, + { title: '개인정보 처리방침', link: 'privacy' }, + { title: '서비스 약관', link: 'terms' }, + { title: '자주 묻는 질문', link: 'faq' }, + { title: '문의하기', link: 'inquiry' }, + { title: '로그아웃', link: '' }, + { title: '탈퇴하기', link: 'withdraw' }, +]; + +function Setting() { + const navigate = useNavigate(); + return ( + + <> + {contents.map((content) => ( + { + content.title !== '로그아웃' && navigate(content.link); + }} + > +

{content.title}

+ +
+ ))} + +
+ ); +} + +export default Setting; + +const Item = styled.button` + height: 56px; + width: 100%; + box-sizing: border-box; + display: flex; + justify-content: space-between; + padding: 0 26px; + align-items: center; + border-bottom: 1px solid ${({ theme }) => theme.palette.greyScale.grey200}; + p { + ${({ theme }) => theme.typo.fixed.EmptyText_S_16_M} + color: ${({ theme }) => theme.palette.greyScale.grey700}; + margin-top: 4px; + } + svg { + fill: ${({ theme }) => theme.palette.greyScale.grey500}; + } + + &:first-child { + margin-top: 20px; + } +`; diff --git a/src/pages/Setting/Tems.tsx b/src/pages/Setting/Tems.tsx new file mode 100644 index 00000000..2c9a9a73 --- /dev/null +++ b/src/pages/Setting/Tems.tsx @@ -0,0 +1,5 @@ +const Terms = () => { + return <>; +}; + +export default Terms; diff --git a/src/pages/Setting/Withdraw.tsx b/src/pages/Setting/Withdraw.tsx new file mode 100644 index 00000000..9dc754ea --- /dev/null +++ b/src/pages/Setting/Withdraw.tsx @@ -0,0 +1,5 @@ +const Withdraw = () => { + return <>; +}; + +export default Withdraw; diff --git a/src/pages/Setting/index.tsx b/src/pages/Setting/index.tsx new file mode 100644 index 00000000..d7d47a8f --- /dev/null +++ b/src/pages/Setting/index.tsx @@ -0,0 +1,32 @@ +import Notice from '@components/setting/notices/Notice'; +import { Route, Routes } from 'react-router-dom'; +import Alerts from './Alerts'; +import Faq from './Faq'; +import Features from './Features'; +import Guides from './Guides'; +import Inquiry from './Inquiry'; +import Notices from './Notices'; +import Privacy from './Privacy'; +import Setting from './Setting'; +import Terms from './Tems'; +import Withdraw from './Withdraw'; + +const SettingRouter = () => { + return ( + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + ); +}; + +export default SettingRouter; diff --git a/src/pages/SungwooTestPage.tsx b/src/pages/SungwooTestPage.tsx deleted file mode 100644 index d07c918e..00000000 --- a/src/pages/SungwooTestPage.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import styled from 'styled-components'; -import useModals from '@lib/hooks/useModals'; -import Modals, { modals } from '@components/common/modals/Modals'; -import MarginTemplate from '@components/layout/MarginTemplate'; -import SkeletonSummary from '@components/common/skeletons/SkeletonSummary'; -import SkeletonDongilList from '@components/common/skeletons/SkeletonDongilList'; - -function SungwooTestPage() { - const { openModal } = useModals(); - function openTertiary() { - openModal(modals.tertiaryModal, { - onSubmit: () => { - console.log('handle submit'); - }, - }); - } - function openProposed() { - openModal(modals.receiptModal, { - variant: 'proposed', - onSubmit: () => { - console.log('handle submit'); - }, - onExtraSubmit: () => { - console.log('handle extra submit'); - }, - createdAt: '2022/08/03 04:06:04', - interestRate: 20, - isMom: false, - itemName: '선물', - title: '할머니 생신선물', - totalPrice: 10000, - weekPrice: 2000, - weeks: 4, - }); - } - function openProposing() { - openModal(modals.receiptModal, { - variant: 'proposing', - onSubmit: () => { - console.log('handle submit'); - }, - createdAt: '2022/08/03 04:06:04', - interestRate: 20, - isMom: false, - itemName: '선물', - title: '할머니 생신선물', - totalPrice: 10000, - weekPrice: 2000, - weeks: 4, - }); - } - function openRejected() { - // modals.myModal: 열고자 하는 모달 - // {...}: submit 시 처리되는 비즈니스 로직 - openModal(modals.receiptModal, { - variant: 'rejected', - onSubmit: () => { - console.log('handle submit'); - }, - createdAt: '2022/08/03 04:06:04', - interestRate: 20, - isMom: false, - itemName: '선물', - title: '할머니 생신선물', - totalPrice: 10000, - weekPrice: 2000, - weeks: 4, - comment: '큰 이자를 줄만한 목표가 아닌것 같다~', - }); - } - - return ( - - - - - {/* - - - */} - {/* - */} - - - ); -} - -export default SungwooTestPage; - -const Wrapper = styled.div` - height: 1000px; - background: pink; -`; - -const RectangleWrapper = styled.div` - width: 100%; - height: 50px; -`; - -const CircleWrapper = styled.div` - background: cyan; - width: 200px; - height: 200px; -`; - -// https://joyful-development.tistory.com/35 -// https://velog.io/@sohee-k/React-TypeScript-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-Swiper-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0image-slider-library - -// // JWT test code -// const refresh = useRefreshToken(); -// async function handleRefresh() { -// const newAccessToken = await refresh(); -// console.log('newAccessToken in handleRefresh: ', newAccessToken); -// } -// const axiosPrivate = useAxiosPrivate(); -// async function handleRequestWithAT() { -// const response = await axiosPrivate.get('/user'); -// console.log('response.data in handleRequest: ', response); // response.status 401 인지 확인 -// console.log('response.data in handleRequest: ', response.status); // response.status 401 인지 확인 -// } -// const auth = useAppSelector(selectAuth); -// function handlePrint() { -// console.log('======================='); -// console.log(auth); -// console.log('======================='); -// } -// async function handleFetchWalkingDongils() { -// const response = await axiosPrivate.get('/challenge/?status=accept'); -// console.log('response in fetch walking roads: ', response); -// console.log('response.data in fetch walking roads: ', response.data); -// } - -// -// -// -// - -{ - /* - - - - - - */ -} diff --git a/src/pages/Test/AuthTest.tsx b/src/pages/Test/AuthTest.tsx new file mode 100644 index 00000000..bfbb8f51 --- /dev/null +++ b/src/pages/Test/AuthTest.tsx @@ -0,0 +1,48 @@ +import styled from 'styled-components'; +import useRefreshAccessToken from '@lib/hooks/auth/useRefreshAccessToken'; +import useAxiosPrivate from '@lib/hooks/auth/useAxiosPrivate'; +import { useAppSelector } from '@store/app/hooks'; +import { selectAuth } from '@store/slices/authSlice'; + +function AuthTest() { + const refreshAccessToken = useRefreshAccessToken(); + async function handleRefresh() { + const newAccessToken = await refreshAccessToken(); + console.log('newAccessToken in handleRefresh: ', newAccessToken); + } + const axiosPrivate = useAxiosPrivate(); + async function handleRequestWithAT() { + const response = await axiosPrivate.get('/user'); + console.log('response.data in handleRequest: ', response); // response.status 401 인지 확인 + console.log('response.data in handleRequest: ', response.status); // response.status 401 인지 확인 + } + const auth = useAppSelector(selectAuth); + function handlePrint() { + console.log('======================='); + console.log(auth); + console.log('======================='); + } + async function handleFetchWalkingDongils() { + const response = await axiosPrivate.get('/challenge/?status=accept'); + console.log('response in fetch walking roads: ', response); + console.log('response.data in fetch walking roads: ', response.data); + } + + return ( + + + + + + + ); +} + +export default AuthTest; + +const Wrapper = styled.div` + height: 1000px; + background: pink; +`; diff --git a/src/pages/Test/ModalTest.tsx b/src/pages/Test/ModalTest.tsx new file mode 100644 index 00000000..5ddc3d09 --- /dev/null +++ b/src/pages/Test/ModalTest.tsx @@ -0,0 +1,102 @@ +import styled from 'styled-components'; +import useModals from '@lib/hooks/useModals'; +import Modals, { modals } from '@components/common/modals/Modals'; + +function ModalTest() { + const { openModal } = useModals(); + + function openTertiary() { + openModal(modals.tertiaryModal, { + onSubmit: () => { + console.log('handle submit'); + }, + }); + } + function openContract() { + openModal(modals.receiptModal, { + variant: 'contract', + onSubmit: () => { + console.log('handle submit'); + }, + onExtraSubmit: () => { + console.log('handle extra submit'); + }, + createdAt: '2022/08/03 04:06:04', + interestRate: 20, + isMom: false, + itemName: '선물', + title: '할머니 생신선물', + totalPrice: 10000, + weekPrice: 2000, + weeks: 4, + }); + } + function openProposed() { + openModal(modals.receiptModal, { + variant: 'proposed', + onSubmit: () => { + console.log('handle submit'); + }, + onExtraSubmit: () => { + console.log('handle extra submit'); + }, + createdAt: '2022/08/03 04:06:04', + interestRate: 20, + isMom: false, + itemName: '선물', + title: '할머니 생신선물', + totalPrice: 10000, + weekPrice: 2000, + weeks: 4, + }); + } + function openProposing() { + openModal(modals.receiptModal, { + variant: 'proposing', + onSubmit: () => { + console.log('handle submit'); + }, + createdAt: '2022/08/03 04:06:04', + interestRate: 20, + isMom: false, + itemName: '선물', + title: '할머니 생신선물', + totalPrice: 10000, + weekPrice: 2000, + weeks: 4, + }); + } + + function openRejected() { + openModal(modals.receiptModal, { + variant: 'rejected', + onSubmit: () => { + console.log('handle submit'); + }, + createdAt: '2022/08/03 04:06:04', + interestRate: 20, + isMom: false, + itemName: '선물', + title: '할머니 생신선물', + totalPrice: 10000, + weekPrice: 2000, + weeks: 4, + comment: { + content: '큰 이자를 줄만한 목표가 아닌것 같다~', + id: '3', + }, + }); + } + + return ( + <> + + + + + + + ); +} + +export default ModalTest; diff --git a/src/pages/Test/TestPage.tsx b/src/pages/Test/TestPage.tsx new file mode 100644 index 00000000..a794e1a2 --- /dev/null +++ b/src/pages/Test/TestPage.tsx @@ -0,0 +1,19 @@ +import styled from 'styled-components'; +import LoadingSpinnerTest from '../../components/common/CustomSyncLoader'; +import ModalTest from './ModalTest'; + +function TestPage() { + return ( + + {/* */} + + + ); +} + +export default TestPage; + +const Wrapper = styled.div` + height: 1000px; + /* background: pink; */ +`; diff --git a/src/store/slices/authSlice.tsx b/src/store/slices/authSlice.tsx index 3dc5bce8..b285998f 100644 --- a/src/store/slices/authSlice.tsx +++ b/src/store/slices/authSlice.tsx @@ -8,6 +8,7 @@ interface IAuth { accessToken: string; isKid: boolean | null; level: TLevel | null; + provider: string; birthday: string; username: string; isFemale: boolean; @@ -27,9 +28,10 @@ interface IAuthState { const initialState: IAuthState = { auth: { accessToken: - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJiYW5raWRzIiwiaWF0IjoxNjYxNDM1MTg5LCJzdWIiOiI1IiwiZXhwIjoxNjYzODU0Mzg5LCJpZCI6NSwicm9sZXMiOiJVU0VSIn0.SyPHLiGa68dGG7iYfo1_k-HRUiL80K0Gk03D0GVQrzI', - isKid: false, + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJiYW5raWRzIiwiaWF0IjoxNjYxNDQwMjUxLCJzdWIiOiI0IiwiZXhwIjoxNjYzODU5NDUxLCJpZCI6NCwicm9sZXMiOiJVU0VSIn0.uqkdNdiIhz5Aix1mY-wx2TIYM2DS8pPqNPNNqrTe7lg', + isKid: true, level: null, + provider: '', birthday: '', username: '', isFemale: false, @@ -49,15 +51,15 @@ export const login = createAsyncThunk( }, ); +interface IRegisterThunkPayload + extends Pick { + axiosPrivate: AxiosInstance; +} + // PATCH: 생년월일과 역할 정보가 없는 회원에 대해 입력받은 정보를 서버로 전송 export const register = createAsyncThunk( 'auth/register', - async (thunkPayload: { - axiosPrivate: AxiosInstance; - birthday: string; - isFemale: boolean; - isKid: boolean; - }) => { + async (thunkPayload: IRegisterThunkPayload) => { const { axiosPrivate, birthday, isFemale, isKid } = thunkPayload; const response = await axiosPrivate.patch('/user', { birthday, @@ -68,22 +70,25 @@ export const register = createAsyncThunk( }, ); -interface ICredentials extends Pick {} +interface ICredentials + extends Pick {} export const authSlice = createSlice({ name: 'auth', initialState, reducers: { setCredentials: (state, action: PayloadAction) => { - const { accessToken, isKid, level } = action.payload; + const { accessToken, isKid, level, provider } = action.payload; state.auth.accessToken = accessToken; state.auth.isKid = isKid; state.auth.level = level; + state.auth.provider = provider; }, resetCredentials: (state) => { state.auth.accessToken = ''; state.auth.isKid = null; state.auth.level = null; + state.auth.provider = ''; }, setBirthday: (state, action: PayloadAction) => { state.auth.birthday = action.payload; @@ -92,10 +97,11 @@ export const authSlice = createSlice({ extraReducers(builder) { builder .addCase(login.fulfilled, (state, action) => { - const { accessToken, isKid, level } = action.payload.data; + const { accessToken, isKid, level, provider } = action.payload.data; state.auth.accessToken = accessToken; state.auth.isKid = isKid; state.auth.level = level; + state.auth.provider = provider; }) .addCase(register.fulfilled, (state, action) => { const { birthday, isFemale, isKid, phone, username } = diff --git a/src/store/slices/kidSummarySlice.tsx b/src/store/slices/kidSummarySlice.tsx index 5c9b3eee..6df56736 100644 --- a/src/store/slices/kidSummarySlice.tsx +++ b/src/store/slices/kidSummarySlice.tsx @@ -49,7 +49,6 @@ export const kidSummarySlice = createSlice({ export const selectKidSummaryStatus = (state: RootState) => state.kidSummary.kidSummaryStatus; - export const selectKidSummary = (state: RootState) => state.kidSummary.kidSummary; diff --git a/src/store/slices/parentSummariesSlice.tsx b/src/store/slices/parentSummariesSlice.tsx index 04d8d066..82c6bbc1 100644 --- a/src/store/slices/parentSummariesSlice.tsx +++ b/src/store/slices/parentSummariesSlice.tsx @@ -21,10 +21,15 @@ const initialState: IParentSummariesState = { parentSummariesStatus: 'idle', }; +interface IFetchParentSummariesThunkPayload + extends Pick { + axiosPrivate: AxiosInstance; +} + // GET: 부모 홈 페이지 Summary 데이터 조회 export const fetchParentSummaries = createAsyncThunk( 'parentSummaries/fetch', - async (thunkPayload: { axiosPrivate: AxiosInstance; kidId: number }) => { + async (thunkPayload: IFetchParentSummariesThunkPayload) => { const { axiosPrivate, kidId } = thunkPayload; const response = await axiosPrivate.get(`/challenge/kid/progress/${kidId}`); return response.data; diff --git a/src/store/slices/proposedDongilsSlice.tsx b/src/store/slices/proposedDongilsSlice.tsx index 0ef20190..05136e6a 100644 --- a/src/store/slices/proposedDongilsSlice.tsx +++ b/src/store/slices/proposedDongilsSlice.tsx @@ -5,7 +5,7 @@ import { AxiosInstance } from 'axios'; import { RootState } from '../app/store'; interface IProposedDongil { - userName: string; + kidId: number; isFemale: boolean; challengeList: IDongil[]; } @@ -20,10 +20,15 @@ const initialState: IProposedDongilsState = { proposedDongilsStatus: 'idle', }; +interface IFetchProposedDongilsThunkPayload + extends Pick { + axiosPrivate: AxiosInstance; +} + // GET: 제안받은 돈길 조회 export const fetchProposedDongils = createAsyncThunk( 'proposedDongils/fetch', - async (thunkPayload: { axiosPrivate: AxiosInstance; kidId: number }) => { + async (thunkPayload: IFetchProposedDongilsThunkPayload) => { const { axiosPrivate, kidId } = thunkPayload; const response = await axiosPrivate.get( `/challenge/kid/${kidId}?status=pending`, diff --git a/src/store/slices/thisWeekSDongilsSlice.tsx b/src/store/slices/thisWeekSDongilsSlice.tsx index d20c711e..dd296f95 100644 --- a/src/store/slices/thisWeekSDongilsSlice.tsx +++ b/src/store/slices/thisWeekSDongilsSlice.tsx @@ -6,7 +6,7 @@ import { AxiosInstance } from 'axios'; import { RootState } from '../app/store'; interface IThisWeekSDongil { - userName: string; + kidId: number; isFemale: boolean; challengeList: IDongil[]; } @@ -21,10 +21,15 @@ const initialState: IThisWeekSDongilsState = { thisWeekSDongilsStatus: 'idle', }; +interface IFetchThisWeekSDongilsThunkPayload + extends Pick { + axiosPrivate: AxiosInstance; +} + // GET: 금주의 돈길 조회 export const fetchThisWeekSDongils = createAsyncThunk( 'thisWeekSDongils/fetch', - async (thunkPayload: { axiosPrivate: AxiosInstance; kidId: number }) => { + async (thunkPayload: IFetchThisWeekSDongilsThunkPayload) => { const { axiosPrivate, kidId } = thunkPayload; const response = await axiosPrivate.get( `/challenge/kid/${kidId}?status=walking`, @@ -43,7 +48,7 @@ export const thisWeekSDongilsSlice = createSlice({ ) => { const { selectedKid, approvedDongil } = action.payload; state.thisWeekSDongils = state.thisWeekSDongils.map((thisWeekSDongil) => { - if (thisWeekSDongil.userName === selectedKid.username) { + if (thisWeekSDongil.kidId === selectedKid.kidId) { thisWeekSDongil.challengeList = thisWeekSDongil.challengeList.concat(approvedDongil); } diff --git a/yarn.lock b/yarn.lock index 2d5678f2..d5199254 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14056,11 +14056,6 @@ react-app-polyfill@^3.0.0: regenerator-runtime "^0.13.9" whatwg-fetch "^3.6.2" -react-apple-login@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/react-apple-login/-/react-apple-login-1.1.5.tgz#847a9ab5bbb653c0d82aad435c1140913ff69fe9" - integrity sha512-RAsiA5ZSB/shFoXTeL8s5AVRcoQBy7iIZum8N/Wvs4DRh95UQuACAzDcbVaOJM1REcJwnyL539LYqP+3MEJbOw== - react-clientside-effect@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz#29f9b14e944a376b03fb650eed2a754dd128ea3a" @@ -14438,6 +14433,11 @@ react-sizeme@^2.6.7: shallowequal "^1.1.0" throttle-debounce "^2.1.0" +react-spinners@^0.13.4: + version "0.13.4" + resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.13.4.tgz#20f7435e5cb3a2bde23110efa8b7dfbe485373e9" + integrity sha512-V6IURjYOwomhdngMfuVxBp4utCF6v21sjQ6r4K2JoKl8fwXZp1UeHMBLf+2SU+cts8hAVj9rHOJ8kdT5UqqaJw== + react-spring-bottom-sheet@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/react-spring-bottom-sheet/-/react-spring-bottom-sheet-3.4.1.tgz#9a4f90b1c0af17eb4a22a606a5efc5d6e62c7b0c"