diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index 29c33774..33127c83 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -2,7 +2,7 @@ /* tslint:disable */ /** - * Mock Service Worker (2.0.0). + * Mock Service Worker (2.0.5). * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. diff --git a/src/assets/icons/KakaoIcon.tsx b/src/assets/icons/KakaoIcon.tsx index bea9b248..6c42296b 100644 --- a/src/assets/icons/KakaoIcon.tsx +++ b/src/assets/icons/KakaoIcon.tsx @@ -8,6 +8,14 @@ export type IconProps = { borderRadius: number } +/** + * @param width - 너비 (string) + * @param height - 높이 (string) + * @param iconWidth - (Optional) 아이콘 너비 (string) + * @param iconHeight - (Optional) 아이콘 높이 (string) + * @param borderRadius - (Optional) 아이콘 테두리 반지름 (string) + */ + const KakaoIcon = ({ width, height, iconWidth, iconHeight, borderRadius }: IconProps) => ( ( ` justify-content: flex-end; height: ${({ height }) => height}; text-align: center; - padding: 6.5% 5% 7%; + padding: 15% 5% 7%; ` const StyledAppHeaderLargeText = styled(Text)>` @@ -75,6 +75,9 @@ const AppHeader = ({ nickname, isDarkMode, height, toggleDarkMode }: AppHeaderPr height={49} imgUrl={''} margin={'0'} + style={{ + cursor: 'pointer', + }} onClick={() => { moveFromAppHeader('profile') }} diff --git a/src/components/common/Avatar/index.tsx b/src/components/common/Avatar/index.tsx index 0182e0b3..f1b5786b 100644 --- a/src/components/common/Avatar/index.tsx +++ b/src/components/common/Avatar/index.tsx @@ -6,43 +6,23 @@ type AvatarProps = { width: number | string height: number | string imgUrl: string - margin: string + margin?: string onClick?: () => void border?: string shadow?: boolean + style?: React.CSSProperties } -const StyledAvatar = styled.div` - width: ${(props) => (typeof props.width === 'number' ? `${props.width}px` : props.width)}; - height: ${(props) => (typeof props.height === 'number' ? `${props.height}px` : props.height)}; - background-image: url(${(props) => props.imgUrl}); - background-size: cover; - background-repeat: no-repeat; - background-position: center center; - border-radius: 50%; // 원 형태로 만들기 위함 - margin: ${(props) => `${props.margin}px`}; - border: ${(props) => (props.border ? props.border : 'none')}; - box-shadow: ${(props) => (props.shadow ? '0px 0px 10px rgba(0, 0, 0, 0.25)' : 'none')}; - - @media (max-width: 280px) { - width: ${(props) => - typeof props.width === 'number' ? `${props.width * 0.95}px` : `calc(${props.width} * 0.95)`}; - height: ${(props) => - typeof props.height === 'number' - ? `${props.height * 0.95}px` - : `calc(${props.height} * 0.95)`}; - } -` - /** * `Avatar` component for displaying profile images. * @param width - 아바타의 너비 (픽셀 또는 유효한 CSS 단위). * @param height - 아바타의 높이 (픽셀 또는 유효한 CSS 단위). * @param imgUrl - 아바타의 이미지 URL. 기본 이미지는 `defaultProfileImage`이다. - * @param margin - 아바타의 마진 (픽셀 또는 유효한 CSS 단위). + * @param margin - (Optional) 아바타의 마진 (픽셀 또는 유효한 CSS 단위). * @param onClick - (Optional) 클릭 이벤트. * @param border - (Optional) 아바타의 테두리. 기본 값은 `none`이다. * @param shadow - (Optional) 아바타의 그림자. 기본 값은 `false`이다. + * @param props - (Optional) 추가적인 CSS 속성. */ const Avatar = ({ width, @@ -52,6 +32,7 @@ const Avatar = ({ onClick, border, shadow = false, + ...props }: AvatarProps) => { return ( ) } +const StyledAvatar = styled.div` + width: ${(props) => (typeof props.width === 'number' ? `${props.width}px` : props.width)}; + height: ${(props) => (typeof props.height === 'number' ? `${props.height}px` : props.height)}; + background-image: url(${(props) => props.imgUrl}); + background-size: cover; + background-repeat: no-repeat; + background-position: center center; + border-radius: 50%; // 원 형태로 만들기 위함 + margin: ${(props) => `${props.margin}px`}; + border: ${(props) => (props.border ? props.border : 'none')}; + box-shadow: ${(props) => (props.shadow ? '0px 0px 10px rgba(0, 0, 0, 0.25)' : 'none')}; + + @media (max-width: 280px) { + width: ${(props) => + typeof props.width === 'number' ? `${props.width * 0.95}px` : `calc(${props.width} * 0.95)`}; + height: ${(props) => + typeof props.height === 'number' + ? `${props.height * 0.95}px` + : `calc(${props.height} * 0.95)`}; + } +` + export default Avatar diff --git a/src/components/common/BackChevron/index.tsx b/src/components/common/BackChevron/index.tsx index 0e9f69cc..02575d1f 100644 --- a/src/components/common/BackChevron/index.tsx +++ b/src/components/common/BackChevron/index.tsx @@ -10,6 +10,7 @@ const StyleBackChevron = styled.div` display: flex; justify-content: center; align-items: center; + cursor: pointer; background-color: ${({ hasBackground, isDarkMode }) => isDarkMode ? hasBackground diff --git a/src/components/common/Buttons/IconButton/IconButtonStyles.ts b/src/components/common/Buttons/IconButton/IconButtonStyles.ts index 3ed54696..227464a3 100644 --- a/src/components/common/Buttons/IconButton/IconButtonStyles.ts +++ b/src/components/common/Buttons/IconButton/IconButtonStyles.ts @@ -14,7 +14,7 @@ export type IconButtonType = export const iconButtonStyles: Record = { interest: { - width: '339px', + width: '100%', height: 70, fontColor: palette.WHITE, font: 'Body_18', @@ -25,7 +25,7 @@ export const iconButtonStyles: Record = { backgroundColor: `linear-gradient(96deg, #7382F8 49.74%, #A6BCFC 93.87%);`, }, 'interest-dark': { - width: '339px', + width: '100%', height: 70, fontColor: palette.DARK_WHITE, font: 'Body_18', diff --git a/src/components/common/Buttons/IconButton/InterestButton.tsx b/src/components/common/Buttons/IconButton/InterestButton.tsx index bb4c4a5a..44040444 100644 --- a/src/components/common/Buttons/IconButton/InterestButton.tsx +++ b/src/components/common/Buttons/IconButton/InterestButton.tsx @@ -13,6 +13,12 @@ type InterestButtonProps = { isDarkMode: boolean } +/** + * @param nickName: 닉네임, string + * @param interests: 관심사 리스트 (최대 3개), string[] + * @param isDarkMode: 다크모드 여부, boolean + */ + const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps) => { const setButtonType = isDarkMode ? 'interest-dark' : 'interest' @@ -32,7 +38,6 @@ const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps display: 'flex', justifyContent: 'center', alignItems: 'center', - margin: '18px 33px 18px 14px', }} > {interest} {index !== interests.length - 1 && ( - + )} ))} diff --git a/src/components/common/Buttons/IconButton/index.tsx b/src/components/common/Buttons/IconButton/index.tsx index a27158be..6e4332d6 100644 --- a/src/components/common/Buttons/IconButton/index.tsx +++ b/src/components/common/Buttons/IconButton/index.tsx @@ -53,9 +53,10 @@ export const StyledIconWrapper = styled.div<{ display: flex; justify-content: center; align-items: center; + margin: 18px 33px 18px 14px; @media (max-width: 280px) { - margin: 2px 10px 2px 10px; + margin: 18px 14px 18px 14px; } ` diff --git a/src/components/common/Divider/index.tsx b/src/components/common/Divider/index.tsx index af4efe1d..ebe61325 100644 --- a/src/components/common/Divider/index.tsx +++ b/src/components/common/Divider/index.tsx @@ -3,15 +3,22 @@ import styled from '@emotion/styled' import { palette } from '@/styles/palette' type DividerProps = { - width: number - height: number + width: string + height: string margin?: string isDarkMode: boolean } +/** + * @param width - 너비 (string) + * @param height - 높이 (string) + * @param margin - (Optional) 마진 + * @param isDarkMode - 다크모드 여부 + */ + export const Divider = styled.div` - width: ${({ width }) => width}px; - height: ${({ height }) => height}px; + width: ${({ width }) => width}; + height: ${({ height }) => height}; margin: ${({ margin }) => margin}; background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY100)}; ` diff --git a/src/components/common/GradationBackground/index.tsx b/src/components/common/GradationBackground/index.tsx index 2eb2968f..df42f66c 100644 --- a/src/components/common/GradationBackground/index.tsx +++ b/src/components/common/GradationBackground/index.tsx @@ -19,7 +19,7 @@ const StyledGradationBackground = styled.div<{ isDarkMode: boolean }>` width: 100%; - height: 100vh; + height: 100%; background: ${({ isDarkMode }) => isDarkMode ? `linear-gradient(157deg, ${palette.BLACK} 16.84%, ${palette.DARK_GRADIENT} 40%)` diff --git a/src/components/common/ListRow/ProfileListRow.tsx b/src/components/common/ListRow/ProfileListRow.tsx index d687e016..55e8cbbf 100644 --- a/src/components/common/ListRow/ProfileListRow.tsx +++ b/src/components/common/ListRow/ProfileListRow.tsx @@ -5,48 +5,34 @@ import { FlexBox } from '@/components/common/Flexbox' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -export const StyleList = styled(FlexBox)<{ - width: number - height: number -}>` - width: ${({ width }) => width}px; - height: ${({ height }) => height}px; - display: flex; - justify-content: space-between; -` - -const StyleIconWrapper = styled.div<{ - width: number - height: number - borderRadius?: string - backgroundColor: string -}>` - width: ${({ width }) => width}px; - height: ${({ height }) => height}px; - display: flex; - justify-content: center; - align-items: center; - border-radius: ${({ borderRadius }) => borderRadius}; - background-color: ${({ backgroundColor }) => backgroundColor}; -` - type ProfileListRowProps = { firstIcon: ReactNode title: string additionalContent?: ReactNode | string isDarkMode?: boolean + moveFromProfileListRow?: () => void } + +/** + * @param firstIcon - 첫번째 아이콘 (ReactNode) + * @param title - 제목 (string) + * @param additionalContent - 추가적인 내용 (string | ReactNode) + * @param isDarkMode - 다크모드 여부 + * @param moveFromProfileListRow - 클릭 시 이동할 경로 + */ + const ProfileListRow = ({ firstIcon, title, additionalContent, isDarkMode, + moveFromProfileListRow, }: ProfileListRowProps) => { const isAdditionalContentString = typeof additionalContent === 'string' const additionalContentColor = isAdditionalContentString ? palette.GRAY300 : undefined return ( - + ` + width: ${({ width }) => (width ? `${width}px` : '100%')}; + height: ${({ height }) => (height ? `${height}px` : '')}; + display: flex; + justify-content: space-between; + cursor: pointer; +` + +const StyleIconWrapper = styled.div<{ + width: number + height: number + borderRadius?: string + backgroundColor: string +}>` + width: ${({ width }) => width}px; + height: ${({ height }) => height}px; + display: flex; + justify-content: center; + align-items: center; + border-radius: ${({ borderRadius }) => borderRadius}; + background-color: ${({ backgroundColor }) => backgroundColor}; +` + export default ProfileListRow diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx index 59cf7b8a..2a56cd74 100644 --- a/src/components/common/Modal/index.tsx +++ b/src/components/common/Modal/index.tsx @@ -1,36 +1,122 @@ import styled from '@emotion/styled' +import { motion } from 'framer-motion' +import { AnimatePresence } from 'framer-motion' import ExclamationIcon from '@/assets/icons/Exclamation.svg' import WarningIcon from '@/assets/icons/Warning.svg' import NormalButton from '@/components/common/Buttons/NormalButton' +import { Text } from '@/components/common/Text' import useModalStore from '@/store/ModalStore' import { palette } from '@/styles/palette' -import { typo } from '@/styles/typo' + +const wrapperVariants = { + hidden: { opacity: 0 }, + visible: { opacity: 1 }, + exit: { opacity: 0 }, +} + +const modalVariants = { + hidden: { + scale: 0.8, + opacity: 0, + transition: { + duration: 0.5, + type: 'spring', + damping: 25, + stiffness: 500, + }, + }, + visible: { + scale: 1, + opacity: 1, + transition: { + duration: 0.5, + type: 'spring', + damping: 25, + stiffness: 500, + }, + }, + exit: { + scale: 0.8, + opacity: 0, + transition: { + duration: 0.5, + type: 'spring', + damping: 25, + stiffness: 500, + }, + }, +} const Modal = () => { - const { modalState, setModalState, okFunc, mainText, subText, type } = useModalStore() + const { + modalState, + setModalState, + okFunc, + mainText, + subText, + type, + acceptText, + cancelText, + isDarkMode, + } = useModalStore() const OkAndClose = () => { okFunc() - closeModal() + handleCloseModal() } - const closeModal = () => { + const handleCloseModal = () => { setModalState(false) } + + const handleModalClick = (event: React.MouseEvent) => { + event.stopPropagation() + } + return ( - <> + {modalState ? ( - - + + {type == 'confirm' ? ( - + ) : ( - + )} - {mainText} - {subText} + + {mainText} + + + {subText} + {type === 'confirm' ? ( - + { {'취소'} - + ) : ( - - + - {'예, 나가겠습니다.'} - - + - {'아니오, 돌아가겠습니다.'} - - + {cancelText ? cancelText : '아니오, 돌아가겠습니다.'} + + )} - - + + ) : ( '' )} - + ) } -const StyleModalWrapper = styled.div` +const StyledModalWrapper = styled(motion.div)` z-index: 999; display: flex; position: absolute; justify-content: center; align-items: center; - width: 100%; - background-color: rgba(0, 0, 0, 0.4); - border-radius: 10px; + max-width: 414px; + background-color: rgba(0, 0, 0, 0.5); top: 0; left: 0; right: 0; bottom: 0; ` -const StyleModal = styled.div<{ type: string }>` - width: 344px; +const StyledModal = styled(motion.div)<{ type: string; isDarkMode?: boolean }>` + max-width: 344px; height: ${({ type }) => (type == 'warn' ? '195.6px' : '246px')}; z-index: 1; position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: white; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; border-radius: 10px; - box-shadow: 3px 3px 3px ${palette.GRAY400}; text-align: center; + + @media (max-width: 280px) { + max-width: 260px; + } +` + +const StyledIcon = styled.img` + margin: 22px; ` -const StyleButtonWrapper = styled.span` +const StyledButtonWrapper = styled.span` justify-content: center; margin: 10px; display: flex; ` -const StyleMainText = styled.div<{ subTrue: boolean }>` - color: ${palette.BLACK}; +const StyledMainText = styled(Text)<{ subTrue: boolean; isDarkMode?: boolean }>` + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.BLACK)}; text-align: center; - font-size: ${typo.Body_20()}; margin-top: ${({ subTrue }) => (subTrue ? '' : '10px')}; margin-bottom: ${({ subTrue }) => (subTrue ? '20px' : '30px')}; ` -const StyleSubText = styled.span<{ type: string }>` - color: ${palette.GRAY500}; +const StyledSubText = styled(Text)<{ type: string; isDarkMode?: boolean }>` + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.GRAY500)}; text-align: center; - font-size: ${typo.Body_14()}; + + @media (max-width: 280px) { + font-size: 10.5px; + } ` -const StyleIcon = styled.img` - margin: 22px; + +const StyledWarningAcceptButton = styled(NormalButton)` + @media (max-width: 280px) { + font-size: 10px; + } ` + export default Modal diff --git a/src/components/common/NavigationBar/index.tsx b/src/components/common/NavigationBar/index.tsx index 1d94eac5..962e8fb2 100644 --- a/src/components/common/NavigationBar/index.tsx +++ b/src/components/common/NavigationBar/index.tsx @@ -6,8 +6,8 @@ import { useNavigate } from 'react-router-dom' import defaultProfileImage from '@/assets/images/defaultProfileImage.png' import Avatar from '@/components/common/Avatar' import { FlexBox } from '@/components/common/Flexbox' +import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -import { typo } from '@/styles/typo' type NavigationBarProps = { isDarkMode: boolean @@ -29,7 +29,14 @@ const NavigationBar = ({ isDarkMode }: NavigationBarProps) => { color: isDarkMode ? `${palette.DARK_WHITE}` : `${palette.DARK_BLUE}`, }} /> - {'이전대화방'} + + {'이전대화방'} + moveFromNavigationBar('')}> @@ -40,7 +47,14 @@ const NavigationBar = ({ isDarkMode }: NavigationBarProps) => { color: isDarkMode ? `${palette.DARK_WHITE}` : `${palette.DARK_BLUE}`, }} /> - {'홈'} + + {'홈'} + moveFromNavigationBar('profile')}> @@ -48,7 +62,14 @@ const NavigationBar = ({ isDarkMode }: NavigationBarProps) => { - {'프로필'} + + {'프로필'} + @@ -59,11 +80,10 @@ const StyledWrapper = styled(FlexBox)` position: sticky; bottom: 0px; ` -const StyledNavigationText = styled.span<{ +const StyledNavigationText = styled(Text)<{ isDarkMode: boolean }>` color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.DARK_BLUE)}; - font-size: ${typo.Body_10()}; ` const StyledNavigation = styled(FlexBox)<{ isDarkMode: boolean diff --git a/src/components/common/PageHeader/index.tsx b/src/components/common/PageHeader/index.tsx index ca5cd0d1..de25cfb5 100644 --- a/src/components/common/PageHeader/index.tsx +++ b/src/components/common/PageHeader/index.tsx @@ -3,46 +3,6 @@ import styled from '@emotion/styled' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -const StyledPageHeader = styled.div<{ - isDarkMode?: boolean - hasBackground?: boolean -}>` - max-width: 414px; - width: 100%; - height: 72px; - padding: 0 18px; - display: flex; - justify-content: space-between; - align-items: center; - border-radius: 20px 20px 0 0; - border-bottom: 1px solid - ${({ isDarkMode, hasBackground }) => - isDarkMode - ? hasBackground - ? palette.GRAY500 - : 'none' - : hasBackground - ? palette.GRAY300 - : 'none'}; - background-color: ${({ isDarkMode, hasBackground }) => - isDarkMode - ? hasBackground - ? `${palette.DARK_BLUE}` - : 'transparent' - : hasBackground - ? `${palette.GRAY100}` - : 'transparent'}; -` - -const StyledIcon = styled.div` - width: 38px; - height: 38px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; -` - type PageHeaderProps = { title: string leftIcon?: React.ReactNode @@ -106,4 +66,43 @@ const PageHeader = ({ ) } +const StyledPageHeader = styled.div<{ + isDarkMode?: boolean + hasBackground?: boolean +}>` + max-width: 414px; + width: 100%; + height: 72px; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 20px 20px 0 0; + border-bottom: 1px solid + ${({ isDarkMode, hasBackground }) => + isDarkMode + ? hasBackground + ? palette.GRAY500 + : 'none' + : hasBackground + ? palette.GRAY300 + : 'none'}; + background-color: ${({ isDarkMode, hasBackground }) => + isDarkMode + ? hasBackground + ? `${palette.DARK_BLUE}` + : 'transparent' + : hasBackground + ? `${palette.GRAY100}` + : 'transparent'}; +` + +const StyledIcon = styled.div` + width: 38px; + height: 38px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +` + export default PageHeader diff --git a/src/components/common/Text/index.tsx b/src/components/common/Text/index.tsx index 10aa093c..7c438b65 100644 --- a/src/components/common/Text/index.tsx +++ b/src/components/common/Text/index.tsx @@ -1,14 +1,25 @@ import { css } from '@emotion/react' import styled from '@emotion/styled' +import { palette } from '@/styles/palette' import { KeyOfTypo } from '@/styles/theme' import { typo } from '@/styles/typo' -export const TextWrapper = styled.div` +/** + * @param display: (기본값: flex) + * @param flexDirection: (기본값: column) + * @param justifyContent: (기본값: center) + * @param alignItems: (기본값: flex-start) + */ + +export const TextWrapper = styled.div<{ + isDarkMode?: boolean +}>` display: flex; flex-direction: column; justify-content: center; align-items: flex-start; + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.WHITE)}; ` export const Text = styled.div<{ diff --git a/src/components/home/Card.tsx b/src/components/home/Card.tsx index 1087ed60..40d3e007 100644 --- a/src/components/home/Card.tsx +++ b/src/components/home/Card.tsx @@ -1,84 +1,78 @@ import styled from '@emotion/styled' -import { timer } from 'd3' import { AnimatePresence, motion } from 'framer-motion' -import { useEffect, useRef, useState } from 'react' -import { PulseLoader } from 'react-spinners' +import { useEffect } from 'react' import { RandomMatchingButton } from '@/components/common/Buttons/IconButton' -import NormalButton from '@/components/common/Buttons/NormalButton' import Spacing from '@/components/common/Spacing' -import { Text } from '@/components/common/Text' +import useTimerStore from '@/store/TimerStore' import { palette } from '@/styles/palette' -import Tip from './Tip' +import CardBottom from './CardBottom' +import CardHeader from './CardHeader' +import CardMiddle from './CardMiddle' -type TimerRefType = ReturnType | null +const StyledCard = styled(motion.div)<{ + isDarkMode: boolean +}>` + width: 100%; + height: 348px; + border-radius: 20px; + box-shadow: ${palette.SHADOW}; + display: flex; + flex-direction: column; + margin: 0 auto; + justify-content: center; + align-items: center; + padding: 19px 7px 15px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; +` + +const StyledWatingWrapper = styled(motion.div)` + width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; +` + +const watingCounter = { + hidden: { opacity: 0 }, + visible: { opacity: 1, transition: { type: 'spring', damping: 12, duration: 0.5 } }, + exit: { opacity: 0, transition: { duration: 1 } }, +} type CardProps = { - isMatching: boolean isDarkMode: boolean - onClick: () => void } /** - * @param isMatching - 현재 매칭 여부 * @param isDarkMode - 다크모드 여부 - * @param onClick - 매칭 버튼 클릭 이벤트 */ -const Card = ({ isMatching, isDarkMode, onClick }: CardProps) => { - const [time, setTime] = useState(0) - const timerRef = useRef(null) +const Card = ({ isDarkMode }: CardProps) => { + const { time, isRunning, startTimer, resetTimer } = useTimerStore() - const handleCancelClick = () => { - setTime(0) - if (timerRef.current) { - timerRef.current.stop() + window.onload = () => { + const navigationType = ( + performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming + ).type + if (navigationType !== 'reload') { + resetTimer() } - onClick() - } - - const formatTime = (time: number) => { - const minutes = Math.floor(time / 60000) - .toString() - .padStart(2, '0') - const seconds = Math.floor((time % 60000) / 1000) - .toString() - .padStart(2, '0') - return `${minutes}:${seconds}` - } - - const watingCounter = { - hidden: { opacity: 0 }, - visible: { opacity: 1, transition: { type: 'spring', damping: 12, duration: 0.5 } }, - exit: { opacity: 0, transition: { duration: 1 } }, } useEffect(() => { - if (isMatching) { - const startTime = Date.now() - const updateTimer = () => { - const elapsedTime = Date.now() - startTime - setTime(elapsedTime) - } - timerRef.current = timer(updateTimer, 1000) - } else { - if (timerRef.current) { - timerRef.current.stop() - } + if (isRunning) { + startTimer() + } else if (!isRunning) { + resetTimer() } - - return () => { - if (timerRef.current) { - timerRef.current.stop() - } - } - }, [isMatching]) + }, [isRunning, startTimer, resetTimer]) return ( - - {!isMatching ? ( + + {!isRunning ? ( { exit={'exit'} variants={watingCounter} > - + ) : ( - - - - - {'3'} - - - {'/5'} - - - + - - - {formatTime(time)} - - - - {'매칭 취소'} - - - - {'매칭 중'}    - - - - - - - - + + + + )} - + ) } -const StyleCard = styled(motion.div)<{ - isDarkMode: boolean -}>` - width: 100%; - height: 348px; - border-radius: 20px; - background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; - box-shadow: ${palette.SHADOW}; - display: flex; - flex-direction: column; - margin: 0 auto; - justify-content: center; - align-items: center; - padding: 5% 1% 5%; -` - -const StyleWatingWrapper = styled(motion.div)` - width: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; - flex: 1; -` - -const StyleWatingTopWrapper = styled.div` - width: 100%; - height: 38px; - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 5%; - position: relative; -` - -const StyleWatingTopTextWrapper = styled.div` - display: flex; - height: inherit; - justify-content: center; - align-items: flex-end; -` - -const StyleWatingMidWrapper = styled.div` - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -` - -const StyleWatingBottomWrapper = styled.div`` - export default Card diff --git a/src/hooks/useModal.tsx b/src/hooks/useModal.tsx index de18310e..e8d921b2 100644 --- a/src/hooks/useModal.tsx +++ b/src/hooks/useModal.tsx @@ -6,16 +6,39 @@ type ModalConfirmPropsType = { okFunc: () => void mainText: string subText?: string + acceptText?: string + cancelText?: string + isDarkMode?: boolean } export const useModal = () => { - const { setModalState, setOkFunc, setMainText, setSubText, setType } = useModalStore() + const { + setModalState, + setOkFunc, + setMainText, + setSubText, + setType, + setAcceptText, + setCancelText, + setDarkMode, + } = useModalStore() - const openModal = ({ mainText, subText, okFunc, type }: ModalConfirmPropsType) => { + const openModal = ({ + mainText, + subText, + okFunc, + type, + acceptText, + cancelText, + isDarkMode, + }: ModalConfirmPropsType) => { setModalState(true) setType(type) setMainText(mainText) setSubText(subText) setOkFunc(okFunc) + setAcceptText(acceptText) + setCancelText(cancelText) + setDarkMode(isDarkMode) } return { openModal, Modal } diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 60ebacce..9bea8f50 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,6 +1,13 @@ import { http, HttpResponse } from 'msw' const nickname = '주다다' export const handlers = [ + + http.get(`/v1/users/duplicate?nickname=${nickname}`, async () => { + return HttpResponse.json({ + duplicate: true, + }) + }), + http.get('/v1/histories', () => { return HttpResponse.json([ { diff --git a/src/pages/chatList/ChatList.tsx b/src/pages/chatList/ChatList.tsx index 595943d7..015f1f86 100644 --- a/src/pages/chatList/ChatList.tsx +++ b/src/pages/chatList/ChatList.tsx @@ -1,3 +1,4 @@ +import styled from '@emotion/styled' import { useQuery } from '@tanstack/react-query' import { motion } from 'framer-motion' import { useNavigate } from 'react-router-dom' @@ -33,7 +34,7 @@ const ChatList = () => { - { } isDarkMode={isDarkMode} hasBackground={true} - style={{ - position: 'fixed', - zIndex: 1, - }} /> {isSuccess && ( @@ -62,4 +59,8 @@ const ChatList = () => { ) } +const StyledPageHeader = styled(PageHeader)` + padding: 0 18px; +` + export default ChatList diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 97813355..7bd7ee97 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -1,5 +1,3 @@ -import { useState } from 'react' - import AppHeader from '@/components/common/AppHeader' import { ParticularTopicButton } from '@/components/common/Buttons/IconButton' import GradationBackground from '@/components/common/GradationBackground' @@ -15,7 +13,6 @@ const Home = () => { const nickname = '우땅' const isDarkMode = useThemeStore((state) => state.isDarkMode) const toggleDarkMode = useThemeStore((state) => state.toggleDarkMode) - const [isMatching, setIsMatching] = useState(false) const { showToast } = useToast() @@ -35,25 +32,19 @@ const Home = () => { letterSpacing={-0.5} style={{ margin: '33px 0 22px 0', - color: isDarkMode ? `${palette.DARK_WHITE}` : `${palette.BLACK}`, + color: isDarkMode ? `${palette.DARK_WHITE}` : `${palette.DARK_BLUE}`, }} > {'진행중인 매칭'} - { - setIsMatching((prev) => !prev) - }} - isDarkMode={isDarkMode} - /> + {'커피밋의 추천기능'} diff --git a/src/pages/notFound/NotFound.tsx b/src/pages/notFound/NotFound.tsx index 46518bd1..4cadb72f 100644 --- a/src/pages/notFound/NotFound.tsx +++ b/src/pages/notFound/NotFound.tsx @@ -1,15 +1,17 @@ import styled from '@emotion/styled' +import { useNavigate } from 'react-router-dom' import NotFoundIcon from '@/assets/icons/NotFoundIcon' import NormalButton from '@/components/common/Buttons/NormalButton' import GradationBackground from '@/components/common/GradationBackground' import Spacing from '@/components/common/Spacing' import { Text } from '@/components/common/Text' +import useThemeStore from '@/store/ThemeStore' import { palette } from '@/styles/palette' const NotFoundPage = () => { - // TODO: zustand로 darkMode 상태 받기 - const isDarkMode = true + const isDarkMode = useThemeStore((state) => state.isDarkMode) + const navigate = useNavigate() return ( @@ -40,7 +42,14 @@ const NotFoundPage = () => { - {'홈으로 돌아가기'} + { + navigate('/') + }} + > + {'홈으로 돌아가기'} + diff --git a/src/pages/profile/ProfileDefault.tsx b/src/pages/profile/ProfileDefault.tsx index df7e8164..365946ec 100644 --- a/src/pages/profile/ProfileDefault.tsx +++ b/src/pages/profile/ProfileDefault.tsx @@ -1,19 +1,274 @@ +import styled from '@emotion/styled' +import { AiOutlineInfoCircle } from 'react-icons/ai' +import { BiBuildings, BiChevronRight, BiPencil } from 'react-icons/bi' +import { MdOutlineRecordVoiceOver } from 'react-icons/md' +import { PiIdentificationCardBold } from 'react-icons/pi' import { useNavigate } from 'react-router-dom' +import NaverIcon from '@/assets/icons/NaverIcon' +import Avatar from '@/components/common/Avatar' +import BackChevron from '@/components/common/BackChevron' +import { InterestButton } from '@/components/common/Buttons/IconButton' +import { Divider } from '@/components/common/Divider' +import { FlexBox } from '@/components/common/Flexbox' +import GradationBackground from '@/components/common/GradationBackground' +import ProfileListRow from '@/components/common/ListRow/ProfileListRow' +import NavigationBar from '@/components/common/NavigationBar' +import PageContainer from '@/components/common/PageContainer' +import PageHeader from '@/components/common/PageHeader' +import Spacing from '@/components/common/Spacing' +import { Text, TextWrapper } from '@/components/common/Text' +import { useModal } from '@/hooks/useModal' +import useToast from '@/hooks/useToast' +import useThemeStore from '@/store/ThemeStore' +import { palette } from '@/styles/palette' + const ProfileDefault = () => { const navigate = useNavigate() + const isDarkMode = useThemeStore((store) => store.isDarkMode) + const { openModal } = useModal() + + const { showToast } = useToast() + + const handleLogout = () => { + openModal({ + mainText: '로그아웃 하시겠습니까?', + subText: '로그아웃 시 로그인 화면으로 이동합니다.', + okFunc: () => { + navigate('/login') + }, + type: 'warn', + acceptText: '네, 로그아웃 하겠습니다.', + cancelText: '아니오. 취소하겠습니다.', + isDarkMode, + }) + } + + const handleDeleteAccount = () => { + openModal({ + mainText: '계정을 삭제하시겠습니까?', + subText: '계정을 삭제하시면 모든 정보가 삭제되며 복구할 수 없습니다.', + okFunc: () => { + navigate('/login') + }, + type: 'warn', + acceptText: '네, 삭제하겠습니다.', + cancelText: '아니오. 취소하겠습니다.', + isDarkMode, + }) + } return ( -
- -
+ + + {'개인 및 어플리케이션 정보'} + + + + } + title={'회사 정보 변경'} + isDarkMode={isDarkMode} + additionalContent={} + moveFromProfileListRow={() => { + navigate('/profile/privacy') + }} + /> + + } + title={'불편사항 접수'} + isDarkMode={isDarkMode} + additionalContent={} + moveFromProfileListRow={() => { + showToast({ + message: '아직 준비중인 기능입니다!', + type: 'info', + isDarkMode, + }) + }} + /> + + } + title={'소셜 로그인 계정'} + isDarkMode={isDarkMode} + additionalContent={ + + } + /> + + } + title={'커피밋'} + isDarkMode={isDarkMode} + additionalContent={'v 0.1'} + /> + + + + {'⚠️ 주의'} + + + + + {'로그아웃'} + + + + {'계정 삭제'} + + +
+ +
) } +const StyledProfilePageHeader = styled.div` + margin: 33px 26.5px 0; +` + +const StyledProfilePrimaryInfo = styled.div<{ + isDarkMode: boolean +}>` + display: flex; + height: 100%; + justify-content: space-between; + align-items: center; + flex: 1; + color: ${({ isDarkMode }) => (isDarkMode ? palette.DARK_WHITE : palette.WHITE)}; +` + +const StyledProfilePrimaryInfoTextWrapper = styled.div` + margin-left: 23px; + height: 49px; + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-around; + + @media (max-width: 280px) { + margin-left: 10px; + } +` + +const StyledProfilePageContentCard = styled.div<{ + isDarkMode: boolean + height: number +}>` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border-radius: 20px; + padding: 0 18px; + height: ${({ height }) => height}px; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; + box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.05); +` + +const StyledProfileWarningText = styled(Text)` + width: 100%; + color: ${palette.RED}; + display: flex; + justify-content: center; + align-items: center; + flex: 1; + cursor: pointer; +` + export default ProfileDefault diff --git a/src/pages/profile/ProfileEdit.tsx b/src/pages/profile/ProfileEdit.tsx index 60da403f..fe740a1e 100644 --- a/src/pages/profile/ProfileEdit.tsx +++ b/src/pages/profile/ProfileEdit.tsx @@ -1,22 +1,7 @@ -import { testWithBtn } from '@/apis/test' -import NormalButton from '@/components/common/Buttons/NormalButton' - const ProfileEdit = () => { - const testClick = async () => { - try { - const response = await testWithBtn('길동이') - console.log(response.data) - // 로그인 성공 시 필요한 로직을 추가합니다. - } catch (error) { - console.error('카카오 로그인에 실패했습니다.', error) - } - } return (
- {'ProfileEdit'} - - {'닉네임 중복 테스트'} - +

{'ProfileEdit'}

) } diff --git a/src/store/ModalStore.tsx b/src/store/ModalStore.tsx index c87e8751..d67b129b 100644 --- a/src/store/ModalStore.tsx +++ b/src/store/ModalStore.tsx @@ -11,6 +11,12 @@ type ModalState = { setModalState: (state: boolean) => void setMainText: (text: string) => void setOkFunc: (func: () => void) => void + acceptText?: string + cancelText?: string + isDarkMode?: boolean + setAcceptText: (text: string | undefined) => void + setCancelText: (text: string | undefined) => void + setDarkMode: (isDarkMode: boolean | undefined) => void } const useModalStore = create((set) => ({ @@ -19,10 +25,16 @@ const useModalStore = create((set) => ({ mainText: '', subText: '', type: 'confirm', + acceptText: '네', + cancelText: '아니오', setType: (type) => set({ type: type }), setSubText: (text) => set({ subText: text }), setModalState: (state) => set({ modalState: state }), setMainText: (text) => set({ mainText: text }), setOkFunc: (func) => set({ okFunc: func }), + setAcceptText: (text) => set({ acceptText: text }), + setCancelText: (text) => set({ cancelText: text }), + setDarkMode: (isDarkMode) => set({ isDarkMode }), })) + export default useModalStore