diff --git a/src/assets/voteIcons/checkbox_checked.svg b/src/assets/voteIcons/checkbox_checked.svg new file mode 100644 index 00000000..50cbe8ba --- /dev/null +++ b/src/assets/voteIcons/checkbox_checked.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/chakra/buttonCustom.ts b/src/chakra/buttonCustom.ts new file mode 100644 index 00000000..aef344f6 --- /dev/null +++ b/src/chakra/buttonCustom.ts @@ -0,0 +1,79 @@ +import { defineStyle, defineStyleConfig } from "@chakra-ui/react"; + +const baseStyle = defineStyle({ + _hover: { + _disabled: { + backgroundColor: "neutral.200", + color: "neutral.400", + }, + }, + _disabled: { + backgroundColor: "neutral.200", + color: "neutral.400", + }, +}); + +const CTAButton = defineStyle({ + position: "fixed", + bottom: "2.4rem", + left: "50%", + transform: "translateX(-50%)", + width: "32.7rem", + height: "4.6rem", + + backgroundColor: "primary.300", + borderRadius: "16px", + + color: "neutral.0", + fontSize: "button", + fontWeight: "button", + lineHeight: "button", + + zIndex: "2", + + _hover: { + backgroundColor: "primary.400", + }, +}); + +const blueButton = defineStyle({ + backgroundColor: "primary.300", + borderRadius: "16px", + boxShadow: "shadow.200", + + color: "neutral.0", + fontSize: "button", + fontWeight: "button", + lineHeight: "button", + + _hover: { + backgroundColor: "primary.400", + }, +}); + +const voteButton = defineStyle({ + w: "18.4rem", + h: "5.4rem", + backgroundColor: "primary.300", + borderRadius: "48px", + boxShadow: "shadow.200", + + position: "fixed", + left: "50%", + transform: "translateX(-50%)", + bottom: "8rem", + + color: "neutral.0", + fontSize: "button", + fontWeight: "button", + lineHeight: "button", + + _hover: { + backgroundColor: "primary.400", + }, +}); + +export const buttonTheme = defineStyleConfig({ + baseStyle, + variants: { CTAButton, blueButton, voteButton }, +}); diff --git a/src/chakra/chakraCustomTheme.ts b/src/chakra/chakraCustomTheme.ts index 73cb7225..7bcd4dd2 100644 --- a/src/chakra/chakraCustomTheme.ts +++ b/src/chakra/chakraCustomTheme.ts @@ -1,10 +1,11 @@ import { extendTheme } from "@chakra-ui/react"; import { avatarTheme } from "./avatarCustom"; +import { buttonTheme } from "./buttonCustom"; +import { checkboxTheme } from "./checkboxCustom"; import { modalTheme } from "./modalCustom"; import { tabsTheme } from "./tabsCustom"; import { tagTheme } from "./tagCustom"; - export const customTheme = extendTheme({ styles: { global: { @@ -216,75 +217,8 @@ export const customTheme = extendTheme({ Tabs: tabsTheme, Tag: tagTheme, Modal: modalTheme, - Button: { - baseStyle: { - _disabled: { - backgroundColor: "neutral.200", - color: "neutral.400", - PointerEvent: "none", - }, - }, - //ex) Button 컴포넌트 내부에 variant="CTAButton" - variants: { - blueButton: { - backgroundColor: "primary.300", - borderRadius: "16px", - boxShadow: "shadow.200", - - color: "neutral.0", - fontSize: "button", - fontWeight: "button", - lineHeight: "button", - - _hover: { - backgroundColor: "primary.400", - }, - }, - CTAButton: { - position: "fixed", - bottom: "2.4rem", - left: "50%", - transform: "translateX(-50%)", - width: "32.7rem", - height: "4.6rem", - - backgroundColor: "primary.300", - borderRadius: "16px", - - color: "neutral.0", - fontSize: "button", - fontWeight: "button", - lineHeight: "button", - - zIndex: "2", - - _hover: { - backgroundColor: "primary.400", - }, - }, - voteButton: { - w: "18.4rem", - h: "5.4rem", - backgroundColor: "primary.300", - borderRadius: "48px", - boxShadow: "shadow.200", - - position: "fixed", - left: "50%", - transform: "translateX(-50%)", - bottom: "8rem", - - color: "neutral.0", - fontSize: "button", - fontWeight: "button", - lineHeight: "button", - - _hover: { - backgroundColor: "primary.400", - }, - }, - }, - }, + Checkbox: checkboxTheme, + Button: buttonTheme, }, fonts: { diff --git a/src/chakra/checkboxCustom.ts b/src/chakra/checkboxCustom.ts new file mode 100644 index 00000000..955261cf --- /dev/null +++ b/src/chakra/checkboxCustom.ts @@ -0,0 +1,32 @@ +import { checkboxAnatomy } from "@chakra-ui/anatomy"; +import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react"; + +// import CheckedIcon from "@/assets/voteIcons/Checkbox_checked.svg?react"; + +const { definePartsStyle, defineMultiStyleConfig } = + createMultiStyleConfigHelpers(checkboxAnatomy.keys); + +const candidateCheckbox = definePartsStyle({ + control: defineStyle({ + rounded: "full", + boxSize: "2rem", + mr: "12px", + mb: "35px", + _checked: { + background: "#2388FF", + borderColor: "#2388FF", + _hover: { + background: "#2388FF", + borderColor: "#2388FF", + }, + }, + _active: { + background: "#2388FF", + borderColor: "#2388FF", + }, + }), +}); + +export const checkboxTheme = defineMultiStyleConfig({ + variants: { candidateCheckbox }, +}); diff --git a/src/chakra/modalCustom.ts b/src/chakra/modalCustom.ts index 6bb3c83d..a3314e1e 100644 --- a/src/chakra/modalCustom.ts +++ b/src/chakra/modalCustom.ts @@ -1,28 +1,49 @@ import { modalAnatomy } from "@chakra-ui/anatomy"; -import { createMultiStyleConfigHelpers } from "@chakra-ui/styled-system"; +import { + createMultiStyleConfigHelpers, + defineStyle, +} from "@chakra-ui/styled-system"; const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(modalAnatomy.keys); const baseStyle = definePartsStyle({ + //dialog 부모 박스, 가로 사이즈 정하고 가운데 정렬 + dialogContainer: { + width: "100%", + maxWidth: "45rem", + minWidth: "36rem", + left: "50%", + transform: "translateX(-50%)", + }, + closeButton: { + fontSize: "1.5rem", + top: "16px", + right: "16px", + }, overlay: { - bg: "rgba(20, 20, 20, 0.8)", + bg: "rgba(0, 0, 0, 0.6)", }, }); +const alertSizeForDialog = defineStyle({ + mx: "20px", +}); + const alertModal = definePartsStyle({ dialogContainer: { - width: "45rem", + width: "100%", + maxWidth: "45rem", minWidth: "36rem", left: "50%", transform: "translateX(-50%)", - px: "0", }, dialog: { - w: "31.1rem", + w: "100%", + maxWidth: "45rem", mt: "264px", - mx: "0", - p: "32px 20px", + mx: "32px", + p: "32px 24px", borderRadius: "16px", gap: "24px", @@ -40,7 +61,12 @@ const alertModal = definePartsStyle({ }, }); +const sizes = { + xl: definePartsStyle({ dialog: alertSizeForDialog }), +}; + export const modalTheme = defineMultiStyleConfig({ + sizes, baseStyle, variants: { alertModal }, }); diff --git a/src/components/AlertModal/AlertModal.tsx b/src/components/AlertModal/AlertModal.tsx index 5c27aabc..7bded659 100644 --- a/src/components/AlertModal/AlertModal.tsx +++ b/src/components/AlertModal/AlertModal.tsx @@ -29,6 +29,7 @@ import { AlertModalProps } from "@/types/vote"; const AlertModal = ({ title, subText, + cancelText, actionButton, isSmallSize, onClickAction, @@ -57,7 +58,7 @@ const AlertModal = ({ onClick={() => setIsModalOpen(false)} className={styles.buttons__cancel} > - 취소 + {cancelText ? cancelText : "취소"} + + + + ); +}; + +export default DeleteCandidatesButton; diff --git a/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.module.scss b/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.module.scss index cf5da3a1..5a12608a 100644 --- a/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.module.scss +++ b/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.module.scss @@ -13,5 +13,9 @@ display: flex; gap: 10px; + + &:disabled { + color: $neutral400; + } } } diff --git a/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx b/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx index 505a66fc..2293b4a6 100644 --- a/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx +++ b/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { useSetRecoilState } from "recoil"; +// import { useState } from "react"; +import { useRecoilState, useSetRecoilState } from "recoil"; import styles from "./VoteMeatball.module.scss"; @@ -9,25 +9,26 @@ import CheckIcon from "@/assets/voteIcons/vote_check.svg?react"; import EditIcon from "@/assets/voteIcons/vote_edit.svg?react"; import RepeatIcon from "@/assets/voteIcons/vote_repeat.svg?react"; import TrashIcon from "@/assets/voteIcons/vote_trash.svg?react"; -import { isModalOpenState } from "@/recoil/vote/alertModal"; +import { + isCandidateSelectingState, + isModalOpenState, + modalContentState, +} from "@/recoil/vote/alertModal"; import { isBottomSlideOpenState } from "@/recoil/vote/bottomSlide"; import { confirmVoteContent, - deleteCandidateContent, deleteVoteContent, retryVoteContent, } from "./modalContent"; import { AlertModalProps, VoteMeatballProps } from "@/types/vote"; -const VoteMeatball = ({ state }: VoteMeatballProps) => { +const VoteMeatball = ({ state, isZeroCandidates }: VoteMeatballProps) => { const setIsBTOpen = useSetRecoilState(isBottomSlideOpenState); const setIsModalOpen = useSetRecoilState(isModalOpenState); - - const [modalProps, setModalProps] = useState( - retryVoteContent, - ); + const [modalContent, setModalContent] = useRecoilState(modalContentState); + const setIsCandidateSelecting = useSetRecoilState(isCandidateSelectingState); const modalConsole = () => { console.log("변경"); @@ -35,7 +36,13 @@ const VoteMeatball = ({ state }: VoteMeatballProps) => { const showAlertModal = ({ ...content }: AlertModalProps) => { setIsModalOpen(true); - setModalProps({ ...content }); + setModalContent({ ...content }); + setIsBTOpen(false); + }; + + const changeToCandidateSelecting = () => { + setIsCandidateSelecting(true); + console.log("체체체체인지"); setIsBTOpen(false); }; @@ -52,6 +59,7 @@ const VoteMeatball = ({ state }: VoteMeatballProps) => { ) : ( - + - {modalProps && } + ); }; diff --git a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx index cef3f90d..e9ee6a03 100644 --- a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx +++ b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { FaRegStar, FaStar } from "react-icons/fa"; import { Link } from "react-router-dom"; +import { useRecoilValue } from "recoil"; import styles from "./CandidateCard.module.scss"; @@ -8,6 +9,7 @@ import FirstIcon from "@/assets/voteIcons/rank_1.svg?react"; import SecondIcon from "@/assets/voteIcons/rank_2.svg?react"; import ThirdIcon from "@/assets/voteIcons/rank_3.svg?react"; import AddDayIcon from "@/assets/voteIcons/vote_addDay.svg?react"; +import { isCandidateSelectingState } from "@/recoil/vote/alertModal"; import VotedUserList from "../../VoteBottomSlideContent/VotedUserList/VotedUserList"; @@ -20,6 +22,7 @@ const CandidateCard = ({ index, }: CandidateCardProps) => { const [isVoted, setIsVoted] = useState(false); + const isCandidateSelecting = useRecoilValue(isCandidateSelectingState); const voteCounts = candidate.voteCounts; const getRankClassName = (index: number) => { @@ -92,7 +95,11 @@ const CandidateCard = ({ )} - -
- {candidates ? ( - candidates.map((candidate, i) => ( -
- -
- -
- {candidate.memo} -
-
-
- )) - ) : ( - - )} -
+ + {/* 후보지&여행지 X -> 상품 추천 없음 */} + {isCandidateSelecting && } ); }; diff --git a/src/mocks/handlers/vote.ts b/src/mocks/handlers/vote.ts index e2842bf1..3780a480 100644 --- a/src/mocks/handlers/vote.ts +++ b/src/mocks/handlers/vote.ts @@ -12,7 +12,7 @@ const candidateData = [ voteUserId: ["Id123", "Id234", "Id345"], voteCounts: 3, memo: "예쁨~~~~~~~~~", - id: 1, + id: 111, }, { name: "니은펜션", @@ -23,7 +23,7 @@ const candidateData = [ voteUserId: ["Id123", "Id345"], voteCounts: 2, memo: "여기 개쩔드라", - id: 2, + id: 222, }, { name: "기역호텔", @@ -34,7 +34,7 @@ const candidateData = [ voteUserId: ["Id123", "Id234", "Id345"], voteCounts: 3, memo: "예쁨~~~~~~~~~", - id: 4, + id: 333, }, { name: "니은펜션", @@ -45,7 +45,7 @@ const candidateData = [ voteUserId: ["Id123", "Id345"], voteCounts: 2, memo: "여기 개쩔드라", - id: 3, + id: 444, }, ]; diff --git a/src/pages/Vote/Vote.tsx b/src/pages/Vote/Vote.tsx index c809a85b..7388c27d 100644 --- a/src/pages/Vote/Vote.tsx +++ b/src/pages/Vote/Vote.tsx @@ -1,7 +1,7 @@ import { Button } from "@chakra-ui/react"; import { ReactNode, useEffect, useState } from "react"; import { useParams } from "react-router-dom"; -import { useRecoilState } from "recoil"; +import { useRecoilState, useSetRecoilState } from "recoil"; import styles from "./Vote.module.scss"; @@ -12,12 +12,18 @@ import VoteContentEmpty from "@/components/Vote/VoteContent/VoteContentEmpty/Vot import VoteHeader from "@/components/Vote/VoteHeader/VoteHeader"; import { getVoteData } from "@/mocks/handlers/vote"; +import { isCandidateSelectingState } from "@/recoil/vote/alertModal"; import { isBottomSlideOpenState } from "@/recoil/vote/bottomSlide"; +import { selectedCandidatesState } from "@/recoil/vote/candidateList"; import { VoteListData } from "@/types/vote"; const Vote = () => { const [isBTOpen, setIsBTOpen] = useRecoilState(isBottomSlideOpenState); + const [isCandidateSelecting, setIsCandidateSelecting] = useRecoilState( + isCandidateSelectingState, + ); + const setSelectedCandidates = useSetRecoilState(selectedCandidatesState); const [showResults, setShowResults] = useState(false); const [bottomSlideContent, setBottomSlideContent] = useState(null); @@ -26,33 +32,22 @@ const Vote = () => { useEffect(() => { getVoteData(param, setData); + setIsCandidateSelecting(false); + setShowResults(false); + setSelectedCandidates(new Set()); }, []); const BottomSlideOpen = (content: ReactNode) => { setBottomSlideContent(content); setIsBTOpen(true); + setSelectedCandidates(new Set()); + setIsCandidateSelecting(false); }; const handleShowResultsClick = () => { setShowResults(!showResults); }; - // const showBottomButton = () => { - // if(data?.state === "진행 중") { - // - // } else if ("리코일 후보삭제") { - // //새로운 버튼 컴포넌트 - // } else { - // null - // } - // } - return ( <> {data && ( @@ -61,7 +56,12 @@ const Vote = () => { - BottomSlideOpen() + BottomSlideOpen( + , + ) } /> @@ -74,8 +74,7 @@ const Vote = () => { ) : ( )} - - {data.state === "진행 중" && ( + {!isCandidateSelecting && data.state === "진행 중" && ( )} - setIsBTOpen(false)} diff --git a/src/recoil/vote/alertModal.ts b/src/recoil/vote/alertModal.ts index c8e1875b..904911bf 100644 --- a/src/recoil/vote/alertModal.ts +++ b/src/recoil/vote/alertModal.ts @@ -1,6 +1,24 @@ import { atom } from "recoil"; +import { AlertModalProps } from "@/types/vote"; + export const isModalOpenState = atom({ key: "isModalOpenState", default: false, }); + +export const isCandidateSelectingState = atom({ + key: "isCandidateSelectingState", + default: false, +}); + +export const modalContentState = atom({ + key: "modalContentState", + default: { + title: "", + subText: "", + actionButton: "", + isSmallSize: false, + onClickAction: () => {}, + }, +}); diff --git a/src/recoil/vote/candidateList.ts b/src/recoil/vote/candidateList.ts new file mode 100644 index 00000000..6f936349 --- /dev/null +++ b/src/recoil/vote/candidateList.ts @@ -0,0 +1,6 @@ +import { atom } from "recoil"; + +export const selectedCandidatesState = atom>({ + key: "selectedCandidatesState", + default: new Set(), +}); diff --git a/src/types/vote.ts b/src/types/vote.ts index a2b88949..5ba52a85 100644 --- a/src/types/vote.ts +++ b/src/types/vote.ts @@ -1,21 +1,5 @@ import { ReactNode } from "react"; -export interface VoteBottomButtonProps { - onClick: () => void; - title: string; -} - -export interface VoteContentProps { - onBottomSlideOpen: (content: ReactNode) => void; - data: VoteListData; - showResults: boolean; -} - -export interface VoteHeaderProps { - onBottomSlideOpen: () => void; - title: string; -} - export interface CandidateData { name: string; imageURL: string; @@ -37,6 +21,68 @@ export interface VoteListData { id: string; } +////////////////스웨거////////////////// +export interface SSCandidateData { + id: number; + placeId: number; // + placeName: string; + category: string; + tagline: string; //메모? + amIVoted: boolean; // + //imageURL: string; + //location: string; + //voteUserId: string[]; //이거 필요 + //voteCounts: number; + //memo: string; +} +[]; +export interface SSVoteData { + id: string; + title: string; + ownerProfile: { + id: number; + nickName: string; + profile: string; + }; + candidates: CandidateData[]; + // state: string; + // voteUserId: string[]; +} + +export interface SSVoteCardData { + voteId: string; + title: string; + ownerProfile: { + id: number; + nickName: string; + profile: string; + }; + votedMemberProfiles: [ + { + id: 0; //props으로 내려줄게 아니라면 사진만 있어도 됨, 배열로 + nickName: string; + profile: string; + }, + ]; +} +////////////////스웨거////////////////// + +export interface VoteBottomButtonProps { + onClick: () => void; + title: string; +} + +export interface VoteContentProps { + onBottomSlideOpen: (content: ReactNode) => void; + data: VoteListData; + showResults: boolean; +} + +export interface VoteHeaderProps { + onBottomSlideOpen: () => void; + title: string; +} + export interface CandidateCardProps { onBottomSlideOpen: (content: ReactNode) => void; candidate: CandidateData; @@ -46,12 +92,21 @@ export interface CandidateCardProps { export interface VoteMeatballProps { state: string; + isZeroCandidates: boolean; } export interface AlertModalProps { title: string; subText?: string; + cancelText?: string; actionButton: string; isSmallSize: boolean; onClickAction?: () => void; } + +export interface CandidateListProps { + candidates: CandidateData[]; + onBottomSlideOpen: (content: ReactNode) => void; + showResults: boolean; + isCandidateSelecting: boolean; +}