diff --git a/client/src/components/icons/svgIcons.tsx b/client/src/components/icons/svgIcons.tsx index 603aa7295..1c766aa6a 100644 --- a/client/src/components/icons/svgIcons.tsx +++ b/client/src/components/icons/svgIcons.tsx @@ -2488,3 +2488,18 @@ export const RightIcon = ({ width, ...props }: SVGProps) => ( ); + +export const NoImageIcon = ({ width, ...props }: SVGProps) => ( + + + +); diff --git a/client/src/components/roadmapDetailPage/extraInfo/ExtraInfo.styles.ts b/client/src/components/roadmapDetailPage/extraInfo/ExtraInfo.styles.ts new file mode 100644 index 000000000..98aa0449c --- /dev/null +++ b/client/src/components/roadmapDetailPage/extraInfo/ExtraInfo.styles.ts @@ -0,0 +1,48 @@ +import styled from 'styled-components'; +import media from '@styles/media'; + +export const ExtraInfo = styled.div` + ${({ theme }) => theme.fonts.description5}; + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: space-between; + + width: 30%; + height: 55rem; + + ${media.mobile` + display: none; + `} +`; + +export const RoadmapMetadata = styled.div` + display: flex; + flex-direction: column; + + & > div:not(:last-child) { + margin-bottom: 3rem; + } +`; + +export const Category = styled.div` + display: flex; + align-items: center; + justify-content: flex-end; +`; + +export const Difficulty = styled.div` + text-align: end; +`; + +export const RecommendedRoadmapPeriod = styled.div` + text-align: end; +`; + +export const Tags = styled.div` + color: ${({ theme }) => theme.colors.main_dark}; + + & > div { + text-align: end; + } +`; diff --git a/client/src/components/roadmapDetailPage/extraInfo/ExtraInfo.tsx b/client/src/components/roadmapDetailPage/extraInfo/ExtraInfo.tsx new file mode 100644 index 000000000..4959bca11 --- /dev/null +++ b/client/src/components/roadmapDetailPage/extraInfo/ExtraInfo.tsx @@ -0,0 +1,33 @@ +import { RoadmapDetailType } from '@myTypes/roadmap/internal'; +import * as S from './ExtraInfo.styles'; +import SVGIcon from '@components/icons/SVGIcon'; +import { CategoriesInfo } from '@constants/roadmap/category'; + +type ExtraInfoProps = { + roadmapInfo: RoadmapDetailType; +}; + +const ExtraInfo = ({ roadmapInfo }: ExtraInfoProps) => { + return ( + +
Created by {roadmapInfo.creator.name}
+ + + 카테고리: {roadmapInfo.category.name} + + + 난이도: {roadmapInfo.difficulty} + + 예상 소요시간: {roadmapInfo.recommendedRoadmapPeriod}일 + + + + {roadmapInfo.tags.map((tag) => ( +
#{tag.name}
+ ))} +
+
+ ); +}; + +export default ExtraInfo; diff --git a/client/src/components/roadmapDetailPage/introduction/Introduction.styles.ts b/client/src/components/roadmapDetailPage/introduction/Introduction.styles.ts new file mode 100644 index 000000000..bd5f1e6bf --- /dev/null +++ b/client/src/components/roadmapDetailPage/introduction/Introduction.styles.ts @@ -0,0 +1,47 @@ +import media from '@styles/media'; +import styled from 'styled-components'; + +export const IntroductionWrapper = styled.div` + width: 70%; + + ${media.mobile` + width:100%; + `} +`; + +export const Introduction = styled.div<{ isExpanded: boolean }>` + ${({ theme }) => theme.fonts.description5}; + overflow: hidden; + max-height: ${({ isExpanded }) => (isExpanded ? 'auto' : '55rem')}; + + & > p:not(:last-child) { + margin-bottom: 2rem; + } + + & div { + ${({ theme }) => theme.fonts.h1}; + margin-bottom: 0.5rem; + color: ${({ theme }) => theme.colors.main_dark}; + } +`; + +export const LineShadow = styled.div` + position: relative; + width: 100%; + height: 0.2rem; + box-shadow: 0 -4px 6px rgba(0, 0, 0, 1); +`; + +export const ReadMoreButton = styled.button` + position: relative; + top: calc(-2rem - 4px); + left: 50%; + transform: translateX(-5rem); + + width: 10rem; + height: 4rem; + + background-color: ${({ theme }) => theme.colors.white}; + border-radius: 8px; + box-shadow: ${({ theme }) => theme.shadows.main}; +`; diff --git a/client/src/components/roadmapDetailPage/introduction/Introduction.tsx b/client/src/components/roadmapDetailPage/introduction/Introduction.tsx new file mode 100644 index 000000000..3069efacf --- /dev/null +++ b/client/src/components/roadmapDetailPage/introduction/Introduction.tsx @@ -0,0 +1,51 @@ +import { RoadmapDetailType } from '@myTypes/roadmap/internal'; +import * as S from './Introduction.styles'; +import { useEffect, useRef, useState } from 'react'; + +type IntroductionProps = { + roadmapInfo: RoadmapDetailType; +}; + +const Introduction = ({ roadmapInfo }: IntroductionProps) => { + const [isExpanded, setIsExpanded] = useState(false); + const [showMoreButton, setShowMoreButton] = useState(false); + const introRef = useRef(null); + + useEffect(() => { + if (!introRef.current) return; + + const element = introRef.current; + if (element.scrollHeight > element.clientHeight) { + setShowMoreButton(true); + } + }, []); + + const toggleExpand = () => { + setIsExpanded((prev) => !prev); + }; + + return ( + + +

+

설명
+ {roadmapInfo.introduction} +

+

+

본문
+ {roadmapInfo.content.content === '' + ? '로드맵에 대한 설명이 없어요🥲' + : roadmapInfo.content.content} +

+
+ {showMoreButton && !isExpanded && ( + <> + + 더 보기 + + )} +
+ ); +}; + +export default Introduction; diff --git a/client/src/components/roadmapDetailPage/nodeContent/NodeContent.styles.ts b/client/src/components/roadmapDetailPage/nodeContent/NodeContent.styles.ts new file mode 100644 index 000000000..695e6a26b --- /dev/null +++ b/client/src/components/roadmapDetailPage/nodeContent/NodeContent.styles.ts @@ -0,0 +1,90 @@ +import styled from 'styled-components'; +import media from '@styles/media'; + +export const SliderContent = styled.div` + display: flex; + aspect-ratio: 5 / 3.5; + background-color: ${({ theme }) => theme.colors.gray100}; + border-radius: 8px; + + ${media.mobile` + aspect-ratio: 0; + `} +`; + +export const LeftContent = styled.div` + width: 45%; + + ${media.mobile` + display: none; + `} +`; + +export const NodeImg = styled.img` + width: 100%; + height: 100%; + padding: 1.5rem; + object-fit: cover; +`; + +export const NoImg = styled.div` + ${({ theme }) => theme.fonts.title_large} + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + width: 100%; + height: 100%; +`; + +export const Separator = styled.div` + display: flex; + flex-direction: column; + width: 0.2rem; + height: 100%; + + & > div { + height: 50%; + } + + & > div:last-child { + background-color: black; + } +`; + +export const RightContent = styled.div` + ${({ theme }) => theme.fonts.h1} + overflow: scroll; + width: 55%; + padding: 1.5rem; + padding-top: 3rem; + + ${media.mobile` + width: 100%; + height: 60rem; + padding-top: 1.5rem; + `} +`; + +export const ContentTitle = styled.div` + ${({ theme }) => theme.fonts.title_large} + display: flex; + align-items: center; + margin-bottom: 1rem; +`; + +export const Step = styled.div` + ${({ theme }) => theme.fonts.h2} + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + + width: 3rem; + height: 3rem; + margin-right: 0.5rem; + + border: 0.3rem solid black; + border-radius: 50%; +`; diff --git a/client/src/components/roadmapDetailPage/nodeContent/NodeContent.tsx b/client/src/components/roadmapDetailPage/nodeContent/NodeContent.tsx new file mode 100644 index 000000000..f2ffb7e60 --- /dev/null +++ b/client/src/components/roadmapDetailPage/nodeContent/NodeContent.tsx @@ -0,0 +1,38 @@ +import type { NodeType } from '@myTypes/roadmap/internal'; +import * as S from './NodeContent.styles'; +import SVGIcon from '@components/icons/SVGIcon'; + +type NodeContentProps = { + node: NodeType; + index: number; +}; + +const NodeContent = ({ node, index }: NodeContentProps) => { + return ( + + + {node.imageUrls[0] ? ( + + ) : ( + + +
No Image
+
+ )} +
+ +
+
+ + + + {index + 1} +

{node.title}

+
+ {node.description} +
+ + ); +}; + +export default NodeContent; diff --git a/client/src/components/roadmapDetailPage/roadmapDetail/RoadmapDetail.styles.ts b/client/src/components/roadmapDetailPage/roadmapDetail/RoadmapDetail.styles.ts index 4cafaf5cf..93c0b17ce 100644 --- a/client/src/components/roadmapDetailPage/roadmapDetail/RoadmapDetail.styles.ts +++ b/client/src/components/roadmapDetailPage/roadmapDetail/RoadmapDetail.styles.ts @@ -1,43 +1,52 @@ -import media from '@styles/media'; import styled from 'styled-components'; export const RoadmapDetail = styled.div` - position: relative; - margin: 4rem 0; - padding: 0 2rem; - white-space: pre-line; - - ${media.mobile` - flex-direction: column; - align-items: center; - `} + padding: 2rem 0 4rem 0; `; -export const RoadmapBody = styled.p` - ${({ theme }) => theme.fonts.button1} - width: 50%; - padding: 4rem 4rem; - height: 35rem; +export const RoadmapInfo = styled.div``; - overflow: scroll; +export const Title = styled.div` + ${({ theme }) => theme.fonts.title_large}; + margin-bottom: 2rem; + color: ${({ theme }) => theme.colors.main_dark}; +`; - border-radius: 18px; - box-shadow: ${({ theme }) => theme.shadows.box}; +export const Description = styled.div` + display: flex; +`; - color: ${({ theme }) => theme.colors.gray300}; +export const ButtonsWrapper = styled.div` + position: relative; + width: 100%; +`; - ${media.mobile` - padding: 4rem 4rem; - `} +export const Buttons = styled.div` + bottom: 3rem; - & > strong { - ${({ theme }) => theme.fonts.h1}; - margin-bottom: 4rem; - color: ${({ theme }) => theme.colors.main_dark}; + display: flex; + align-items: center; + justify-content: space-around; + + margin: 2rem 0; + + background-color: ${({ theme }) => theme.colors.main_dark}; + border-radius: 8px; + + & > div { + width: 0.2rem; + height: 5.5rem; + background-color: ${({ theme }) => theme.colors.white}; } `; -export const PageOnTop = styled.div` - display: flex; - justify-content: space-around; +export const Button = styled.button` + ${({ theme }) => theme.fonts.nav_text} + width: 50%; + height: 5.5rem; + + color: ${({ theme }) => theme.colors.white}; + + background-color: ${({ theme }) => theme.colors.main_dark}; + border-radius: 8px; `; diff --git a/client/src/components/roadmapDetailPage/roadmapDetail/RoadmapDetail.tsx b/client/src/components/roadmapDetailPage/roadmapDetail/RoadmapDetail.tsx index 94da78baa..912425ee5 100644 --- a/client/src/components/roadmapDetailPage/roadmapDetail/RoadmapDetail.tsx +++ b/client/src/components/roadmapDetailPage/roadmapDetail/RoadmapDetail.tsx @@ -1,36 +1,41 @@ -import RoadmapItem from '../../_common/roadmapItem/RoadmapItem'; -import Button from '../../_common/button/Button'; import * as S from './RoadmapDetail.styles'; -import useValidParams from '@/hooks/_common/useValidParams'; +import useValidParams from '@hooks/_common/useValidParams'; import { useNavigate } from 'react-router-dom'; -import { useRoadmapDetail } from '@/hooks/queries/roadmap'; -import RoadmapNodeList from '../roadmapNodeList/RoadmapNodeList'; +import { useRoadmapDetail } from '@hooks/queries/roadmap'; + +import Slider from '@components/_common/slider/Slider'; +import NodeContent from '../nodeContent/NodeContent'; +import ExtraInfo from '../extraInfo/ExtraInfo'; +import Introduction from '../introduction/Introduction'; const RoadmapDetail = () => { const { id: roadmapId } = useValidParams<{ id: string }>(); const navigate = useNavigate(); const { roadmapInfo } = useRoadmapDetail(Number(roadmapId)); - const moveToGoalRoomCreatePage = () => { - navigate(`/roadmap/${roadmapId}/goalroom-create`); - }; - return ( - - - - 로드맵 설명
- {roadmapInfo.content.content === '' - ? '로드맵에 대한 설명이 없어요🥲' - : roadmapInfo.content.content} -
-
- - + + {roadmapInfo.roadmapTitle} + + + + + + + navigate(`/roadmap/${roadmapId}/goalroom-create`)}> + 모임 생성하기 + +
+ navigate(`/roadmap/${roadmapId}/goalroom-list`)}> + 진행중인 모임보기 + + + + {roadmapInfo.content.nodes.map((node, index) => ( + + ))} + ); };