diff --git a/index.html b/index.html index 29434a66..645505d3 100644 --- a/index.html +++ b/index.html @@ -64,7 +64,7 @@
- + diff --git a/package.json b/package.json index 06bcc210..b4d14920 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "axios": "^0.26.0", + "clipboard-copy": "^4.0.1", "compressorjs": "^1.1.1", "qs": "^6.11.0", "react": "^18.1.0", diff --git a/src/@components/@common/Modal/index.tsx b/src/@components/@common/Modal/index.tsx index f2159e8f..75b2e565 100644 --- a/src/@components/@common/Modal/index.tsx +++ b/src/@components/@common/Modal/index.tsx @@ -5,7 +5,7 @@ import useOutClickCloser from "../hooks/useOutClickCloser"; import ModalPortal from "./Portal"; import * as St from "./style"; -type ModalTheme = "DEFAULT" | "WHITE_BOTTOM" | "GRAY_CENTER" | "GRAY_BOTTOM"; +type ModalTheme = "DEFAULT" | "WHITE_BOTTOM" | "GRAY_CENTER" | "GRAY_BOTTOM" | "COACHMARK"; interface ModalContents { theme?: ModalTheme; @@ -19,6 +19,17 @@ export default function Modal(props: PropsWithChildren) { const { theme = "DEFAULT", closeHandler, closeOpacityClassName, closeBtnClassName, isNoCloseBtn, children } = props; const outClickCloserRef = useOutClickCloser(closeHandler, true); + if (theme === "COACHMARK") + return ( + + + + {children} + + + + ); + if (theme === "GRAY_CENTER") return ( diff --git a/src/@components/@common/Modal/style.ts b/src/@components/@common/Modal/style.ts index 49fec432..c0b77a24 100644 --- a/src/@components/@common/Modal/style.ts +++ b/src/@components/@common/Modal/style.ts @@ -15,7 +15,6 @@ export const Root = styled.div` z-index: 10; - background-color: rgb(0, 0, 0, 0.5); display: flex; justify-content: center; `; @@ -29,8 +28,17 @@ export const fadeOut = keyframes` } `; +export const CoachMarkRoot = styled(Root)` + height: 100vh; + height: calc(var(--vh, 1vh) * 100); + min-height: -webkit-fill; + + background-color: ${({ theme }) => theme.newColors.blackblur}; +`; + export const GrayRoot = styled(Root)` animation: ${fadeOut} 0.8s ease-in-out; + background-color: ${({ theme }) => theme.newColors.black50}; `; export const WhiteRoot = styled(Root)` @@ -41,6 +49,8 @@ export const WhiteRoot = styled(Root)` min-height: -webkit-fill-available; animation: ${fadeOut} 0.8s ease-in-out; + + background-color: ${({ theme }) => theme.newColors.black50}; `; export const DefaultRoot = styled(Root)` @@ -50,6 +60,8 @@ export const DefaultRoot = styled(Root)` height: calc(var(--vh, 1vh) * 100); min-height: -webkit-fill-available; padding: 1.6rem; + + background-color: ${({ theme }) => theme.newColors.black50}; `; export const bottomUp = keyframes` @@ -70,6 +82,14 @@ const CenterModal = styled.section` transform: translateY(-50%); `; +export const CoachMarkModal = styled(CenterModal)` + width: 100%; + ${({ theme }) => theme.media.desktop` + width: 36rem; + `}; + height: 100%; +`; + export const GrayCenterModal = styled(CenterModal)` width: 100%; ${({ theme }) => theme.media.desktop` diff --git a/src/@components/@common/hooks/useNavigateCardCollection.ts b/src/@components/@common/hooks/useNavigateCardCollection.ts index cadf0754..8bbffdd6 100644 --- a/src/@components/@common/hooks/useNavigateCardCollection.ts +++ b/src/@components/@common/hooks/useNavigateCardCollection.ts @@ -9,12 +9,18 @@ import { LocationType } from "../../../types/cardCollection"; export type NavigateCardCollectionAllType = (sliderIdx?: number) => void; export type NavigateCardCollectionBestType = (sliderIdx?: number) => void; export type NavigateCardCollectionBookMarkType = (sliderIdx?: number) => void; +export type NavigateCardCollectionRecentType = (sliderIdx?: number) => void; +export type NavigateCardCollectionUpdateType = (sliderIdx?: number) => void; export type NavigateCardCollectionCategoryType = (categoryId: string, sliderIdx?: number) => void; export type NavigateCardCollectionFilterType = (filterTypes: string[], sliderIdx?: number) => void; export type NavigateCardCollectionMedleyType = (medleyId: string, sliderIdx?: number) => void; export type NavigateRecentCollectionType = (sliderIdx?: number) => void; export type NavigateFemaleCollectionType = (sliderIdx?: number) => void; export type NavigateMaleCollectionType = (sliderIdx?: number) => void; +export type NavigateCardCollectionShareType = ( + cardId: string, + sliderIdx?: number, +) => { navigate: () => void; url: string }; export default function useNavigateCardCollection(locationType: LocationType) { const navigate = useNavigate(); @@ -66,16 +72,36 @@ export default function useNavigateCardCollection(locationType: LocationType) { navigate(`${routePaths.CardCollection}?type=${LocationType.RECENT}`); setSliderIdx(sliderIdx); }; + + case LocationType.UPDATE: + return (sliderIdx = 0) => { + navigate(`${routePaths.CardCollection}?type=${LocationType.UPDATE}`); + setSliderIdx(sliderIdx); + }; + case LocationType.FEMALE: return (sliderIdx = 0) => { navigate(`${routePaths.CardCollection}?type=${LocationType.FEMALE}`); setSliderIdx(sliderIdx); }; + case LocationType.MALE: return (sliderIdx = 0) => { navigate(`${routePaths.CardCollection}?type=${LocationType.MALE}`); setSliderIdx(sliderIdx); }; + + case LocationType.SHARE: + return (cardId: string, sliderIdx = 0) => { + const shareUrl = `${routePaths.CardCollection}?type=${LocationType.SHARE}&cardId=${cardId}`; + return { + navigate: () => { + navigate(shareUrl); + setSliderIdx(sliderIdx); + }, + url: shareUrl, + }; + }; } } diff --git a/src/@components/@common/hooks/useRecentlyBookmarked.ts b/src/@components/@common/hooks/useRecentlyBookmarked.ts new file mode 100644 index 00000000..7ceb8624 --- /dev/null +++ b/src/@components/@common/hooks/useRecentlyBookmarked.ts @@ -0,0 +1,17 @@ +import useSWR from "swr"; + +import { realReq } from "../../../core/api/common/axios"; +import { PATH } from "../../../core/api/common/constants"; +import { RecentCardList } from "../../../types/cardCollection"; +import { PiickleSWRResponse } from "../../../types/remote/swr"; + +export function useRecentlyBookmarked() { + const { data } = useSWR>(`${PATH.CARDS_}${PATH.CARDS_RECENT}`, realReq.GET_SWR, { + suspense: true, + }); + + return { + recentlyBookmarkedDate: data?.data.data.recentlyDate, + recentlyBookmarkedCards: data?.data.data.cardResponseDtos, + }; +} diff --git a/src/@components/BestPiicklePage/BestPiickleRank/RankItem/style.ts b/src/@components/BestPiicklePage/BestPiickleRank/RankItem/style.ts index 05450098..4987cb7c 100644 --- a/src/@components/BestPiicklePage/BestPiickleRank/RankItem/style.ts +++ b/src/@components/BestPiicklePage/BestPiickleRank/RankItem/style.ts @@ -34,7 +34,7 @@ export const RankItemText = styled.p` overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - + width: 26.4rem; cursor: pointer; diff --git a/src/@components/BestPiicklePage/BestPiickleRank/index.tsx b/src/@components/BestPiicklePage/BestPiickleRank/index.tsx index 36128224..2f854785 100644 --- a/src/@components/BestPiicklePage/BestPiickleRank/index.tsx +++ b/src/@components/BestPiicklePage/BestPiickleRank/index.tsx @@ -15,6 +15,8 @@ const rankTitles: HeadingTitle = { content: "가장 많이 북마크한 대화주제를 확인해보세요", }; +const BEST_PIICKLE_TOTAL_COUNT = 8; + export default function BestPiickleRank() { const { bestPiickle } = useBestPiickle(); const { isModalOpen: isLoginModalOpen, toggleModal: toggleLoginModal } = useModal(); @@ -24,7 +26,7 @@ export default function BestPiickleRank() { {bestPiickle && [...bestPiickle.data] - .slice(0, 8) + .slice(0, BEST_PIICKLE_TOTAL_COUNT) .map(({ _id, content, isBookmark }, idx) => ( - {recommendList.cards.slice(0, 4).map((cards, idx) => { + {recommendList.cards.slice(0, BEST_PIICKLE_TOTAL_COUNT).map((cards, idx) => { return ( BEST_PIICKLE_TOTAL_COUNT && BEST_PIICKLE_TOTAL_COUNT - 1) + } locationType={recommendList.locationType} /> ); diff --git a/src/@components/BestPiicklePage/BestPiickleRecommend/hooks/useRecentlyBookmarked.ts b/src/@components/BestPiicklePage/BestPiickleRecommend/hooks/useRecentlyBookmarked.ts deleted file mode 100644 index a62101a4..00000000 --- a/src/@components/BestPiicklePage/BestPiickleRecommend/hooks/useRecentlyBookmarked.ts +++ /dev/null @@ -1,16 +0,0 @@ -import useSWR from "swr"; - -import { realReq } from "../../../../core/api/common/axios"; -import { PATH } from "../../../../core/api/common/constants"; -import { RecentCardList } from "../../../../types/cardCollection"; -import { PiickleSWRResponse } from "../../../../types/remote/swr"; - -export function useRecentlyBookmarked() { - const { data } = useSWR>(`${PATH.CARDS_}${PATH.CARDS_RECENT}`, realReq.GET_SWR, { - suspense: true, - }); - - return { - recentlyBookmarkedCards: data?.data.data.cardResponseDtos, - }; -} diff --git a/src/@components/BestPiicklePage/BestPiickleRecommend/index.tsx b/src/@components/BestPiicklePage/BestPiickleRecommend/index.tsx index fee8b028..05d3734b 100644 --- a/src/@components/BestPiicklePage/BestPiickleRecommend/index.tsx +++ b/src/@components/BestPiicklePage/BestPiickleRecommend/index.tsx @@ -1,8 +1,8 @@ import { CardList, LocationType } from "../../../types/cardCollection"; import { HeadingTitle } from "../../../util/main/headingTitles"; import HeadingTitleContainer from "../../@common/HeadingTitleContainer"; +import { useRecentlyBookmarked } from "../../@common/hooks/useRecentlyBookmarked"; import { useCardsByGender } from "./hooks/useCardsByGender"; -import { useRecentlyBookmarked } from "./hooks/useRecentlyBookmarked"; import RecommendItem from "./RecommendItem"; import * as St from "./style"; diff --git a/src/@components/CardCollectionPage/Card/CardMenu/index.tsx b/src/@components/CardCollectionPage/Card/CardMenu/index.tsx index 267f2077..6f6cbd84 100644 --- a/src/@components/CardCollectionPage/Card/CardMenu/index.tsx +++ b/src/@components/CardCollectionPage/Card/CardMenu/index.tsx @@ -3,6 +3,7 @@ import IcMenuBtn from "../../../../asset/icon/IcMenuBtn"; import IcShareBtn from "../../../../asset/icon/IcShareBtn"; import { GTM_CLASS_NAME } from "../../../../util/const/gtm"; import useCardBookmark from "../../hooks/useCardBookmark"; +import useCardShare from "../../hooks/useCardShare"; import * as St from "./style"; interface CardMenuProps { @@ -15,13 +16,15 @@ interface CardMenuProps { export default function CardMenu(props: CardMenuProps) { const { _id, isBookmark, openLoginModalHandler, toggleMenuModal } = props; + const { handleCopyClipBoard } = useCardShare(); const { isBookmarked, handleClickBookmark } = useCardBookmark(isBookmark, openLoginModalHandler); + return ( - + handleCopyClipBoard(_id)}> diff --git a/src/@components/CardCollectionPage/CoachMark/index.tsx b/src/@components/CardCollectionPage/CoachMark/index.tsx new file mode 100644 index 00000000..706fedb7 --- /dev/null +++ b/src/@components/CardCollectionPage/CoachMark/index.tsx @@ -0,0 +1,37 @@ +import IcMenuBtn from "../../../asset/icon/IcMenuBtn"; +import IcShareBtn from "../../../asset/icon/IcShareBtn"; +import useOutClickCloser from "../../@common/hooks/useOutClickCloser"; +import Modal from "../../@common/Modal"; +import * as St from "./style"; + +interface CoachMarkProps { + closeHandler: () => void; +} + +export default function CoachMark(props: CoachMarkProps) { + const { closeHandler } = props; + const outClickCloserRef = useOutClickCloser(closeHandler, true); + + return ( + + + + 새로 생긴 기능들을 +
+ 확인해보세요 +
+ + + + + + + + + 공유하기 + + +
+
+ ); +} diff --git a/src/@components/CardCollectionPage/CoachMark/style.ts b/src/@components/CardCollectionPage/CoachMark/style.ts new file mode 100644 index 00000000..63527d78 --- /dev/null +++ b/src/@components/CardCollectionPage/CoachMark/style.ts @@ -0,0 +1,55 @@ +import styled from "styled-components"; + +export const Container = styled.section` + position: absolute; + right: 0; + + margin-top: 5.2rem; + height: calc((100% + 3.3rem) * 0.77); + padding-bottom: 10.5rem; + + display: flex; + flex-direction: column; + justify-content: flex-end; +`; + +export const Contents = styled.p` + text-align: right; + padding-right: 1.6rem; + margin-bottom: 2.5rem; + + ${({ theme }) => theme.newFonts.caption1} + color: ${({ theme }) => theme.newColors.white}; +`; + +export const ImageWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: flex-end; + margin-right: 2.1rem; + + gap: 2.4rem; +`; + +export const ButtonWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + width: 4.1rem; +`; + +export const IconWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 4rem; + height: 4rem; +`; + +export const ButtonLabel = styled.p` + margin-top: 0.6rem; + + ${({ theme }) => theme.newFonts.caption1}; + color: ${({ theme }) => theme.newColors.white}; +`; diff --git a/src/@components/CardCollectionPage/hooks/useCardLists.ts b/src/@components/CardCollectionPage/hooks/useCardLists.ts index 21c9c38b..bc1498ac 100644 --- a/src/@components/CardCollectionPage/hooks/useCardLists.ts +++ b/src/@components/CardCollectionPage/hooks/useCardLists.ts @@ -47,6 +47,8 @@ function getReturnCardLists( return data?.data.data.cards; case LocationType.RECENT: return data?.data.data.cardResponseDtos; + case LocationType.UPDATE: + return data?.data.data.cardResponseDtos; default: return data?.data.data; } @@ -92,12 +94,17 @@ function getSWRFetchingKeyByLocation(cardsTypeLocation: CardsTypeLocation) { case LocationType.FILTER: { return `${PATH.CATEGORIES_}${PATH.CATEGORIES_CARDS}?${cardsTypeLocation.filterTypes}`; } + + case LocationType.UPDATE: + return `${PATH.CARDS_}${PATH.CARDS_UPDATE}`; case LocationType.RECENT: return `${PATH.CARDS_}${PATH.CARDS_RECENT}`; case LocationType.FEMALE: return `${PATH.CARDS_}${PATH.CARDS_GENDER}/여`; case LocationType.MALE: return `${PATH.CARDS_}${PATH.CARDS_GENDER}/남`; + case LocationType.SHARE: + return `${PATH.CARDS_}/${cardsTypeLocation.cardId}`; case LocationType.ALL: default: { diff --git a/src/@components/CardCollectionPage/hooks/useCardShare.ts b/src/@components/CardCollectionPage/hooks/useCardShare.ts new file mode 100644 index 00000000..93a26607 --- /dev/null +++ b/src/@components/CardCollectionPage/hooks/useCardShare.ts @@ -0,0 +1,28 @@ +import copyLink from "clipboard-copy"; + +import { LocationType } from "../../../types/cardCollection"; +import useNavigateCardCollection, { + NavigateCardCollectionShareType, +} from "../../@common/hooks/useNavigateCardCollection"; +import useToast from "../../@common/Toast/hooks/useToast"; + +export default function useCardShare() { + const navigateCardCollection = useNavigateCardCollection(LocationType.SHARE) as NavigateCardCollectionShareType; + const showToast = useToast(); + + const getShareUrl = (_id: string) => { + const basePath = `${import.meta.env.DEV ? "http://127.0.0.1:5173" : import.meta.env.VITE_SERVICE_URL}`; + const shareNavigation = navigateCardCollection(_id); + return `${basePath}${shareNavigation.url}`; + }; + + const handleCopyClipBoard = (_id: string) => { + const shareUrl = getShareUrl(_id); + const textToCopy = `이런 대화주제는 어때요?\n지금 한번 확인해보세요.\n${shareUrl}?utm_content=sharelink`; + + copyLink(textToCopy); + showToast({ message: "📤 링크를 복사했어요", duration: 2.5 }); + }; + + return { handleCopyClipBoard }; +} diff --git a/src/@components/CardCollectionPage/hooks/useCoachMark.ts b/src/@components/CardCollectionPage/hooks/useCoachMark.ts new file mode 100644 index 00000000..10ed82d8 --- /dev/null +++ b/src/@components/CardCollectionPage/hooks/useCoachMark.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react"; + +const COACH_MARK_SESSION_KEY = "coach-mark-shown"; + +export default function useCoachMark() { + const [isOpened, setIsOpened] = useState(false); + + useEffect(() => { + const isCoachMarkShown = localStorage.getItem(COACH_MARK_SESSION_KEY); + if (!isCoachMarkShown || isOpened) { + setIsOpened(true); + localStorage.setItem(COACH_MARK_SESSION_KEY, "true"); + } + }, []); + + const handleCloseCoachMark = () => { + setIsOpened(false); + }; + + return { isOpened, handleCloseCoachMark }; +} diff --git a/src/@components/CardCollectionPage/index.tsx b/src/@components/CardCollectionPage/index.tsx index 65248149..08b230b2 100644 --- a/src/@components/CardCollectionPage/index.tsx +++ b/src/@components/CardCollectionPage/index.tsx @@ -11,8 +11,10 @@ import useScroll from "../@common/hooks/useScrollToTop"; import LoginModal from "../@common/LoginModal"; import SuspenseBoundary from "../@common/SuspenseBoundary"; import CardSlider from "./CardSlider"; +import CoachMark from "./CoachMark"; import FilterModal from "./FilterModal"; import { useCardLists } from "./hooks/useCardLists"; +import useCoachMark from "./hooks/useCoachMark"; import useCTAFilter from "./hooks/useCTAFilter"; import * as St from "./style"; @@ -35,6 +37,8 @@ function CardCollectionContent() { const { isModalOpen: isFilterModalOpen, toggleModal: toggleFilterModal } = useModal(); const { isModalOpen: isLoginModalOpen, toggleModal: toggleLoginModal } = useModal(); + const { isOpened: isCoachMarkOpen, handleCloseCoachMark: toggleCoachMark } = useCoachMark(); + const isSliderDown = useRecoilValue(isSliderDownState); return ( @@ -52,7 +56,7 @@ function CardCollectionContent() { 필터 설정하기 )} - + {isCoachMarkOpen && } {isLoginModalOpen && } {isFilterModalOpen && ( diff --git a/src/@components/MainPage/Banner/Slide/index.tsx b/src/@components/MainPage/Banner/Slide/index.tsx new file mode 100644 index 00000000..614318be --- /dev/null +++ b/src/@components/MainPage/Banner/Slide/index.tsx @@ -0,0 +1,26 @@ +import "swiper/swiper.css"; + +import { newBannerType } from "../../../../util/main/banner"; +import SlideContent from "../SlideContent"; +import * as St from "./style"; + +export default function Slide(props: newBannerType) { + const { bannerImage, phrase, topic, isLightMode, isLast } = props; + return ( + + + + {phrase} + {topic} + + + + + + + + + + + ); +} diff --git a/src/@components/MainPage/Banner/Slide/style.ts b/src/@components/MainPage/Banner/Slide/style.ts new file mode 100644 index 00000000..b07c3a4f --- /dev/null +++ b/src/@components/MainPage/Banner/Slide/style.ts @@ -0,0 +1,62 @@ +import styled from "styled-components"; + +export const SlideContainer = styled.div` + position: relative; +`; + +export const SlideContentWrapper = styled.div` + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-between; + + width: 100%; + height: 100%; + padding-top: 3rem; +`; + +export const SlideSubtitle = styled.h2` + ${({ theme }) => theme.newFonts.h2}; +`; +export const SlideTitle = styled.h1` + ${({ theme }) => theme.newFonts.h1}; + color: ${({ theme }) => theme.newColors.green}; +`; + +export const SlideTitles = styled.div<{ islightmode: boolean; isLast: boolean }>` + display: flex; + flex-direction: column; + gap: 0.4rem; + + align-items: ${({ isLast }) => isLast && "center"}; + margin-left: ${({ isLast }) => !isLast && "2.5rem"}; + + ${SlideSubtitle} { + color: ${({ theme, islightmode }) => (islightmode ? theme.newColors.gray600 : theme.newColors.gray400)}; + font-size: ${({ isLast }) => isLast && "1.6rem"}; + } + + ${SlideTitle} { + font-size: ${({ isLast }) => isLast && "2.3rem"}; + } +`; +export const ImageWrapper = styled.img` + width: 100%; + height: 30.2rem; + object-fit: cover; +`; + +export const Gradient = styled.div<{ islightmode: boolean }>` + position: absolute; + + width: 100%; + height: 10.2rem; + + bottom: 0; + z-index: 1; + + background: ${({ islightmode }) => + islightmode + ? "linear-gradient(0, #EDEDEF 0%, rgba(232, 232, 234, 0.00) 100%);" + : "linear-gradient(0, #241e20 0%, rgba(36, 30, 32, 0) 100%)"}; +`; diff --git a/src/@components/MainPage/Banner/SlideContent/index.tsx b/src/@components/MainPage/Banner/SlideContent/index.tsx new file mode 100644 index 00000000..9c353cf1 --- /dev/null +++ b/src/@components/MainPage/Banner/SlideContent/index.tsx @@ -0,0 +1,37 @@ +import { newBannerType } from "../../../../util/main/banner"; +import * as St from "./style"; +const DATE_FORMAT = { + START: 2, + END: 10, +}; + +const CARDS_LIMIT = { + START: 0, + END: 4, +}; + +export default function SlideContent(props: newBannerType) { + const { isLast, date, isLightMode, cards, linkTo } = props; + + if (isLast) { + return 추가하러가기; + } + + return ( + + + + {date?.replace(/-/g, ".").substring(DATE_FORMAT.START, DATE_FORMAT.END)} + + New + + + {cards?.slice(CARDS_LIMIT.START, CARDS_LIMIT.END).map((card) => ( + + {card.content} + + ))} + + + ); +} diff --git a/src/@components/MainPage/Banner/SlideContent/style.ts b/src/@components/MainPage/Banner/SlideContent/style.ts new file mode 100644 index 00000000..760b3a2e --- /dev/null +++ b/src/@components/MainPage/Banner/SlideContent/style.ts @@ -0,0 +1,72 @@ +import styled from "styled-components"; + +export const SlideContent = styled.div` + display: flex; + flex-direction: column; + + margin-bottom: 3.2rem; + margin-left: 2.5rem; + + gap: 1.2rem; +`; + +export const SlideDate = styled.span` + display: flex; + flex-direction: row; + align-items: center; + + gap: 0.4rem; +`; + +export const DateString = styled.h2<{ islightmode: boolean }>` + ${({ theme }) => theme.newFonts.h2}; + color: ${({ theme, islightmode }) => (islightmode ? theme.newColors.gray600 : theme.newColors.gray400)}; +`; + +export const NewBadge = styled.div` + ${({ theme }) => theme.newFonts.caption1}; + color: ${({ theme }) => theme.newColors.white}; + + display: flex; + align-items: center; + justify-content: center; + + width: 4rem; + height: 2.1rem; + border-radius: 4.2rem; + + background: ${({ theme }) => theme.newColors.green}; +`; + +export const SlideCard = styled.p<{ islightmode: boolean }>` + ${({ theme }) => theme.newFonts.body4}; + color: ${({ theme, islightmode }) => (islightmode ? theme.newColors.gray900 : theme.newColors.white)}; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + width: 26.5rem; +`; + +export const LinkTo = styled.a` + ${({ theme }) => theme.newFonts.btn1}; + color: ${({ theme }) => theme.newColors.gray900}; + text-decoration: underline; + + margin-bottom: 2.4rem; + align-self: center; + + z-index: 10; +`; + +export const LinkScope = styled.a` + position: absolute; + left: 0; + top: 0; + + width: 100%; + height: 100%; + + z-index: 9; +`; diff --git a/src/@components/MainPage/Banner/index.tsx b/src/@components/MainPage/Banner/index.tsx index 0256cd3e..f6e912eb 100644 --- a/src/@components/MainPage/Banner/index.tsx +++ b/src/@components/MainPage/Banner/index.tsx @@ -3,36 +3,75 @@ import "swiper/swiper.css"; import { Helmet } from "react-helmet"; import { Swiper, SwiperSlide } from "swiper/react"; -import { bannerImage } from "../../../util/main/banner"; +import { routePaths } from "../../../core/routes/path"; +import { LocationType } from "../../../types/cardCollection"; +import { externalLinks } from "../../../util/const/externalLinks"; +import { newBannerImages, newBannerType } from "../../../util/main/banner"; +import { useRecentlyBookmarked } from "../../@common/hooks/useRecentlyBookmarked"; import useBannerSwiper from "../hooks/useBannerSwiper"; -import St from "./style"; +import { useRecentlyUpdated } from "../hooks/useRecentlyUpdated"; +import Slide from "./Slide"; +import * as St from "./style"; export default function Banner() { const { swiperSettings, currentSlide } = useBannerSwiper(); + const { recentlyBookmarkedDate, recentlyBookmarkedCards } = useRecentlyBookmarked(); + const { recentlyUpdatedDate, recentlyUpdatedCards } = useRecentlyUpdated(); + + const newBanners: newBannerType[] = [ + { + bannerImage: newBannerImages[0], + phrase: "가장 최근 북마크 된", + topic: "핫한 대화주제", + date: recentlyBookmarkedDate, + cards: recentlyBookmarkedCards, + linkTo: `${routePaths.CardCollection}?type=${LocationType.RECENT}`, + isLightMode: false, + }, + { + bannerImage: newBannerImages[1], + phrase: "가장 최근 업데이트 된", + topic: "새로운 대화주제", + date: recentlyUpdatedDate, + cards: recentlyUpdatedCards, + linkTo: `${routePaths.CardCollection}?type=${LocationType.UPDATE}`, + isLightMode: true, + }, + { + bannerImage: newBannerImages[2], + phrase: "여러분만의 톡톡 튀는", + topic: "대화주제를 얘기해주세요", + linkTo: externalLinks.TOPIC_FORM, + isLightMode: true, + isLast: true, + }, + ]; return ( <> - + - {bannerImage.map((img, index) => ( - - - - - + {newBanners.map((banner, idx) => ( + + ))} - {currentSlide + 1} / {bannerImage.length} + {currentSlide + 1} / {newBannerImages.length} + + {newBanners.map((banner, idx) => ( + + ))} + ); } diff --git a/src/@components/MainPage/Banner/style.ts b/src/@components/MainPage/Banner/style.ts index 37bce695..71d5b928 100644 --- a/src/@components/MainPage/Banner/style.ts +++ b/src/@components/MainPage/Banner/style.ts @@ -1,23 +1,19 @@ import styled from "styled-components"; -const BannerSlider = styled.section` +export const BannerSlider = styled.section` touch-action: pan-x; position: relative; - height: 21.6rem; + height: 30.2rem; & .slick-track { scroll-snap-type: x mandatory; } -`; -const ImageWrapper = styled.img` - width: 100%; - height: 21.6rem; - object-fit: cover; + cursor: pointer; `; -const ContentsPages = styled.div` +export const ContentsPages = styled.div` position: absolute; right: 0; bottom: 0; @@ -35,15 +31,29 @@ const ContentsPages = styled.div` z-index: 10; `; -const CurrentPage = styled.span` +export const CurrentPage = styled.span` ${({ theme }) => theme.newFonts.caption1}; - color: ${({ theme }) => theme.colors.white}; + color: ${({ theme }) => theme.newColors.white}; +`; + +export const PagingWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + height: 3.2rem; + + gap: 1.6rem; + + cursor: default; `; -const St = { - BannerSlider, - ImageWrapper, - ContentsPages, - CurrentPage, -}; -export default St; +export const PagingButton = styled.div<{ isselected: boolean }>` + width: 0.8rem; + height: 0.8rem; + + border-radius: 50%; + + background: ${({ theme, isselected }) => (isselected ? theme.newColors.green : theme.newColors.gray300)}; +`; diff --git a/src/@components/MainPage/Medley/MedleyCard/index.tsx b/src/@components/MainPage/Medley/MedleyCard/index.tsx index a41eb330..ec81b206 100644 --- a/src/@components/MainPage/Medley/MedleyCard/index.tsx +++ b/src/@components/MainPage/Medley/MedleyCard/index.tsx @@ -9,6 +9,8 @@ interface MedleyCardProps { canToggleModal: boolean; } +const STICKER_EDITOR_PICK = "피클 기획자가 추천하는"; + export default function MedleyCard(props: MedleyCardProps) { const { medleyCard, canToggleModal } = props; const { isModalOpen, toggleModal } = useModal(); @@ -23,7 +25,11 @@ export default function MedleyCard(props: MedleyCardProps) { toggleModal(); }}> - {medleyCard.sticker} + + {medleyCard.sticker} + {medleyCard.coverTitle} diff --git a/src/@components/MainPage/Medley/MedleyCard/style.ts b/src/@components/MainPage/Medley/MedleyCard/style.ts index a820ce39..294b79b1 100644 --- a/src/@components/MainPage/Medley/MedleyCard/style.ts +++ b/src/@components/MainPage/Medley/MedleyCard/style.ts @@ -18,13 +18,13 @@ const MedleyCard = styled.div<{ bgcolorId: string }>` background: ${({ bgcolorId }) => medleyGradation[bgcolorId]}; `; -const ContentTag = styled.p` +const ContentTag = styled.p<{ iseditorpick: boolean }>` padding: 0rem 0.4rem; margin-top: 2.4rem; ${({ theme }) => theme.newFonts.caption1} - color: ${({ theme }) => theme.newColors.gray200}; - background-color: ${({ theme }) => theme.newColors.darkblue}; + color: ${({ theme, iseditorpick }) => (iseditorpick ? theme.newColors.gray900 : theme.newColors.gray200)}; + background-color: ${({ theme, iseditorpick }) => (iseditorpick ? theme.newColors.green : theme.newColors.darkblue)}; `; const ContentTitle = styled.strong` diff --git a/src/@components/MainPage/PiickleMe/style.ts b/src/@components/MainPage/PiickleMe/style.ts index 749fb529..3e93ef64 100644 --- a/src/@components/MainPage/PiickleMe/style.ts +++ b/src/@components/MainPage/PiickleMe/style.ts @@ -75,7 +75,7 @@ const ContentWrapper = styled(Link)` const ContentText = styled.p` width: 28rem; // MEMO :: 말줄임표를 위한 값 - ${({ theme }) => theme.newFonts.body4}; + ${({ theme }) => theme.newFonts.body4} color: ${({ theme }) => theme.newColors.gray900}; overflow: hidden; diff --git a/src/@components/MainPage/TopicLink/index.tsx b/src/@components/MainPage/TopicLink/index.tsx new file mode 100644 index 00000000..494a00c4 --- /dev/null +++ b/src/@components/MainPage/TopicLink/index.tsx @@ -0,0 +1,14 @@ +import { externalLinks } from "../../../util/const/externalLinks"; +import * as St from "./style"; + +export default function TopicLink() { + return ( + + + 대화주제를 주세요 제발요:0 + 도움주러 가기 + + + + ); +} diff --git a/src/@components/MainPage/TopicLink/style.ts b/src/@components/MainPage/TopicLink/style.ts new file mode 100644 index 00000000..186baaf7 --- /dev/null +++ b/src/@components/MainPage/TopicLink/style.ts @@ -0,0 +1,53 @@ +import styled from "styled-components"; + +import { ImgTopicLinkBanner } from "../../../asset/image"; +import { OriginImgTopicLinkBanner } from "../../../asset/image/origin"; + +export const TopicLinkContainer = styled.section` + position: relative; + display: flex; + + height: 13.4rem; + margin: 0 0.8rem 0.8rem; + border-radius: 0.8rem; + + background-image: url(${ImgTopicLinkBanner}); + background-image: image-set(${ImgTopicLinkBanner} type("image/webp"), ${OriginImgTopicLinkBanner} type("image/png")); + background-size: cover; +`; + +export const TopicTitles = styled.span` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + align-self: flex-end; + + width: 100%; + margin: 0 1.2rem 1rem; + + z-index: 2; +`; + +export const Text = styled.h1` + ${({ theme }) => theme.newFonts.body3} + color: ${({ theme }) => theme.newColors.darkblue} +`; + +export const Link = styled.a` + ${({ theme }) => theme.newFonts.btn2} + color: ${({ theme }) => theme.newColors.gray900}; + text-decoration-line: underline; +`; + +export const Gradient = styled.div` + position: absolute; + bottom: 0; + + width: 100%; + height: 12rem; + border-radius: 0.8rem; + + background: linear-gradient(0, #ccd3d3 0%, rgba(204, 211, 211, 0) 100%); + z-index: 1; +`; diff --git a/src/@components/MainPage/hooks/useRecentlyUpdated.ts b/src/@components/MainPage/hooks/useRecentlyUpdated.ts new file mode 100644 index 00000000..3adaefb7 --- /dev/null +++ b/src/@components/MainPage/hooks/useRecentlyUpdated.ts @@ -0,0 +1,17 @@ +import useSWR from "swr"; + +import { realReq } from "../../../core/api/common/axios"; +import { PATH } from "../../../core/api/common/constants"; +import { RecentCardList } from "../../../types/cardCollection"; +import { PiickleSWRResponse } from "../../../types/remote/swr"; + +export function useRecentlyUpdated() { + const { data } = useSWR>(`${PATH.CARDS_}${PATH.CARDS_UPDATE}`, realReq.GET_SWR, { + suspense: true, + }); + + return { + recentlyUpdatedDate: data?.data.data.recentlyDate, + recentlyUpdatedCards: data?.data.data.cardResponseDtos, + }; +} diff --git a/src/@components/MainPage/index.tsx b/src/@components/MainPage/index.tsx index 06d938a3..552b231b 100644 --- a/src/@components/MainPage/index.tsx +++ b/src/@components/MainPage/index.tsx @@ -12,6 +12,7 @@ import MoodPiickle from "./MoodPiickle"; import PiickleMe from "./PiickleMe"; import StripBanner from "./StripBanner"; import { St } from "./style"; +import TopicLink from "./TopicLink"; //const UpdateModal = lazy(() => import("./UpdateModal")); @@ -22,7 +23,9 @@ export default function MainPage() {
- + + + @@ -42,6 +45,8 @@ export default function MainPage() { + +