diff --git a/src/app/showcase/page.tsx b/src/app/showcase/page.tsx index c99bd72a1..229fae121 100644 --- a/src/app/showcase/page.tsx +++ b/src/app/showcase/page.tsx @@ -17,7 +17,7 @@ const Showcase = () => { const [page, setPage] = useState(1) const [cardList, setCardList] = useState>([]) const [draggedCardList, setDraggedCardList] = useState([]) - const { isPc } = useMedia() + const { isPc, isTablet } = useMedia() const { isLogin } = useAuthStore() const axiosWithAuth = useAxiosWithAuth() const { data, isLoading, error } = useSWR>( @@ -85,7 +85,7 @@ const Showcase = () => { else if (isLoading && !cardList.length) message = '로딩중' else if (error) message = '에러 발생' - if (isPc) { + if (isPc && !isTablet) { return ( { ) } + return ( ) } diff --git a/src/app/showcase/panel/CardContainer.style.ts b/src/app/showcase/panel/CardContainer.style.ts index 3dab0fd59..4890bb0b2 100644 --- a/src/app/showcase/panel/CardContainer.style.ts +++ b/src/app/showcase/panel/CardContainer.style.ts @@ -1,4 +1,10 @@ import { SxProps } from '@mui/material' +import { Inter } from 'next/font/google' + +const inter = Inter({ + subsets: ['latin'], + display: 'swap', +}) export const cardContainerStyleBase: SxProps = { width: '100%', @@ -26,7 +32,7 @@ export const gnbContainerStyle: SxProps = { export const gnbTypographyStyle: SxProps = { color: 'text.normal', textAlign: 'center', - fontFamily: 'Inter', + fontFamily: `${inter.style.fontFamily} !important` as string, fontSize: '13px', fontStyle: 'normal', fontWeight: 700, diff --git a/src/app/showcase/panel/CardContainer.tsx b/src/app/showcase/panel/CardContainer.tsx index 469750cbc..db458edf6 100644 --- a/src/app/showcase/panel/CardContainer.tsx +++ b/src/app/showcase/panel/CardContainer.tsx @@ -1,36 +1,41 @@ 'use client' -import React from 'react' + +import React, { Dispatch, SetStateAction } from 'react' import useMedia from '@/hook/useMedia' -import { Stack, Typography } from '@mui/material' -import * as cardStyle from './ShowcaseCard.style' +import { IconButton, Stack, Typography } from '@mui/material' import * as containerStyle from './CardContainer.style' +import * as style from '../showcase.style' import CardStack from './CardStack' import { ICardData } from '@/app/showcase/panel/types' import { BetaIcon } from '@/components/BetaBadge' +import { ChevronLeft, ChevronRight } from '@/icons' const CardContainer = ({ cardList, removeCard, message, addCard, + addDisabled, + mutate, }: { cardList: Array removeCard: (recruit_id: number) => void message: string addCard?: () => void + addDisabled?: boolean + mutate: Dispatch> }) => { - const { isPc } = useMedia() + const { isPc, isTablet } = useMedia() return ( {!message ? ( @@ -57,11 +64,39 @@ const CardContainer = ({ cardList={cardList} removeCard={removeCard} addCard={addCard} + mutate={mutate} /> ) : ( {message} )} + + <> + + + + removeCard(cardList[cardList.length - 1]?.id)} + disabled={cardList.length === 1} + > + + + + ) } diff --git a/src/app/showcase/panel/CardStack.tsx b/src/app/showcase/panel/CardStack.tsx index 4e85477e9..af6c81b07 100644 --- a/src/app/showcase/panel/CardStack.tsx +++ b/src/app/showcase/panel/CardStack.tsx @@ -1,10 +1,9 @@ 'use client' import { Box } from '@mui/material' -import React, { useState } from 'react' +import React, { Dispatch, SetStateAction, useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' import * as style from './ShowcaseCard.style' -import useMedia from '@/hook/useMedia' import { ICardData } from '@/app/showcase/panel/types' import { ShowcaseCard } from './ShowcaseCard' @@ -19,13 +18,14 @@ const CardStack = ({ cardList, removeCard, addCard, + mutate, }: { cardList: Array removeCard: (recruit_id: number) => void addCard?: () => void + mutate: Dispatch> }) => { const [dragged, setDragged] = useState(false) - const { isPc } = useMedia() const checkDragDirection = (x: number, y: number) => { return y < 0 ? ESwipeDirection.up : ESwipeDirection.down @@ -51,10 +51,7 @@ const CardStack = ({ return ( <> - + 1 ? 1 : 0, @@ -65,7 +62,7 @@ const CardStack = ({ > @@ -80,7 +77,7 @@ const CardStack = ({ > @@ -122,9 +119,9 @@ const CardStack = ({ > ) diff --git a/src/app/showcase/panel/PhoneFrame.tsx b/src/app/showcase/panel/PhoneFrame.tsx index 9164633ad..7d8f7987c 100644 --- a/src/app/showcase/panel/PhoneFrame.tsx +++ b/src/app/showcase/panel/PhoneFrame.tsx @@ -6,11 +6,13 @@ const PhoneFrame = ({ imageUrl }: { imageUrl: string | undefined }) => { { src={imageUrl} sx={{ width: '16rem', - height: '40rem', + height: '20rem', objectFit: 'contain', }} /> @@ -32,8 +34,9 @@ const PhoneFrame = ({ imageUrl }: { imageUrl: string | undefined }) => { (null) const [currentCardWidth, setCurrentCardWidth] = useState(0) - const [favorite, setFavorite] = useState(isFavorite) - const [likeNum, setLikeNum] = useState(like) - const [isLiked, setIsLiked] = useState(liked) + const [favorite, setFavorite] = useState(false) + const [likeNum, setLikeNum] = useState(0) + const [isLiked, setIsLiked] = useState(false) const axiosWithAuth = useAxiosWithAuth() + useEffect(() => { + setFavorite(isFavorite) + setLikeNum(like) + setIsLiked(liked) + }, [like, liked, isFavorite]) + useEffect(() => { if (ref.current) { setCurrentCardWidth(ref.current.clientWidth) @@ -59,26 +66,58 @@ function PostCard({ ) .then((res) => { if (res.status === 200) { - setFavorite(!favorite) + mutate((prev) => { + return prev.map((card) => { + if (card.id === postId) { + return { + ...card, + favorite: !favorite, + } + } + return card + }) + }) } }) - }, [setFavorite, axiosWithAuth]) + .catch(() => {}) + }, [favorite, setFavorite, mutate]) const clickLike = useCallback(() => { axiosWithAuth .post(`${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase/like/${postId}`) .then((res) => { if (res.status === 200) { - if (liked === false) { - setIsLiked(true) - setLikeNum(likeNum + 1) + if (isLiked === false) { + mutate((prev) => { + return prev.map((card) => { + if (card.id === postId) { + return { + ...card, + like: likeNum + 1, + liked: !isLiked, + } + } + return card + }) + }) } else { - setIsLiked(false) - setLikeNum(likeNum - 1) + mutate((prev) => { + return prev.map((card) => { + if (card.id === postId) { + return { + ...card, + like: likeNum - 1, + liked: !isLiked, + } + } + return card + }) + }) } } }) - }, [setIsLiked, setLikeNum, axiosWithAuth]) + .catch(() => {}) + }, [isLiked, likeNum, setLikeNum, setIsLiked]) return ( - + + + + { + const [lineCount, setLineCount] = useState({ + title: 1, + content: 1, + }) const router = useRouter() - const { isPc } = useMedia() + const card = useRef(null) const [currentPageUrl, setCurrentPageUrl] = useState('') - //window is not defined 에러 방지 + useEffect(() => { + setLineCount({ + title: getLineCount(46, 22.5, 2), + content: getLineCount(191, 18, 8), + }) + }, [card]) + useEffect(() => { setCurrentPageUrl(window.location.href) + + const handleResize = () => { + if (card.current) { + setLineCount({ + title: getLineCount(46, 22.5, 2), + content: getLineCount(191, 18, 8), + }) + } + } + + window.addEventListener('resize', handleResize) + return () => { + window.removeEventListener('resize', handleResize) + } }, []) - const getLineCount = (originHeight: number, lineHeight: number) => { - const lineCount = Math.floor((cardWidth * originHeight) / 328 / lineHeight) - return lineCount ? lineCount : 1 + const getLineCount = ( + originHeight: number, + lineHeight: number, + maxLine: number, + ) => { + const removeCount = card.current?.clientHeight ?? 0 < 441 ? 2 : 1 + const lineCount = Math.floor( + ((card.current?.clientHeight ?? 0) * originHeight) / lineHeight / 441, + ) + if (lineCount > maxLine) return maxLine + else if (lineCount < 1 + removeCount) return 1 + else return lineCount - removeCount } const handleSeeAll = (e: React.MouseEvent) => { @@ -71,6 +109,7 @@ const ShowcaseCardBack = ({ backfaceVisibility: 'hidden', padding: '1rem', }} + ref={card} > {content ? ( @@ -118,10 +157,7 @@ const ShowcaseCardBack = ({ color={'text.normal'} sx={{ ...style.cardTitleStyleBase, - height: isPc ? '46px' : getLineCount(46, 22.5) * 22.5, - WebkitLineClamp: isPc - ? 2 - : getLineCount(46, 22.5) /* 라인수 */, + WebkitLineClamp: lineCount.title, }} > {title} @@ -137,30 +173,38 @@ const ShowcaseCardBack = ({ }} onClick={onClick} > - - {content.split('\n').map((line) => { - return ( - <> - {line} -
- - ) - })} -
+ + h1:first-of-type': { + marginTop: 0, + }, + '.toastui-editor-contents h1': { + paddingBottom: 0, + }, + '.toastui-editor-contents h2': { + paddingBottom: 0, + }, + }} + /> +
@@ -199,38 +243,20 @@ const ShowcaseCard = ({ data, dragged, setDragged, - sx, + mutate, }: { data: ICardData sx?: SxProps dragged: boolean setDragged: React.Dispatch> + mutate: Dispatch> }) => { const [isFlipped, setIsFlipped] = useState(false) - const [cardWidth, setCardWidth] = useState(0) const [currentDomain, setCurrentDomain] = useState('') - const { isPc } = useMedia() useEffect(() => { // 현재 도메인 설정 setCurrentDomain(window.location.origin) - - // 카드 너비 설정 - setCardWidth( - isPc ? window.innerWidth * 0.9 : (window.innerHeight * 0.8 * 328) / 800, - ) - const handleResize = () => { - const newCardWidth = isPc - ? window.innerWidth * 0.9 - : (window.innerHeight * 0.8 * 328) / 800 - setCardWidth(newCardWidth) - } - - window.addEventListener('resize', handleResize) - - return () => { - window.removeEventListener('resize', handleResize) - } }, []) const handleMouseUp = (e: React.MouseEvent) => { @@ -262,23 +288,22 @@ const ShowcaseCard = ({ like={data.like} liked={data.liked} sx={{ - ...sx, + ...style.cardStyleBase, backfaceVisibility: 'hidden', transform: 'translate(-50%, 0)', - width: isPc ? '90%' : '90vw', }} onClick={handleMouseUp} + mutate={mutate} /> diff --git a/src/states/useShowcaseCardStore.tsx b/src/states/useShowcaseCardStore.tsx new file mode 100644 index 000000000..a0b2269ff --- /dev/null +++ b/src/states/useShowcaseCardStore.tsx @@ -0,0 +1,60 @@ +import { ICardData } from '@/app/showcase/panel/types' +import { create } from 'zustand' + +import axios from 'axios' + +interface IShowcaseStore { + showcases: ICardData[] + draggedCardList: ICardData[] + page: number + index: number + setShowcases: (showcases: ICardData[]) => void + setDraggedCardList: (draggedCardList: ICardData[]) => void + getShowcases: (page: number) => void + removeShowcase: () => void + nextShowcase: () => void + prevShowcase: () => void + resetShowcases: () => void +} + +const useShowcaseStore = create((set, get) => { + return { + showcases: [], + draggedCardList: [], + page: 1, + index: 0, + setShowcases: (showcases: ICardData[]) => { + set(() => ({ showcases: showcases })) + }, + setDraggedCardList: (draggedCardList: ICardData[]) => { + set(() => ({ draggedCardList: draggedCardList })) + }, + + getShowcases: (page: number) => { + axios + .get( + `${process.env.NEXT_PUBLIC_CSR_API}/api/v1/showcase?page=${page}&pageSize=10`, + ) + .then((res) => { + const oldShowcases = get().showcases + const newShowcases = [...oldShowcases] + .reverse() + .concat(res.data.content) + set(() => ({ showcases: newShowcases, page: page })) + }) + }, + + removeShowcase: () => { + const oldShowcases = get().showcases + const oldLength = oldShowcases.length + if (oldShowcases.length > 1) { + oldShowcases.push(oldShowcases[oldLength - 1]) + } + }, + nextShowcase: () => {}, + prevShowcase: () => {}, + resetShowcases: () => {}, + } +}) + +export default useShowcaseStore diff --git a/src/types/IPostCard.ts b/src/types/IPostCard.ts index 643550640..136faf38f 100644 --- a/src/types/IPostCard.ts +++ b/src/types/IPostCard.ts @@ -1,6 +1,7 @@ import { SxProps } from '@mui/material' import { ITag } from './IPostDetail' -import { IShowcaseTag } from '@/app/showcase/panel/types' +import { ICardData, IShowcaseTag } from '@/app/showcase/panel/types' +import { Dispatch, SetStateAction } from 'react' export interface IPostCard { authorImage: string // 글 작성자 프로필 이미지 @@ -36,6 +37,7 @@ export interface IPostCardShowcase { liked: boolean sx?: SxProps onClick?: (e: React.MouseEvent) => void + mutate: Dispatch> } export interface IHitchhikingCardBack {