diff --git a/package-lock.json b/package-lock.json index f4daf9e9..60875e21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,9 @@ "react-icons": "^4.11.0", "react-router-dom": "^6.16.0", "react-spinners": "^0.13.8", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "zustand": "^4.4.4", + "zustand-persist": "^0.4.0" }, "devDependencies": { "@rushstack/eslint-config": "^3.4.1", @@ -2518,13 +2520,13 @@ "version": "15.7.9", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.2.30", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.30.tgz", "integrity": "sha512-OfqdJnDsSo4UNw0bqAjFCuBpLYQM7wvZidz0hVxHRjrEkzRlvZL1pJVyOSY55HMiKvRNEo9DUBRuEl7FNlJ/Vg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2544,7 +2546,7 @@ "version": "0.16.5", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==", - "dev": true + "devOptional": true }, "node_modules/@types/semver": { "version": "7.5.4", @@ -9208,6 +9210,42 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.4.tgz", + "integrity": "sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/zustand-persist": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/zustand-persist/-/zustand-persist-0.4.0.tgz", + "integrity": "sha512-u6bBIc4yZRpSKBKuTNhoqvoIb09gGHk2NkiPg4K7MPIWTYZg70PlpBn48QEDnKZwfNurnf58TaW5BuMGIMf5hw==", + "peerDependencies": { + "react": ">=16.8.0", + "zustand": ">=3.6.3" + } } } } diff --git a/package.json b/package.json index 243bac8f..5c48947f 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,10 @@ "react-hook-form": "^7.47.0", "react-icons": "^4.11.0", "react-router-dom": "^6.16.0", - "react-spinners": "^0.13.8", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "zustand": "^4.4.4", + "zustand-persist": "^0.4.0", + "react-spinners": "^0.13.8" }, "devDependencies": { "@rushstack/eslint-config": "^3.4.1", diff --git a/src/assets/icons/Exclamation.svg b/src/assets/icons/Exclamation.svg new file mode 100644 index 00000000..c6348371 --- /dev/null +++ b/src/assets/icons/Exclamation.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/Warning.svg b/src/assets/icons/Warning.svg new file mode 100644 index 00000000..8a1b6177 --- /dev/null +++ b/src/assets/icons/Warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/common/BottomSheet/RandomMatchingSheet.tsx b/src/components/common/BottomSheet/RandomMatchingSheet.tsx index 0664610b..8b6a68be 100644 --- a/src/components/common/BottomSheet/RandomMatchingSheet.tsx +++ b/src/components/common/BottomSheet/RandomMatchingSheet.tsx @@ -3,7 +3,7 @@ import { AnimatePresence, motion } from 'framer-motion' import { MouseEvent, useState } from 'react' import { AiOutlineClose } from 'react-icons/ai' -import RandomMatchingJoinButton from '@/components/common/Buttons/IconButton/RandomMatchingJoin' +import RandomMatchingJoinButton from '@/components/common/Buttons/IconButton/RandomMatchingJoinButton' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' diff --git a/src/components/common/BottomSheet/index.tsx b/src/components/common/BottomSheet/index.tsx deleted file mode 100644 index dc8673ba..00000000 --- a/src/components/common/BottomSheet/index.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import styled from '@emotion/styled' -import { AnimatePresence, motion } from 'framer-motion' -import { MouseEvent, useState } from 'react' -import { AiOutlineClose } from 'react-icons/ai' - -import RandomMatchingJoinButton from '@/components/common/Buttons/IconButton/RandomMatchingJoin' -import { Text } from '@/components/common/Text' -import { palette } from '@/styles/palette' - -import Timer from './Timer' - -const Background = styled.div` - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: flex-end; - overflow-y: hidden; -` - -const BottomContentWrapper = styled(motion.div)<{ - isDarkMode: boolean -}>` - width: 100%; - display: flex; - flex-direction: column; - height: 378px; - border-top-left-radius: 20px; - border-top-right-radius: 20px; - background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; -` - -const BottomContentHeader = styled.div<{ - isDarkMode: boolean -}>` - width: 100%; - position: relative; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY200)}; - background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; - border-top-left-radius: 20px; - border-top-right-radius: 20px; - padding: 24px 0; -` - -const BottomContent = styled.div<{ - isDarkMode: boolean -}>` - display: flex; - flex-direction: column; - align-items: center; - border-top-left-radius: 20px; - border-top-right-radius: 20px; - background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY700 : palette.WHITE)}; -` - -type BottomSheetProps = { - title: string - isDarkMode: boolean -} - -const BottomSheet = ({ title, isDarkMode }: BottomSheetProps) => { - const [isOpen, setIsOpen] = useState(true) // BottomSheet의 상태 - - const handleWrapperClick = (e: MouseEvent) => { - e.stopPropagation() - } - - const toggleBottomSheet = () => { - // isOpen이 true일 때만 상태를 토글 - if (isOpen) { - console.log('매칭 참가 취소') - setIsOpen(!isOpen) - } - } - - const slideUp = { - hidden: { y: '100%', opacity: 0 }, - visible: { y: '0%', opacity: 1, transition: { type: 'spring', damping: 15, stiffness: 100 } }, - partiallyVisible: { - y: '85%', - opacity: 1, - transition: { type: 'spring', damping: 15, stiffness: 100 }, - }, - exit: { y: '100%', opacity: 0, transition: { type: 'spring', damping: 20, stiffness: 100 } }, - } - return ( - - - - - - - {title} - - - - { - console.log('타이머 종료!') - }} - /> - { - console.log('랜덤 매칭 참가') - }} - /> - - {'현재 매칭에 참가하지 않으면 다음 매칭에 불이익이 있습니다.'} - - - - - - ) -} - -export default BottomSheet diff --git a/src/components/common/Buttons/IconButton/InterestButton.tsx b/src/components/common/Buttons/IconButton/InterestButton.tsx index 229c816a..942e060e 100644 --- a/src/components/common/Buttons/IconButton/InterestButton.tsx +++ b/src/components/common/Buttons/IconButton/InterestButton.tsx @@ -5,7 +5,7 @@ import { Divider } from '@/components/common/Divider' import { Text, TextWrapper } from '@/components/common/Text' import { palette } from '@/styles/palette' -import { IconButtonWrapper, IconWrapper } from '.' +import { StyleIconButtonWrapper, StyleIconWrapper } from '.' type InterestButtonProps = { nickName: string @@ -17,7 +17,7 @@ const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps const setButtonType = isDarkMode ? 'interest-dark' : 'interest' return ( - - - + {`${nickName}의 관심사`} @@ -61,12 +61,14 @@ const InterestButton = ({ nickName, interests, isDarkMode }: InterestButtonProps {interests.map((interest, index) => ( {interest} - {index !== interests.length - 1 && } + {index !== interests.length - 1 && ( + + )} ))} - + ) } diff --git a/src/components/common/Buttons/IconButton/KakaoButton.tsx b/src/components/common/Buttons/IconButton/KakaoButton.tsx index 7c5068d4..37b7776c 100644 --- a/src/components/common/Buttons/IconButton/KakaoButton.tsx +++ b/src/components/common/Buttons/IconButton/KakaoButton.tsx @@ -4,7 +4,7 @@ import KakaoIcon from '@/assets/icons/KakaoIcon' import { Text } from '@/components/common/Text' import { palette } from '@/styles/palette' -import { IconWrapper } from '.' +import { StyleIconWrapper } from '.' export const ButtonWrapper = styled.button<{ buttonTheme: 'kakao' | 'naver' @@ -21,13 +21,13 @@ export const ButtonWrapper = styled.button<{ const KakaoButton = () => ( - - + { return ( - - + { const getSecondTextColor = isDarkMode ? palette.GRAY300 : palette.GRAY500 return ( - { alignItems: 'center', }} > - { height: 20, }} /> - + { {'네트워크를 넓혀보세요!'} - { height: 30, }} /> - - + + ) } diff --git a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx index 1fde1eea..f705d881 100644 --- a/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx +++ b/src/components/common/Buttons/IconButton/RandomMatchingButton.tsx @@ -5,7 +5,7 @@ import { Text, TextWrapper } from '@/components/common/Text' import { palette } from '@/styles/palette' import { getTimeDelta } from '@/utils/getTimeStamp' -import { IconButtonWrapper, IconWrapper } from '.' +import { StyleIconButtonWrapper, StyleIconWrapper } from '.' type RandomMatchingButtonProps = { date: string @@ -16,7 +16,7 @@ const RandomMatchingButton = ({ date, isDarkMode }: RandomMatchingButtonProps) = const setButtonType = isDarkMode ? 'random-matching-dark' : 'random-matching' return ( - - - + - - - + + ) } diff --git a/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx index 58afee29..e69de29b 100644 --- a/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx +++ b/src/components/common/Buttons/IconButton/RandomMatchingJoin.tsx @@ -1,61 +0,0 @@ -import { BiChevronRight } from 'react-icons/bi' - -import { Text, TextWrapper } from '@/components/common/Text' - -import { IconButtonWrapper, IconWrapper } from '.' - -type RandomMatchingJoinButtonProps = { - isDarkMode: boolean - moveToRandomMatching: () => void -} - -const RandomMatchingJoinButton = ({ - isDarkMode, - moveToRandomMatching, -}: RandomMatchingJoinButtonProps) => { - const setButtonType = isDarkMode ? 'random-matching-join-dark' : 'random-matching-join' - - return ( - - - - {'매칭방에 접속해주세요!'} - - - - - - - ) -} - -export default RandomMatchingJoinButton diff --git a/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx b/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx new file mode 100644 index 00000000..12174a8b --- /dev/null +++ b/src/components/common/Buttons/IconButton/RandomMatchingJoinButton.tsx @@ -0,0 +1,61 @@ +import { BiChevronRight } from 'react-icons/bi' + +import { Text, TextWrapper } from '@/components/common/Text' + +import { StyleIconButtonWrapper, StyleIconWrapper } from '.' + +type RandomMatchingJoinButtonProps = { + isDarkMode: boolean + moveToRandomMatching: () => void +} + +const RandomMatchingJoinButton = ({ + isDarkMode, + moveToRandomMatching, +}: RandomMatchingJoinButtonProps) => { + const setButtonType = isDarkMode ? 'random-matching-join-dark' : 'random-matching-join' + + return ( + + + + {'매칭방에 접속해주세요!'} + + + + + + + ) +} + +export default RandomMatchingJoinButton diff --git a/src/components/common/Buttons/IconButton/index.tsx b/src/components/common/Buttons/IconButton/index.tsx index 07a56af8..95519bd2 100644 --- a/src/components/common/Buttons/IconButton/index.tsx +++ b/src/components/common/Buttons/IconButton/index.tsx @@ -10,7 +10,7 @@ import NaverButton from './NaverButton' import ParticularTopicButton from './ParticularTopicButton' import RandomMatchingButton from './RandomMatchingButton' -export const IconButtonWrapper = styled.button<{ +export const StyleIconButtonWrapper = styled.button<{ iconButtonType: IconButtonType }>` ${({ iconButtonType }) => { @@ -30,7 +30,7 @@ export const IconButtonWrapper = styled.button<{ }} ` -export const IconWrapper = styled.div<{ +export const StyleIconWrapper = styled.div<{ borderRadius?: string backgroundColor?: string }>` diff --git a/src/components/common/Buttons/NormalButton/NormalButton.tsx b/src/components/common/Buttons/NormalButton/NormalButton.tsx index af501a38..db596958 100644 --- a/src/components/common/Buttons/NormalButton/NormalButton.tsx +++ b/src/components/common/Buttons/NormalButton/NormalButton.tsx @@ -7,20 +7,28 @@ import { NormalButtonStyles, NormalButtonType } from './NormalButtonStyles' const NormalButton = styled.button<{ normalButtonType: NormalButtonType + isDarkMode?: boolean }>` - ${({ normalButtonType }) => { - const fontFunc = typo[NormalButtonStyles[normalButtonType].font] + ${({ normalButtonType, isDarkMode = false }) => { + const processedTypeKey = isDarkMode ? `${normalButtonType}-dark` : normalButtonType + const processedType = ( + NormalButtonStyles[processedTypeKey as NormalButtonType] ? processedTypeKey : normalButtonType + ) as NormalButtonType + + console.log(processedType) + + const fontFunc = typo[NormalButtonStyles[processedType].font] return css` ${fontFunc( - NormalButtonStyles[normalButtonType].fontWeight, - NormalButtonStyles[normalButtonType].letterSpacing, + NormalButtonStyles[processedType].fontWeight, + NormalButtonStyles[processedType].letterSpacing, )} - width: ${NormalButtonStyles[normalButtonType].width}px; - height: ${NormalButtonStyles[normalButtonType].height}px; - color: ${NormalButtonStyles[normalButtonType].fontColor}; - background-color: ${NormalButtonStyles[normalButtonType].backgroundColor}; - box-shadow: ${NormalButtonStyles[normalButtonType].boxShadow}; - border-radius: ${NormalButtonStyles[normalButtonType].borderRadius}px; + width: ${NormalButtonStyles[processedType].width}px; + height: ${NormalButtonStyles[processedType].height}px; + color: ${NormalButtonStyles[processedType].fontColor}; + background-color: ${NormalButtonStyles[processedType].backgroundColor}; + box-shadow: ${NormalButtonStyles[processedType].boxShadow}; + border-radius: ${NormalButtonStyles[processedType].borderRadius}px; ` }} ` diff --git a/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts index fb028b1d..2f97c3b7 100644 --- a/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts +++ b/src/components/common/Buttons/NormalButton/NormalButtonStyles.ts @@ -144,7 +144,7 @@ export const NormalButtonStyles: Record = { width: 85, height: 40, fontColor: palette.GRAY400, - backgroundColor: palette.GRAY100, + backgroundColor: palette.GRAY200, font: 'Body_14', fontWeight: 600, letterSpacing: -2, diff --git a/src/components/common/Divider/index.tsx b/src/components/common/Divider/index.tsx index 6553babe..af4efe1d 100644 --- a/src/components/common/Divider/index.tsx +++ b/src/components/common/Divider/index.tsx @@ -2,9 +2,16 @@ import styled from '@emotion/styled' import { palette } from '@/styles/palette' -export const Divider = styled.div` - width: 1px; - height: 10px; - margin: 0 12px; - background-color: ${palette.WHITE}; +type DividerProps = { + width: number + height: number + margin?: string + isDarkMode: boolean +} + +export const Divider = styled.div` + width: ${({ width }) => width}px; + height: ${({ height }) => height}px; + margin: ${({ margin }) => margin}; + background-color: ${({ isDarkMode }) => (isDarkMode ? palette.GRAY500 : palette.GRAY100)}; ` diff --git a/src/components/common/ListRow/AdminListRow.tsx b/src/components/common/ListRow/AdminListRow.tsx new file mode 100644 index 00000000..7976bf03 --- /dev/null +++ b/src/components/common/ListRow/AdminListRow.tsx @@ -0,0 +1,90 @@ +import { FlexBox } from '@/components/common/Flexbox' +import { StyleList } from '@/components/common/ListRow/ProfileListRow' +import { Text } from '@/components/common/Text' +import { palette } from '@/styles/palette' + +type ProfileListRowProps = { + height: number + nickname: string + infoMessage: string | number + isDarkMode: boolean +} +const AdminListRow = ({ height, nickname, infoMessage, isDarkMode }: ProfileListRowProps) => { + const renderInfoMessage = () => { + if (typeof infoMessage === 'number') { + return ( + + + {'누적 '} + + + {infoMessage} + + + {' 회'} + + + ) + } + return ( + + {infoMessage} + + ) + } + + return ( + + + {nickname} + + {renderInfoMessage()} + + ) +} + +export default AdminListRow diff --git a/src/components/common/ListRow/ProfileListRow.tsx b/src/components/common/ListRow/ProfileListRow.tsx new file mode 100644 index 00000000..d687e016 --- /dev/null +++ b/src/components/common/ListRow/ProfileListRow.tsx @@ -0,0 +1,94 @@ +import styled from '@emotion/styled' +import { ReactNode } from 'react' + +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 +} +const ProfileListRow = ({ + firstIcon, + title, + additionalContent, + isDarkMode, +}: ProfileListRowProps) => { + const isAdditionalContentString = typeof additionalContent === 'string' + const additionalContentColor = isAdditionalContentString ? palette.GRAY300 : undefined + + return ( + + + {firstIcon} + + + {title} + + + {additionalContent} + + + ) +} + +export default ProfileListRow diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx new file mode 100644 index 00000000..75b43e9a --- /dev/null +++ b/src/components/common/Modal/index.tsx @@ -0,0 +1,124 @@ +import styled from '@emotion/styled' + +import ExclamationIcon from '@/assets/icons/Exclamation.svg' +import WarningIcon from '@/assets/icons/Warning.svg' +import NormalButton from '@/components/common/Buttons/NormalButton/NormalButton' +import useModalStore from '@/store/ModalStore' +import { palette } from '@/styles/palette' +import { typo } from '@/styles/typo' + +const Modal = () => { + const { modalState, setModalState, okFunc, mainText, subText, type } = useModalStore() + const OkAndClose = () => { + okFunc() + closeModal() + } + const closeModal = () => { + setModalState(false) + } + return ( + <> + {modalState ? ( + + + {type == 'confirm' ? ( + + ) : ( + + )} + + {mainText} + {subText} + {type === 'confirm' ? ( + + + {'확인'} + + + {'취소'} + + + ) : ( + + + {'예, 나가겠습니다.'} + + + {'아니오, 돌아가겠습니다.'} + + + )} + + + ) : ( + '' + )} + + ) +} + +const StyleModalWrapper = styled.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; + top: 0; + left: 0; + right: 0; + bottom: 0; +` +const StyleModal = styled.div<{ type: string }>` + 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; + border-radius: 10px; + box-shadow: 3px 3px 3px ${palette.GRAY400}; + text-align: center; +` + +const StyleButtonWrapper = styled.span` + justify-content: center; + margin: 10px; + display: flex; +` +const StyleMainText = styled.div<{ subTrue: boolean }>` + color: ${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}; + text-align: center; + font-size: ${typo.Body_14()}; +` +const StyleIcon = styled.img` + margin: 22px; +` +export default Modal diff --git a/src/components/common/SelectorButton/index.tsx b/src/components/common/SelectorButton/index.tsx index 6eb7d94f..9d88f9af 100644 --- a/src/components/common/SelectorButton/index.tsx +++ b/src/components/common/SelectorButton/index.tsx @@ -4,36 +4,43 @@ import { useState } from 'react' import { palette } from '@/styles/palette' type SelectorButtonProps = { + isDarkMode: boolean buttonName: string - selectedButtonColor: string - defaultButtonColor: string - textColor: string onClick?: (selected: boolean) => void - selected?: boolean -} - -type StyledButtonProps = { - backgroundColor: string - textColor: string + isButtonselected?: boolean } const SelectorButton = ({ + isDarkMode, buttonName, - selectedButtonColor, - defaultButtonColor = palette.TERTIARY, - textColor = palette.SECONDARY, onClick, - selected = false, + isButtonselected = false, }: SelectorButtonProps) => { - const initialBackgroundColor = selected ? selectedButtonColor : defaultButtonColor + const defaultSettings = isDarkMode + ? { + selectedButtonColor: palette.SECONDARY, + defaultButtonColor: palette.WHITE, + textColor: palette.SECONDARY, + } + : { + selectedButtonColor: palette.BLUE, + defaultButtonColor: palette.TERTIARY, + textColor: palette.WHITE, + } + + const initialBackgroundColor = isButtonselected + ? defaultSettings.selectedButtonColor + : defaultSettings.defaultButtonColor const [backgroundColor, setBackgroundColor] = useState(initialBackgroundColor) - const [currentTextColor, setCurrentTextColor] = useState(textColor) + const [currentTextColor, setCurrentTextColor] = useState(defaultSettings.textColor) const handleButtonClick = () => { - const isSelected = backgroundColor !== selectedButtonColor - setBackgroundColor(isSelected ? selectedButtonColor : defaultButtonColor) - if (textColor !== palette.WHITE) { - setCurrentTextColor(isSelected ? palette.WHITE : textColor) + const isSelected = backgroundColor !== defaultSettings.selectedButtonColor + setBackgroundColor( + isSelected ? defaultSettings.selectedButtonColor : defaultSettings.defaultButtonColor, + ) + if (defaultSettings.textColor !== palette.WHITE) { + setCurrentTextColor(isSelected ? palette.WHITE : defaultSettings.textColor) } if (onClick) onClick(isSelected) } diff --git a/src/components/layouts/Layout.tsx b/src/components/layouts/Layout.tsx index ecdc23be..e6e41eb6 100644 --- a/src/components/layouts/Layout.tsx +++ b/src/components/layouts/Layout.tsx @@ -1,12 +1,18 @@ +import 'react-toastify/dist/ReactToastify.css' + import styled from '@emotion/styled' import { Outlet } from 'react-router-dom' +import { ToastContainer } from 'react-toastify' +import Modal from '@/components/common/Modal' import { theme } from '@/styles/theme' const Layout = () => { return ( + + ) } diff --git a/src/hooks/useModal.tsx b/src/hooks/useModal.tsx new file mode 100644 index 00000000..de18310e --- /dev/null +++ b/src/hooks/useModal.tsx @@ -0,0 +1,22 @@ +import Modal from '@/components/common/Modal' +import useModalStore from '@/store/ModalStore' + +type ModalConfirmPropsType = { + type: 'warn' | 'confirm' + okFunc: () => void + mainText: string + subText?: string +} +export const useModal = () => { + const { setModalState, setOkFunc, setMainText, setSubText, setType } = useModalStore() + + const openModal = ({ mainText, subText, okFunc, type }: ModalConfirmPropsType) => { + setModalState(true) + setType(type) + setMainText(mainText) + setSubText(subText) + setOkFunc(okFunc) + } + + return { openModal, Modal } +} diff --git a/src/hooks/useToast.tsx b/src/hooks/useToast.tsx new file mode 100644 index 00000000..cf7581cb --- /dev/null +++ b/src/hooks/useToast.tsx @@ -0,0 +1,25 @@ +import { ReactNode } from 'react' +import { toast } from 'react-toastify' + +type ToastType = 'success' | 'error' | 'info' | 'warning' + +type ToastProps = { + message: string | ReactNode + type: ToastType + isDarkMode: boolean +} + +export const useToast = () => { + const showToast = ({ message, type, isDarkMode }: ToastProps) => { + toast(message, { + position: 'top-center', + draggable: true, + theme: isDarkMode ? 'dark' : 'light', + type, + }) + } + + return { showToast } +} + +export default useToast diff --git a/src/store/ModalStore.tsx b/src/store/ModalStore.tsx new file mode 100644 index 00000000..c87e8751 --- /dev/null +++ b/src/store/ModalStore.tsx @@ -0,0 +1,28 @@ +import { create } from 'zustand' + +type ModalState = { + modalState: boolean + type: 'confirm' | 'warn' + okFunc: () => void + mainText: string + subText?: string | undefined + setType: (type: 'confirm' | 'warn') => void + setSubText: (text: string | undefined) => void + setModalState: (state: boolean) => void + setMainText: (text: string) => void + setOkFunc: (func: () => void) => void +} + +const useModalStore = create((set) => ({ + modalState: false, + okFunc: () => {}, + mainText: '', + subText: '', + type: 'confirm', + 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 }), +})) +export default useModalStore diff --git a/src/styles/global.ts b/src/styles/global.ts index cafaa3e1..2230a98c 100644 --- a/src/styles/global.ts +++ b/src/styles/global.ts @@ -6,19 +6,12 @@ export const globalStyle = css` ${emotionReset} @font-face { - font-family: 'InkLipquid'; - src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/InkLipquid.woff') - format('woff'); - font-weight: normal; - font-style: normal; - } - - @font-face { - font-family: 'Pretendard'; + font-family: 'Pretendard-Regular'; src: url('https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff'); font-weight: 400; font-style: normal; + font-display: swap; } body { diff --git a/tsconfig.json b/tsconfig.json index 51f7e0f0..027d4a68 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,8 @@ "@/hooks/*": ["src/hooks/*"], "@/assets/*": ["src/assets/*"], "@/styles/*": ["src/styles/*"], - "@/mocks/*": ["src/mocks/*"] + "@/mocks/*": ["src/mocks/*"], + "@/store/*": ["src/store/*"] } }, "include": ["src"],