diff --git a/public/icons/arrow_left.svg b/public/icons/arrow_left.svg new file mode 100644 index 0000000..289e04e --- /dev/null +++ b/public/icons/arrow_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/arrow_right.svg b/public/icons/arrow_right.svg new file mode 100644 index 0000000..583993b --- /dev/null +++ b/public/icons/arrow_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/back_arrow_left.svg b/public/icons/back_arrow_left.svg new file mode 100644 index 0000000..7892372 --- /dev/null +++ b/public/icons/back_arrow_left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/capacity.svg b/public/icons/capacity.svg new file mode 100644 index 0000000..898c879 --- /dev/null +++ b/public/icons/capacity.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/icons/class_arrow_right.svg b/public/icons/class_arrow_right.svg new file mode 100644 index 0000000..7e324bf --- /dev/null +++ b/public/icons/class_arrow_right.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/current_location.svg b/public/icons/current_location.svg new file mode 100644 index 0000000..2697450 --- /dev/null +++ b/public/icons/current_location.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/icons/division.svg b/public/icons/division.svg new file mode 100644 index 0000000..18ae550 --- /dev/null +++ b/public/icons/division.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/heart.svg b/public/icons/heart.svg index 11a9ed9..8c3ee07 100755 --- a/public/icons/heart.svg +++ b/public/icons/heart.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/public/icons/heart_default.svg b/public/icons/heart_default.svg old mode 100755 new mode 100644 index 12170ea..4329772 --- a/public/icons/heart_default.svg +++ b/public/icons/heart_default.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icons/hosted_by.svg b/public/icons/hosted_by.svg old mode 100755 new mode 100644 index 3d307f1..ee5d3f4 --- a/public/icons/hosted_by.svg +++ b/public/icons/hosted_by.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/public/icons/like.svg b/public/icons/like.svg old mode 100755 new mode 100644 index 12170ea..0676612 --- a/public/icons/like.svg +++ b/public/icons/like.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icons/location.svg b/public/icons/location.svg old mode 100755 new mode 100644 index cdede15..7756572 --- a/public/icons/location.svg +++ b/public/icons/location.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/public/icons/map_pin.svg b/public/icons/map_pin.svg old mode 100755 new mode 100644 index 0525d82..0f822fb --- a/public/icons/map_pin.svg +++ b/public/icons/map_pin.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/public/icons/period.svg b/public/icons/period.svg new file mode 100644 index 0000000..1e1980c --- /dev/null +++ b/public/icons/period.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/icons/price.svg b/public/icons/price.svg old mode 100755 new mode 100644 index 9466a70..bcb2722 --- a/public/icons/price.svg +++ b/public/icons/price.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/public/icons/sijak_logo.svg b/public/icons/sijak_logo.svg new file mode 100644 index 0000000..e2779da --- /dev/null +++ b/public/icons/sijak_logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/time.svg b/public/icons/time.svg old mode 100755 new mode 100644 index 149180d..a0f5a26 --- a/public/icons/time.svg +++ b/public/icons/time.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/public/icons/user_default.svg b/public/icons/user_default.svg old mode 100755 new mode 100644 index fda8380..b6e8463 --- a/public/icons/user_default.svg +++ b/public/icons/user_default.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/public/icons/x_circle.svg b/public/icons/x_circle.svg new file mode 100644 index 0000000..433c5b7 --- /dev/null +++ b/public/icons/x_circle.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/images/kakao_tooltip_arrow.png b/public/images/kakao_tooltip_arrow.png new file mode 100644 index 0000000..04cc712 Binary files /dev/null and b/public/images/kakao_tooltip_arrow.png differ diff --git a/public/images/main_banner_1.png b/public/images/main_banner_1.png new file mode 100644 index 0000000..c83b906 Binary files /dev/null and b/public/images/main_banner_1.png differ diff --git a/public/images/main_banner_2.png b/public/images/main_banner_2.png new file mode 100644 index 0000000..0f5bde3 Binary files /dev/null and b/public/images/main_banner_2.png differ diff --git a/public/images/main_banner_3.png b/public/images/main_banner_3.png new file mode 100644 index 0000000..0a5dca1 Binary files /dev/null and b/public/images/main_banner_3.png differ diff --git a/public/images/marker_icon.png b/public/images/marker_icon.png old mode 100755 new mode 100644 index 3fef606..e54c554 Binary files a/public/images/marker_icon.png and b/public/images/marker_icon.png differ diff --git a/public/images/senior.png b/public/images/senior.png deleted file mode 100755 index 2bd7f2e..0000000 Binary files a/public/images/senior.png and /dev/null differ diff --git a/public/images/sijak_footer_logo.png b/public/images/sijak_footer_logo.png new file mode 100644 index 0000000..6046abf Binary files /dev/null and b/public/images/sijak_footer_logo.png differ diff --git a/public/images/sijak_happy.png b/public/images/sijak_happy.png deleted file mode 100755 index f0b8f58..0000000 Binary files a/public/images/sijak_happy.png and /dev/null differ diff --git a/public/images/sijak_logo.png b/public/images/sijak_logo.png deleted file mode 100755 index a1b2a74..0000000 Binary files a/public/images/sijak_logo.png and /dev/null differ diff --git a/public/images/sijak_position.png b/public/images/sijak_position.png deleted file mode 100755 index 521b9a2..0000000 Binary files a/public/images/sijak_position.png and /dev/null differ diff --git a/src/app/class/[id]/page.tsx b/src/app/class/[id]/page.tsx index f8c4916..967d987 100755 --- a/src/app/class/[id]/page.tsx +++ b/src/app/class/[id]/page.tsx @@ -9,6 +9,9 @@ import { } from "@/entities/lecture/ui"; import { useEffect, useState } from "react"; +import Image from "next/image"; +import LectureMinimap from "@/entities/lecture/ui/LectureSummary/LectureMinimap/LectureMinimap"; +import Link from "next/link"; import { useGeoLocation } from "@/shared/lib/useGeolocation"; import useLectureInfo from "@/entities/lecture/api/useLectureInfo"; import { useParams } from "next/navigation"; @@ -18,6 +21,7 @@ export const runtime = "edge"; const LectureInfoPage = () => { const [lectureInfo, setLectureInfo] = useState(); const [user, setUser] = useState(); + const [windowInnerWidth, setWindowInnerWidth] = useState(); const { id } = useParams(); @@ -26,6 +30,20 @@ const LectureInfoPage = () => { const geolocation = useGeoLocation(); + const handleResize = () => { + const width = window.innerWidth; + if (width >= 1440) { + // desktop + setWindowInnerWidth("desktop"); + } else if (width >= 768) { + // tablet + setWindowInnerWidth("tablet"); + } else { + // mobile + setWindowInnerWidth("mobile"); + } + }; + useEffect(() => { if (user) { getLectureInfo.mutate( @@ -63,12 +81,82 @@ const LectureInfoPage = () => { } }, [geolocation.curLocation]); + useEffect(() => { + handleResize(); // 초기 렌더링 시 크기 설정 + window.addEventListener("resize", handleResize); // resize 이벤트 리스너 추가 + + return () => { + window.removeEventListener("resize", handleResize); // 컴포넌트 언마운트 시 리스너 제거 + }; + }, []); + + const renderSummary = () => { + if (windowInnerWidth === "desktop") { + return renderDesktopSummary(); + } + if (windowInnerWidth === "tablet") { + return renderTabletSummary(); + } + if (windowInnerWidth === "mobile") { + return renderMobileSummary(); + } + }; + + const renderDesktopSummary = () => { + return ( +
+
+ +
+ + +
+
+
+ ); + }; + + const renderTabletSummary = () => { + return ( +
+
+
+ + +
+
+ +
+
+
+ ); + }; + + const renderMobileSummary = () => { + return ( +
+
+ + + +
+
+ ); + }; + return ( -
-
- - +
+
+ + back +
+ {renderSummary()}
diff --git a/src/app/entire/page.tsx b/src/app/entire/page.tsx index b785863..3d69c81 100755 --- a/src/app/entire/page.tsx +++ b/src/app/entire/page.tsx @@ -8,9 +8,10 @@ import { import { LectureList, SkeletonCard } from "@/entities/lecture/ui"; import { useEffect, useState } from "react"; +import { BackToPrevious } from "@/shared/ui"; +import Image from "next/image"; import { useGeoLocation } from "@/shared/lib/useGeolocation"; import useLectureList from "@/entities/lecture/api/useLectureList"; -import useLoginedUserStore from "@/shared/store/user"; const EntirePage = () => { const [lectureListData, setLectureListData] = useState(); @@ -81,24 +82,59 @@ const EntirePage = () => { } if (lectureListData && lectureListData.length > 0) { - return ; + return ( + + ); } - return
클래스가 존재하지 않습니다
; + return ( +
+
+ x_circle +
+
+
+
+ 클래스가 +
+
+ 존재하지 않습니다. +
+
+
+ 이용에 불편을 드려 죄송합니다. +
+
+
+ ); }; // TODO: 무한 스크롤 return ( -
-
+
+
+ +
+
-
전체 클래스
-
한번에 보기
+
+ 전체 클래스 +
+
+ 한번에 보기 +
-
{renderEntireCardContent()}
+
+ {renderEntireCardContent()} +
); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b6b45dd..ecfbf47 100755 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -9,8 +9,9 @@ import localFont from "next/font/local"; const pretendard = localFont({ src: "./fonts/PretendardVariable.woff2", + display: "swap", + weight: "45 920", variable: "--font-pretendard", - weight: "100 900", }); export const metadata: Metadata = { @@ -32,15 +33,13 @@ export default function RootLayout({ src={`https://oapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_NAVER_API_CLIENT_ID}&submodules=geocoder`} /> - +
-
+
{children}
diff --git a/src/app/like/page.tsx b/src/app/like/page.tsx index 413a067..937ee75 100755 --- a/src/app/like/page.tsx +++ b/src/app/like/page.tsx @@ -3,7 +3,9 @@ import { LectureList, SkeletonCard } from "@/entities/lecture/ui"; import { useEffect, useState } from "react"; +import { BackToPrevious } from "@/shared/ui"; import { HeartsLectureListResDataInfo } from "@/features/like/model/like"; +import Image from "next/image"; import { LectureSize } from "@/entities/lecture/model/lecture"; import useLikeLectureList from "@/features/like/api/useLikeLectureList"; @@ -40,24 +42,57 @@ const LikePage = () => { } if (lectureListData && lectureListData.length > 0) { - return ; + return ( + + ); } - return
클래스가 존재하지 않습니다
; + return ( +
+
+ x_circle +
+
+
+
+ 클래스가 +
+
+ 존재하지 않습니다. +
+
+
+ 이용에 불편을 드려 죄송합니다. +
+
+
+ ); }; return ( -
+
+
+ +
-
-
+
+
내가 찜한 클래스
-
한번에 보기
+
+ 한번에 보기 +
-
{renderLikeCardContent()}
+
+ {renderLikeCardContent()} +
); diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 5ecd80e..4ce9f1d 100755 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { Button, UnifiedTooltip } from "@/shared/ui"; +import { Button, LinkArrowLeft, UnifiedTooltip } from "@/shared/ui"; import Image from "next/image"; @@ -16,7 +16,7 @@ const LoginPage = () => { const triggerItem = () => { return ( +
+
+ {isLoading && } + {lectureListData && ( + + )}
-
- +
+
+
+ 내 위치에서 +
+
+
+ 1km 이내 +
+
+ 에 이런 클래스가 있어요! +
+
+
+
{renderHomeLectureList()}
- {isLoading && } - {lectureListData && ( - - )} -
-
- 가장 가까운 순으로 클래스 정보를 보여드릴게요! + +
+
+
+
+ 시:작 PICK +
+
+ 클래스 📌 +
+
+
+ 조회 수 많은 추천 클래스를 소개할게요! +
-
{renderColLectureList()}
-
-
-
-
-
시:작 PICK
-
클래스 📌
+
{renderPickLectureList()}
-
{renderRowLectureList()}
); diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index ba9d8bf..b361f20 100755 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -86,10 +86,23 @@ const SignUpPage = () => { }; return ( -
+
+
+
+ 닉네임을 적어주세요! +
+
+
+ 시ː작에서 사용할 닉네임을 적어주세요. +
+
+ 닉네임은 나중에 수정할 수 있어요. +
+
+
{ )} />
diff --git a/src/app/user/[id]/page.tsx b/src/app/user/[id]/page.tsx index b2829d0..9490b45 100755 --- a/src/app/user/[id]/page.tsx +++ b/src/app/user/[id]/page.tsx @@ -1,19 +1,19 @@ "use client"; -import { Button, InputLabel, UnifiedDialog } from "@/shared/ui"; +import { BackToPrevious, Button, InputLabel, UnifiedDialog } from "@/shared/ui"; import { Controller, SubmitHandler, useForm } from "react-hook-form"; import { LoginUserInfo, PatchUserAddress } from "@/entities/user/model/user"; -import { deleteCookie, getCookie } from "cookies-next"; import { useEffect, useState } from "react"; +import Image from "next/image"; import { InputLabelStatus } from "@/shared/ui/InputLabel/InputLabel"; import { SquareLoader } from "react-spinners"; import axios from "axios"; import { debounce } from "lodash"; +import { deleteCookie } from "cookies-next"; import { toast } from "sonner"; import { useGeoLocation } from "@/shared/lib/useGeolocation"; import useGetLoginUserInfo from "@/entities/user/api/useGetLoginUserInfo"; -import useGetLogout from "@/features/authentication/api/usePostLogout"; import usePatchUserAddress from "@/entities/user/api/usePatchUserAddress"; import usePatchUserInfo from "@/entities/user/api/usePatchUserInfo"; import usePostLogout from "@/features/authentication/api/usePostLogout"; @@ -44,7 +44,6 @@ const UserInfoPage = () => { const [openLogoutDialog, setOpenLogoutDialog] = useState(false); const router = useRouter(); - const accessToken = getCookie("accessToken"); const geolocation = useGeoLocation(); const { data, isLoading, isSuccess } = useGetLoginUserInfo(); @@ -144,15 +143,14 @@ const UserInfoPage = () => { onSuccess: () => { deleteCookie("accessToken"); deleteCookie("refreshToken"); + toast("로그아웃 성공"); + router.push("/"); }, }); }; const handleLogout = () => { logout(); - - toast("로그아웃 성공"); - router.push("/"); }; useEffect(() => { @@ -184,7 +182,7 @@ const UserInfoPage = () => { const triggerItem = () => { return (
-
+
로그아웃
@@ -193,7 +191,7 @@ const UserInfoPage = () => { const dialogContent = () => { return ( -
+
로그아웃 하시겠어요?
@@ -228,117 +226,136 @@ const UserInfoPage = () => { return ( loginedUser && ( -
-
-
마이페이지
+
+
+
-
-
-
-
- {loginedUser.nickname}님 -
- {/* TODO: 연령대, 주소 조건문 처리 */} -
- {loginedUser.age_range}대, {loginedUser.location} +
+
+ 마이페이지 +
+
+
+
+
+
+
+ {loginedUser.nickname}님 +
+ {/* TODO: 연령대, 주소 조건문 처리 */} +
+ {loginedUser.age_range}대, {loginedUser.location} +
-
-
-
-
- ( - { - field.onChange(e.target.value); // react-hook-form의 onChange 호출 - handleChangeNickname(e.target.value); // 디바운스된 유효성 검사 호출 - }} - onBlur={field.onBlur} - value={field.value} - status={status} - message={errors.nickname?.message || message} // 메시지 처리 - /> - )} - /> -
+ +
+
( { field.onChange(e.target.value); // react-hook-form의 onChange 호출 + handleChangeNickname(e.target.value); // 디바운스된 유효성 검사 호출 }} onBlur={field.onBlur} value={field.value} + status={status} + message={errors.nickname?.message || message} // 메시지 처리 /> )} /> -
- +
+ ( + { + field.onChange(e.target.value); // react-hook-form의 onChange 호출 + }} + onBlur={field.onBlur} + value={field.value} + /> + )} + /> +
+ +
+ + + +
- - - -
-
-
- -
- +
+ +
+ +
= [ { - src: "/icons/person.svg", + src: "/icons/division.svg", + type: "division", + render: (content) => `${content}`, + }, + { + src: "/icons/capacity.svg", type: "capacity", render: (content) => `정원 ${content}명`, }, @@ -93,12 +98,7 @@ export const lectureSummaryList: Array = [ render: (content) => `${content}`, }, { - src: "/icons/user_circle.svg", - type: "division", - render: (content) => `${content}`, - }, - { - src: "/icons/calendar_filled.svg", + src: "/icons/period.svg", type: "period", render: (content) => { const lectureContent = content as LecturePeriod[]; @@ -128,6 +128,7 @@ export const lectureSummaryList: Array = [ ]; type LectureDetailTitleType = + | "condition" | "description" | "certification" | "text_book_name" @@ -135,7 +136,7 @@ type LectureDetailTitleType = | "need"; export const LectureDetailTitleEnum = { - target: "수강자격", + condition: "수강자격", description: "교육내용", certification: "자격증 관련사항", text_book_name: "교재명", @@ -149,6 +150,10 @@ export interface LectureDetailListProps { } export const lectureDetailList: Array = [ + { + type: "condition", + render: (content) => `${content}`, + }, { type: "description", render: (content) => `${content}`, @@ -192,6 +197,7 @@ export interface LectureInfo { hosted_by: string; latitude: number; longitude: number; + division: string; } export interface LectureListResData { @@ -236,6 +242,7 @@ export interface PickLectureInfo { start_date: string; end_date: string; day_of_week: string; + division: string; } export interface LectureListResData { diff --git a/src/entities/lecture/ui/Description/Description.tsx b/src/entities/lecture/ui/Description/Description.tsx index 010798b..f36f309 100755 --- a/src/entities/lecture/ui/Description/Description.tsx +++ b/src/entities/lecture/ui/Description/Description.tsx @@ -12,19 +12,19 @@ const Description = () => { const descriptionImages: CarouselImage[] = [ { - src: "/images/senior.png", + src: "/images/main_banner_1.png", alt: "senior", width: 1440, height: 640, }, { - src: "/images/sijak_happy.png", + src: "/images/main_banner_2.png", alt: "sijak_happy", width: 1440, height: 640, }, { - src: "/images/sijak_position.png", + src: "/images/main_banner_3.png", alt: "sijak_position", width: 1440, height: 640, @@ -35,13 +35,17 @@ const Description = () => {
-
+
+
+
{`${current}`}
+
{`/ ${count}`}
+
diff --git a/src/entities/lecture/ui/IntroductionBanner/IntroductionBanner.tsx b/src/entities/lecture/ui/IntroductionBanner/IntroductionBanner.tsx new file mode 100644 index 0000000..39fcf4a --- /dev/null +++ b/src/entities/lecture/ui/IntroductionBanner/IntroductionBanner.tsx @@ -0,0 +1,36 @@ +import Link from "next/link"; + +const IntroductionBanner = () => { + return ( + +
+
+
+
+
+ 시ː작 +
+
+ 을 통하면 +
+
+
+ 새로운 일상의 +
+
+ 반은 왔어요 +
+
+
+ 시작이 반이다! +
+
+
+ + ); +}; + +export default IntroductionBanner; diff --git a/src/entities/lecture/ui/IntroductionBanner/index.ts b/src/entities/lecture/ui/IntroductionBanner/index.ts new file mode 100644 index 0000000..b55c4ed --- /dev/null +++ b/src/entities/lecture/ui/IntroductionBanner/index.ts @@ -0,0 +1 @@ +export { default as IntroductionBanner } from "./IntroductionBanner"; diff --git a/src/entities/lecture/ui/LectureCard/HomeLectureCard/HomeLectureCard.tsx b/src/entities/lecture/ui/LectureCard/HomeLectureCard/HomeLectureCard.tsx new file mode 100644 index 0000000..796bb75 --- /dev/null +++ b/src/entities/lecture/ui/LectureCard/HomeLectureCard/HomeLectureCard.tsx @@ -0,0 +1,168 @@ +import { LectureInfo, PickLectureInfo } from "@/entities/lecture/model/lecture"; +import { MouseEvent, useEffect, useState } from "react"; + +import { Button } from "@/shared/ui"; +import { HeartsLectureListResDataInfo } from "@/features/like/model/like"; +import Image from "next/image"; +import Link from "next/link"; +import { toast } from "sonner"; +import useDeleteLikeLecture from "@/features/like/api/useDeleteLikeLecture"; +import usePostLikeLecture from "@/features/like/api/usePostLikeLecture"; + +interface HomeLectureCardProps { + lectureData: LectureInfo | PickLectureInfo | HeartsLectureListResDataInfo; + type: "homeLecture" | "pickLecture"; +} +const HomeLectureCard = (props: HomeLectureCardProps) => { + const { lectureData, type } = props; + + const { + id, + thumbnail, + name, + time, + target, + status, + address, + division, + link, + heart, + start_date, + end_date, + day_of_week, + } = lectureData; + + const [dimensions, setDimensions] = useState({ width: 384, height: 280 }); + + const postLikeLecture = usePostLikeLecture(id); + const deleteLikeLecture = useDeleteLikeLecture(id); + + useEffect(() => { + const handleResize = () => { + if (window.innerWidth < 768) { + setDimensions({ width: 240, height: 168 }); + } else if (window.innerWidth < 1440) { + setDimensions({ width: 280, height: 188 }); + } else { + setDimensions({ width: 384, height: 280 }); + } + }; + + window.addEventListener("resize", handleResize); + + // 초기 크기 설정 + handleResize(); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + const handleLikeClick = (e: MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + + if (lectureData.heart === true) { + deleteLikeLecture.mutate( + { + lectureId: id, + }, + { + onSuccess: () => { + toast("좋아요 삭제 성공"); + }, + }, + ); + } + if (lectureData.heart === false) { + postLikeLecture.mutate( + { + lectureId: id, + }, + { + onSuccess: () => { + toast("좋아요 성공"); + }, + }, + ); + } + }; + + return ( + +
+
+
+
+ thumbnail +
{`문화`}
+
+ +
+
+
+
+
+
+ {division} +
+
+
+ [{name}] +
+
+
+
+
+ {/* FIXME: 색상 추가 */} +
+ {address} +
+
+ {start_date.replaceAll("-", ".").split(".")[1]}. + {start_date.replaceAll("-", ".").split(".")[2]}({day_of_week}){" "} + {/* TODO: time to 오전 오후 시간 */} + {Number(time.split(":")[0]) / 12 ? "오후" : "오전"}{" "} + {Number(time.split(":")[0]) % 12}: + {time.split(":")[1].slice(0, 2)} +
+
+
+
+
+
+ + ); +}; + +export default HomeLectureCard; diff --git a/src/entities/lecture/ui/LectureCard/HomeLectureCard/index.ts b/src/entities/lecture/ui/LectureCard/HomeLectureCard/index.ts new file mode 100644 index 0000000..06254fe --- /dev/null +++ b/src/entities/lecture/ui/LectureCard/HomeLectureCard/index.ts @@ -0,0 +1 @@ +export { default as HomeLectureCard } from "./HomeLectureCard"; diff --git a/src/entities/lecture/ui/LectureCard/LectureCard.tsx b/src/entities/lecture/ui/LectureCard/LectureCard.tsx index 18b9a0e..45f6b32 100755 --- a/src/entities/lecture/ui/LectureCard/LectureCard.tsx +++ b/src/entities/lecture/ui/LectureCard/LectureCard.tsx @@ -1,170 +1,19 @@ import { LectureInfo, PickLectureInfo } from "@/entities/lecture/model/lecture"; -import { Button } from "@/shared/ui"; import { HeartsLectureListResDataInfo } from "@/features/like/model/like"; -import Image from "next/image"; -import Link from "next/link"; -import { MouseEvent } from "react"; -import { toast } from "sonner"; -import useDeleteLikeLecture from "@/features/like/api/useDeleteLikeLecture"; -import usePostLikeLecture from "@/features/like/api/usePostLikeLecture"; +import { HomeLectureCard } from "./HomeLectureCard"; +import { PickLectureCard } from "./PickLectureCard"; interface LectureCardProps { lectureData: LectureInfo | PickLectureInfo | HeartsLectureListResDataInfo; - type: "row" | "col"; + type: "homeLecture" | "pickLecture"; } const LectureCard = ({ lectureData, type }: LectureCardProps) => { - const { - id, - thumbnail, - name, - time, - target, - status, - address, - link, - heart, - start_date, - end_date, - day_of_week, - } = lectureData; - - const postLikeLecture = usePostLikeLecture(id); - const deleteLikeLecture = useDeleteLikeLecture(id); - - const handleLikeClick = (e: MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - - if (lectureData.heart === true) { - deleteLikeLecture.mutate( - { - lectureId: id, - }, - { - onSuccess: () => { - toast("좋아요 삭제 성공"); - }, - }, - ); - } - if (lectureData.heart === false) { - postLikeLecture.mutate( - { - lectureId: id, - }, - { - onSuccess: () => { - toast("좋아요 성공"); - }, - }, - ); - } - }; - - return type === "col" ? ( - -
-
-
-
- thumbnail -
-
-
-
-
- {target} -
-
-
- [{name}] -
- -
-
-
-
- {/* FIXME: 색상 추가 */} -
- {address} -
-
- {time.split(" ")[0].replaceAll("-", ".")} -
-
-
-
-
-
- + return type === "homeLecture" ? ( + ) : ( - -
-
- thumbnail_image_row -
-
-
- {/* TODO: CHIP 컴포넌트 개발 필요 */} -
- 문화 -
-
-
-
- {name} -
-
- {target} -
-
-
- {/* FIXME: 색상 추가 */} -
- {address} -
-
- {time.split(" ")[0].replaceAll("-", ".")} -
-
-
-
- + ); }; diff --git a/src/entities/lecture/ui/LectureCard/PickLectureCard/PickLectureCard.tsx b/src/entities/lecture/ui/LectureCard/PickLectureCard/PickLectureCard.tsx new file mode 100644 index 0000000..41de6bd --- /dev/null +++ b/src/entities/lecture/ui/LectureCard/PickLectureCard/PickLectureCard.tsx @@ -0,0 +1,168 @@ +import { LectureInfo, PickLectureInfo } from "@/entities/lecture/model/lecture"; +import { MouseEvent, useEffect, useState } from "react"; + +import { Button } from "@/shared/ui"; +import { HeartsLectureListResDataInfo } from "@/features/like/model/like"; +import Image from "next/image"; +import Link from "next/link"; +import { toast } from "sonner"; +import useDeleteLikeLecture from "@/features/like/api/useDeleteLikeLecture"; +import usePostLikeLecture from "@/features/like/api/usePostLikeLecture"; + +interface PickLectureCardProps { + lectureData: LectureInfo | PickLectureInfo | HeartsLectureListResDataInfo; + type: "homeLecture" | "pickLecture"; +} + +const PickLectureCard = (props: PickLectureCardProps) => { + const { lectureData, type } = props; + + const { + id, + thumbnail, + name, + time, + target, + status, + address, + link, + heart, + start_date, + end_date, + day_of_week, + division, + } = lectureData; + + const [dimensions, setDimensions] = useState({ width: 384, height: 280 }); + + const postLikeLecture = usePostLikeLecture(id); + const deleteLikeLecture = useDeleteLikeLecture(id); + + useEffect(() => { + const handleResize = () => { + if (window.innerWidth < 768) { + setDimensions({ width: 312, height: 190 }); + } else if (window.innerWidth < 1440) { + setDimensions({ width: 344, height: 256 }); + } else { + setDimensions({ width: 384, height: 280 }); + } + }; + + window.addEventListener("resize", handleResize); + + // 초기 크기 설정 + handleResize(); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + const handleLikeClick = (e: MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + + if (lectureData.heart === true) { + deleteLikeLecture.mutate( + { + lectureId: id, + }, + { + onSuccess: () => { + toast("좋아요 삭제 성공"); + }, + }, + ); + } + if (lectureData.heart === false) { + postLikeLecture.mutate( + { + lectureId: id, + }, + { + onSuccess: () => { + toast("좋아요 성공"); + }, + }, + ); + } + }; + return ( + +
+
+
+
+ thumbnail +
{`문화`}
+
+ +
+
+
+
+
+
+ {division} +
+
+
+ [{name}] +
+
+
+
+
+ {/* FIXME: 색상 추가 */} +
+ {address} +
+
+ {start_date.replaceAll("-", ".").split(".")[1]}. + {start_date.replaceAll("-", ".").split(".")[2]}({day_of_week}){" "} + {/* TODO: time to 오전 오후 시간 */} + {Number(time.split(":")[0]) / 12 ? "오후" : "오전"}{" "} + {Number(time.split(":")[0]) % 12}: + {time.split(":")[1].slice(0, 2)} +
+
+
+
+
+
+ + ); +}; + +export default PickLectureCard; diff --git a/src/entities/lecture/ui/LectureCard/PickLectureCard/index.ts b/src/entities/lecture/ui/LectureCard/PickLectureCard/index.ts new file mode 100644 index 0000000..3ed864b --- /dev/null +++ b/src/entities/lecture/ui/LectureCard/PickLectureCard/index.ts @@ -0,0 +1 @@ +export { default as PickLectureCard } from "./PickLectureCard"; diff --git a/src/entities/lecture/ui/LectureCard/RowLectureCard/RowLectureCard.tsx b/src/entities/lecture/ui/LectureCard/RowLectureCard/RowLectureCard.tsx new file mode 100644 index 0000000..a21ca60 --- /dev/null +++ b/src/entities/lecture/ui/LectureCard/RowLectureCard/RowLectureCard.tsx @@ -0,0 +1,59 @@ +import { LectureInfo, PickLectureInfo } from "@/entities/lecture/model/lecture"; + +import { HeartsLectureListResDataInfo } from "@/features/like/model/like"; +import Image from "next/image"; +import Link from "next/link"; + +interface RowLectureCardProps { + lectureData: LectureInfo | PickLectureInfo | HeartsLectureListResDataInfo; + type: "homeLecture" | "pickLecture"; +} + +const RowLectureCard = (props: RowLectureCardProps) => { + const { lectureData, type } = props; + + const { id, thumbnail, name, time, target, address } = lectureData; + + return ( + +
+
+ thumbnail_image_row +
+
+
+ {/* TODO: CHIP 컴포넌트 개발 필요 */} +
+ 문화 +
+
+
+
+ {name} +
+
+ {target} +
+
+
+ {/* FIXME: 색상 추가 */} +
+ {address} +
+
+ {time.split(" ")[0].replaceAll("-", ".")} +
+
+
+
+ + ); +}; + +export default RowLectureCard; diff --git a/src/entities/lecture/ui/LectureCard/RowLectureCard/index.ts b/src/entities/lecture/ui/LectureCard/RowLectureCard/index.ts new file mode 100644 index 0000000..9bdf593 --- /dev/null +++ b/src/entities/lecture/ui/LectureCard/RowLectureCard/index.ts @@ -0,0 +1 @@ +export { default as RowLectureCard } from "./RowLectureCard"; diff --git a/src/entities/lecture/ui/LectureCard/index.ts b/src/entities/lecture/ui/LectureCard/index.ts index 0d54338..54a3f03 100755 --- a/src/entities/lecture/ui/LectureCard/index.ts +++ b/src/entities/lecture/ui/LectureCard/index.ts @@ -1 +1,4 @@ export { default as LectureCard } from "./LectureCard"; +export * from "./HomeLectureCard"; +export * from "./PickLectureCard"; +export * from "./RowLectureCard"; diff --git a/src/entities/lecture/ui/LectureCarousel/LectureCarousel.tsx b/src/entities/lecture/ui/LectureCarousel/LectureCarousel.tsx new file mode 100644 index 0000000..d88e536 --- /dev/null +++ b/src/entities/lecture/ui/LectureCarousel/LectureCarousel.tsx @@ -0,0 +1,69 @@ +import { + Carousel, + CarouselApi, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/shared/ui"; +import { Dispatch, SetStateAction } from "react"; + +import Image from "next/image"; +import { LectureCard } from "../LectureCard"; +import { LectureInfo } from "../../model/lecture"; + +export interface CarouselImage { + src: string; + alt: string; + width: number; + height: number; +} + +interface LectureCarouselProps { + setApi: Dispatch>; + lectureInfo: LectureInfo[]; + isPreviousIcon?: boolean; + isNextIcon?: boolean; +} + +const LectureCarousel = ({ + setApi, + lectureInfo, + isPreviousIcon, + isNextIcon, +}: LectureCarouselProps) => { + return ( + + + {lectureInfo.map((lectureData) => { + return ( + + + + ); + })} + + {isPreviousIcon && ( + + )} + {isNextIcon && ( + + )} + + ); +}; + +export default LectureCarousel; diff --git a/src/entities/lecture/ui/LectureCarousel/index.ts b/src/entities/lecture/ui/LectureCarousel/index.ts new file mode 100644 index 0000000..3789193 --- /dev/null +++ b/src/entities/lecture/ui/LectureCarousel/index.ts @@ -0,0 +1 @@ +export { default as LectureCarousel } from "./LectureCarousel"; diff --git a/src/entities/lecture/ui/LectureDetail/LectureDetail.tsx b/src/entities/lecture/ui/LectureDetail/LectureDetail.tsx index 2ec4385..a1c2761 100644 --- a/src/entities/lecture/ui/LectureDetail/LectureDetail.tsx +++ b/src/entities/lecture/ui/LectureDetail/LectureDetail.tsx @@ -1,6 +1,7 @@ import { Divider, Skeleton } from "@/shared/ui"; import { Lecture, + LectureDetailListProps, LectureDetailTitleEnum, lectureDetailList, } from "../../model/lecture"; @@ -15,46 +16,54 @@ interface LectureDetailProps { const LectureDetail = ({ lectureInfo, isLoading }: LectureDetailProps) => { if (isLoading) { - return ; + return ( +
+ +
+ ); } return ( -
-
-
+
+
+
클래스 내용
- {/* TODO: 컴포넌트화 */} - {lectureDetailList.map((lectureDetailItem) => { - return ( -
- - -
- ); - })} + {lectureDetailList.map( + (lectureDetailItem: LectureDetailListProps) => { + return ( +
+ + +
+ ); + }, + )}
-
-
+
+
강사 이력
{isLoading && } {lectureInfo && (
-
+
{lectureInfo.instructor_name.map((instructor) => instructor.name)}
-
    +
      {lectureInfo.instructor_name.map((instructor) => { return instructor.instructor_history.map((history, idx) => { return
    • {history.content}
    • ; @@ -64,8 +73,10 @@ const LectureDetail = ({ lectureInfo, isLoading }: LectureDetailProps) => {
)}
-
-
교육 계획
+
+
+ 교육 계획 +
{/* {isLoading && } */} {/* {lectureInfo &&
{lectureInfo.educationPlan}
} */}
diff --git a/src/entities/lecture/ui/LectureDetail/LectureDetailItem/LectureDetailItem.tsx b/src/entities/lecture/ui/LectureDetail/LectureDetailItem/LectureDetailItem.tsx index 16afa8c..75534ad 100644 --- a/src/entities/lecture/ui/LectureDetail/LectureDetailItem/LectureDetailItem.tsx +++ b/src/entities/lecture/ui/LectureDetail/LectureDetailItem/LectureDetailItem.tsx @@ -6,10 +6,12 @@ export interface LectureDetailItemProps { const LectureDetailItem = ({ title, content }: LectureDetailItemProps) => { return (
-
+
{title}
- {content &&
{content}
} + {content && ( +
{content}
+ )}
); }; diff --git a/src/entities/lecture/ui/LectureFooter/LectureFooter.tsx b/src/entities/lecture/ui/LectureFooter/LectureFooter.tsx index 9dc5ad5..07a6018 100644 --- a/src/entities/lecture/ui/LectureFooter/LectureFooter.tsx +++ b/src/entities/lecture/ui/LectureFooter/LectureFooter.tsx @@ -4,6 +4,7 @@ import { Button, IconButton, Skeleton, UnifiedDialog } from "@/shared/ui"; import Image from "next/image"; import { Lecture } from "../../model/lecture"; +import { getCookie } from "cookies-next"; import { toast } from "sonner"; import useDeleteLikeLecture from "@/features/like/api/useDeleteLikeLecture"; import usePostLikeLecture from "@/features/like/api/usePostLikeLecture"; @@ -17,7 +18,7 @@ interface LectureFooterProps { const LectureFooter = ({ lectureInfo, isLoading }: LectureFooterProps) => { const [open, setOpen] = useState(false); - const tempApplyStatus = false; + const token = getCookie("accessToken"); const router = useRouter(); const postLikeLecture = usePostLikeLecture(lectureInfo ? lectureInfo.id : -1); @@ -66,7 +67,7 @@ const LectureFooter = ({ lectureInfo, isLoading }: LectureFooterProps) => { const triggerItem = () => { return (
-
+
신청하러 가기
@@ -75,17 +76,14 @@ const LectureFooter = ({ lectureInfo, isLoading }: LectureFooterProps) => { const dialogContent = () => { return ( -
+
신청 페이지를
-
불러오지 못했습니다.
-
-
- 데이터가 만료되거나 이용 완료되어 불러오지 못했습니다. +
불러오지 못했어요
+
+ )} + {isLoading && ( + + )} {lectureInfo && ( )} -
-
- map_pin -
- {isLoading && } - {/* TODO: 거리 계산 */} - {lectureInfo && ( -
- 내 위치에서 1.1km ∙ 대중교통 약 10분 이내 -
- )} -
); }; diff --git a/src/entities/lecture/ui/LectureList/LectureList.tsx b/src/entities/lecture/ui/LectureList/LectureList.tsx index f19b5b6..5ed1074 100755 --- a/src/entities/lecture/ui/LectureList/LectureList.tsx +++ b/src/entities/lecture/ui/LectureList/LectureList.tsx @@ -8,19 +8,12 @@ interface LectureListProps { | LectureInfo[] | PickLectureInfo[] | HeartsLectureListResDataInfo[]; - type: "row" | "col"; + type: "homeLecture" | "pickLecture"; } const LectureList = ({ lectureListData, type }: LectureListProps) => { - const setClassNameByType = () => { - if (type === "col") { - return `w-full grid grid-cols-3 gap-6 mobile:grid-cols-1 tablet:grid-cols-2 desktop:grid-cols-3`; - } - return `w-full grid grid-cols-2 gap-6 mobile:grid-cols-1 tablet:grid-cols-1 desktop:grid-cols-2 pb-[265px]`; - }; - return ( -
+
{lectureListData.map((lectureData) => { return ( { + const { lectureInfo, isLoading } = props; + + if (isLoading) { + return ( + + ); + } + return ( + lectureInfo && ( +
+ +
+
+ map_pin +
+ {isLoading && } +
+ 내 위치에서 {lectureInfo.distance} ∙{lectureInfo.estimatedTime}분 + 이내 +
+
+
+ ) + ); +}; + +export default LectureMinimap; diff --git a/src/entities/lecture/ui/LectureSummary/LectureSummary.tsx b/src/entities/lecture/ui/LectureSummary/LectureSummary.tsx index 5f312f9..2af3ff9 100644 --- a/src/entities/lecture/ui/LectureSummary/LectureSummary.tsx +++ b/src/entities/lecture/ui/LectureSummary/LectureSummary.tsx @@ -6,8 +6,6 @@ import { import { LectureSummaryHeader } from "./LectureSummaryHeader"; import { LectureSummaryItem } from "."; -import MiniMap from "@/features/map/ui/MiniMap/MiniMap"; -import { Skeleton } from "@/shared/ui"; interface LectureInfoDetail { lectureInfo?: Lecture; @@ -16,37 +14,30 @@ interface LectureInfoDetail { const LectureSummary = ({ lectureInfo, isLoading }: LectureInfoDetail) => { return ( -
- -
-
- {lectureSummaryList.map((lectureSummaryItem) => { - return ( - - ); - })} +
+
+ +
+
+ {lectureSummaryList.map((lectureSummaryItem) => { + return ( + + ); + })} +
- {isLoading && } - {lectureInfo && ( -
- -
- )}
); }; diff --git a/src/entities/lecture/ui/LectureSummary/LectureSummaryHeader/LectureSummaryHeader.tsx b/src/entities/lecture/ui/LectureSummary/LectureSummaryHeader/LectureSummaryHeader.tsx index f00a9d9..9fa56df 100644 --- a/src/entities/lecture/ui/LectureSummary/LectureSummaryHeader/LectureSummaryHeader.tsx +++ b/src/entities/lecture/ui/LectureSummary/LectureSummaryHeader/LectureSummaryHeader.tsx @@ -1,11 +1,8 @@ -import { Button, IconDialog, ImageDescription, Skeleton } from "@/shared/ui"; +import { IconDialog, ImageDescription, Skeleton } from "@/shared/ui"; import Image from "next/image"; import { Lecture } from "@/entities/lecture/model/lecture"; import { handleCopyClipBoard } from "@/shared/lib/utils"; -import { toast } from "sonner"; -import useDeleteLikeLecture from "@/features/like/api/useDeleteLikeLecture"; -import usePostLikeLecture from "@/features/like/api/usePostLikeLecture"; interface LectureInfoDetailHeaderProps { lectureInfo?: Lecture; @@ -16,41 +13,6 @@ const LectureSummaryHeader = ({ lectureInfo, isLoading, }: LectureInfoDetailHeaderProps) => { - const postLikeLecture = usePostLikeLecture(lectureInfo ? lectureInfo.id : -1); - const deleteLikeLecture = useDeleteLikeLecture( - lectureInfo ? lectureInfo.id : -1, - ); - - const handleLikeLecture = () => { - if (lectureInfo) { - // TODO: 좋아요 API TEST 필요 - if (lectureInfo.heart === true) { - deleteLikeLecture.mutate( - { - lectureId: lectureInfo.id, - }, - { - onSuccess: () => { - toast("좋아요 삭제 성공"); - }, - }, - ); - } - if (lectureInfo.heart === false) { - postLikeLecture.mutate( - { - lectureId: lectureInfo.id, - }, - { - onSuccess: () => { - toast("좋아요 성공"); - }, - }, - ); - } - } - }; - const shareLinkToKakao = () => { // TODO: 카카오 링크 공유하기 API }; @@ -61,11 +23,19 @@ const LectureSummaryHeader = ({ } }; + const renderDialogTriggerItem = () => { + return ( +
+ share +
+ ); + }; + const renderDialogContent = () => { return ( -
+
-
+
+
{isLoading && } {lectureInfo && ( -
+
{lectureInfo.name}
)} -
- {lectureInfo && lectureInfo.heart === true ? ( - - ) : ( - - )} +
{ return ( -
+
- {title} -
+ {title} +
{title}
{content ? ( -
+
{content}
) : ( diff --git a/src/entities/lecture/ui/index.ts b/src/entities/lecture/ui/index.ts index e4b46f3..9bbcb4b 100755 --- a/src/entities/lecture/ui/index.ts +++ b/src/entities/lecture/ui/index.ts @@ -6,3 +6,5 @@ export * from "./LectureImageInfo"; export * from "./LectureSummary"; export * from "./LectureDetail"; export * from "./LectureFooter"; +export * from "./IntroductionBanner"; +export * from "./LectureCarousel"; diff --git a/src/features/like/model/like.ts b/src/features/like/model/like.ts index fb25835..a1f60b7 100644 --- a/src/features/like/model/like.ts +++ b/src/features/like/model/like.ts @@ -19,6 +19,7 @@ export interface HeartsLectureListResDataInfo { end_date: string; day_of_week: string; hosted_by: string | null; + division: string; } // TODO: 타입 중복되는 것 리팩토링 => 제네릭으로 처리 diff --git a/src/features/map/ui/Map/Map.tsx b/src/features/map/ui/Map/Map.tsx index 9d170e3..ac7648a 100755 --- a/src/features/map/ui/Map/Map.tsx +++ b/src/features/map/ui/Map/Map.tsx @@ -65,28 +65,26 @@ const Map = ({ latitude, longitude, lectureListData }: MapProps) => { clickable: true, map: map, icon: { - content: `클래스 위치`, + content: `클래스 위치`, size: new naver.maps.Size(35, 35), anchor: new naver.maps.Point(11, 35), }, }); const hostedBy = lectureData.hosted_by; + const address = lectureData.address; // InfoWindow 생성 const infoWindow = new naver.maps.InfoWindow({ content: ` -
+
-
- -
-
+
${hostedBy}
-
- 서울 송파구 백제고분로42길 5 송파여성문화회관 +
+ ${address}
`, borderWidth: 0, @@ -143,11 +141,13 @@ const Map = ({ latitude, longitude, lectureListData }: MapProps) => { }, [lectureListData, infoWindows, latitude, longitude, markers]); useEffect(() => { - initMap(); + if (typeof naver !== "undefined") { + initMap(); + } }, [initMap]); return ( -
+
); diff --git a/src/features/map/ui/MiniMap/MiniMap.tsx b/src/features/map/ui/MiniMap/MiniMap.tsx index 1798ee6..ae18c03 100755 --- a/src/features/map/ui/MiniMap/MiniMap.tsx +++ b/src/features/map/ui/MiniMap/MiniMap.tsx @@ -36,14 +36,26 @@ const MiniMap = ({ latitude, longitude }: MiniMapProps) => { }, [latitude, longitude]); useEffect(() => { - initMap(); + if (typeof naver !== "undefined") { + initMap(); + } else { + // naver 객체가 정의되지 않은 경우, 스크립트 로드 후 재시도 + const checkNaver = setInterval(() => { + if (typeof naver !== "undefined") { + clearInterval(checkNaver); + initMap(); + } + }, 100); // 100ms마다 체크 + + return () => clearInterval(checkNaver); + } }, [initMap]); return ( -
+
); diff --git a/src/shared/ui/BackToPrevious/BackToPrevious.tsx b/src/shared/ui/BackToPrevious/BackToPrevious.tsx new file mode 100644 index 0000000..8c8e4a6 --- /dev/null +++ b/src/shared/ui/BackToPrevious/BackToPrevious.tsx @@ -0,0 +1,27 @@ +"use client"; + +import Image from "next/image"; +import { useRouter } from "next/navigation"; + +const BackToPrevious = () => { + const router = useRouter(); + + const backToPreviousPage = () => { + router.back(); + }; + return ( +
+ back +
+ ); +}; + +export default BackToPrevious; diff --git a/src/shared/ui/BackToPrevious/index.ts b/src/shared/ui/BackToPrevious/index.ts new file mode 100644 index 0000000..b3468e8 --- /dev/null +++ b/src/shared/ui/BackToPrevious/index.ts @@ -0,0 +1 @@ +export { default as BackToPrevious } from "./BackToPrevious"; diff --git a/src/shared/ui/Carousel/Carousel.tsx b/src/shared/ui/Carousel/Carousel.tsx index 6c44f26..4584f99 100755 --- a/src/shared/ui/Carousel/Carousel.tsx +++ b/src/shared/ui/Carousel/Carousel.tsx @@ -2,12 +2,12 @@ "use client"; import * as React from "react"; -import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons"; import useEmblaCarousel, { type UseEmblaCarouselType, } from "embla-carousel-react"; import { Button } from "../Button"; import { cn } from "@/shared/lib/utils"; +import Image from "next/image"; type CarouselApi = UseEmblaCarouselType[1]; type UseCarouselParameters = Parameters; @@ -206,7 +206,7 @@ const CarouselPrevious = React.forwardRef< variant={variant} size={size} className={cn( - "absolute h-8 w-8 rounded-full ml-10 bg-gray-300", + "desktop:flex tablet:flex mobile:hidden absolute h-8 w-8 rounded-full desktop:ml-[120px] tablet:ml-8", orientation === "horizontal" ? "-left-0 top-1/2 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90", @@ -216,7 +216,12 @@ const CarouselPrevious = React.forwardRef< onClick={scrollPrev} {...props} > - + arrow left Previous slide ); @@ -235,7 +240,7 @@ const CarouselNext = React.forwardRef< variant={variant} size={size} className={cn( - "absolute h-8 w-8 rounded-full mr-10 bg-gray-300", + "desktop:flex tablet:flex mobile:hidden absolute h-8 w-8 rounded-full desktop:mr-[120px] tablet:mr-8", orientation === "horizontal" ? "-right-0 top-1/2 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", @@ -245,7 +250,12 @@ const CarouselNext = React.forwardRef< onClick={scrollNext} {...props} > - + arrow right Next slide ); diff --git a/src/shared/ui/Footer/Footer.tsx b/src/shared/ui/Footer/Footer.tsx index 52224c6..69f5df9 100755 --- a/src/shared/ui/Footer/Footer.tsx +++ b/src/shared/ui/Footer/Footer.tsx @@ -2,6 +2,7 @@ import { ExternalLink } from "../ExternalLink"; import { ExternalLinkProps } from "../ExternalLink/ExternalLink"; +import Image from "next/image"; import { usePathname } from "next/navigation"; const Footer = () => { @@ -17,35 +18,47 @@ const Footer = () => { const externalLinkList: Array = [ { - link: "https://ebony-specialist-cf1.notion.site/59f05d08d90346ad989223480f372c84?pvs=4", - content: "개인정보처리방침 |", + link: "https://www.notion.so/6d012c4a80f845eca3d98defc11d6d86?pvs=4", + content: "개인정보처리방침", }, { - link: "https://ebony-specialist-cf1.notion.site/a46bfe06464b4101927da295479d4576?pvs=4", - content: "이용약관 |", + link: "https://www.notion.so/b942a4f9070442b7891cb136037ffa74?pvs=4", + content: "이용약관", }, { - link: "https://ebony-specialist-cf1.notion.site/807318b7508f4b0c977a2be77f22dfdb?pvs=4", - content: "위치기반시스템이용약관 |", - }, - { - link: "https://ebony-specialist-cf1.notion.site/15d15238629640f8a3ed6e1fc289eb86?pvs=4", - content: "서비스가이드", + link: "https://www.notion.so/17e92e6c1188429cb17ad92d84f65103?pvs=4", + content: "위치기반시스템이용약관", }, ]; return ( isRenderFooter() && ( -
-
- {/* TODO: URL ENV 처리 필요한지 확인: 공유 페이지라 괜찮을 것 같지만 확인 필요 */} +
+
+
+ sijak footer logo +
+
+
시ː작이 반이다.
+
+ 모든 여정은 한 걸음에서 시작됩니다. +
+
+
+
{externalLinkList.map((externalLink) => { return ( - +
+ +
); })}
diff --git a/src/shared/ui/Header/Header.tsx b/src/shared/ui/Header/Header.tsx index 849e8dd..49c60fd 100755 --- a/src/shared/ui/Header/Header.tsx +++ b/src/shared/ui/Header/Header.tsx @@ -1,14 +1,19 @@ "use client"; +import { HeaderFeatures, HeaderPrevious, HeaderTitle } from "."; + import { HeaderDescription } from "./HeaderDescription"; -import { HeaderFeatures } from "."; import { Logo } from "./Logo"; const Header = () => { return ( -
- - +
+
+ + + +
+
); diff --git a/src/shared/ui/Header/HeaderDescription/HeaderDescription.tsx b/src/shared/ui/Header/HeaderDescription/HeaderDescription.tsx index 5bef95a..8d3fb00 100644 --- a/src/shared/ui/Header/HeaderDescription/HeaderDescription.tsx +++ b/src/shared/ui/Header/HeaderDescription/HeaderDescription.tsx @@ -1,14 +1,8 @@ const HeaderDescription = () => { return ( -
-
- {/* FIXME: 수정 필요 */} -
- 50+ 시ː니어 -
-
- 를 위한 문화생활 플랫폼 -
+
+
+ 50+ 시ː니어를 위한 문화생활 플랫폼
); diff --git a/src/shared/ui/Header/HeaderFeatures/HeaderFeatures.tsx b/src/shared/ui/Header/HeaderFeatures/HeaderFeatures.tsx index 0de35fe..d8e6da2 100644 --- a/src/shared/ui/Header/HeaderFeatures/HeaderFeatures.tsx +++ b/src/shared/ui/Header/HeaderFeatures/HeaderFeatures.tsx @@ -43,25 +43,20 @@ const HeaderFeatures = () => { const dialogContent = () => { return ( -
+
-
- 로그인이 필요한 서비스 입니다. -
-
- 간편 회원가입 +
+ 로그인이 필요한
+
서비스에요
@@ -69,19 +64,20 @@ const HeaderFeatures = () => { }; const renderLikeIcon = () => { - // 개발 편의상 임시 처리 if (loginedUser && loginedUserInfo && accessToken) { return (
-
- heart-icons -
+
+
+ heart-icons +
+
@@ -95,16 +91,20 @@ const HeaderFeatures = () => { dialogDescription="로그인 오류 Dialog" triggerItem={
- heart-icons -
+
+ heart-icons +
+
+ 찜 +
} dialogContent={dialogContent()} @@ -124,15 +124,17 @@ const HeaderFeatures = () => { return (
-
- user-icons -
- 마이페이지 +
+
+ user-icons +
+
+ 마이
@@ -146,17 +148,19 @@ const HeaderFeatures = () => { dialogDescription="로그인 오류 알림" triggerItem={
- user-icons -
- 마이페이지 +
+ user-icons +
+
+ 마이
} @@ -168,7 +172,7 @@ const HeaderFeatures = () => { }; return ( -
+
{renderLikeIcon()} {renderUserIcon()}
diff --git a/src/shared/ui/Header/HeaderPrevious/HeaderPrevious.tsx b/src/shared/ui/Header/HeaderPrevious/HeaderPrevious.tsx new file mode 100644 index 0000000..1a30b9c --- /dev/null +++ b/src/shared/ui/Header/HeaderPrevious/HeaderPrevious.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { usePathname, useRouter } from "next/navigation"; + +import Image from "next/image"; + +const HeaderPrevious = () => { + const pathname = usePathname(); + const router = useRouter(); + const url = pathname.split("/")[1]; + + const isRenderMobileArrow = () => { + if ( + url === "class" || + url === "user" || + url === "like" || + url === "entire" + ) { + return true; + } + return false; + }; + + const backToPreviousPage = () => { + router.back(); + }; + + return ( + isRenderMobileArrow() && ( +
+
+ back +
+
+ ) + ); +}; + +export default HeaderPrevious; diff --git a/src/shared/ui/Header/HeaderPrevious/index.ts b/src/shared/ui/Header/HeaderPrevious/index.ts new file mode 100644 index 0000000..eae4299 --- /dev/null +++ b/src/shared/ui/Header/HeaderPrevious/index.ts @@ -0,0 +1 @@ +export { default as HeaderPrevious } from "./HeaderPrevious"; diff --git a/src/shared/ui/Header/HeaderTitle/HeaderTitle.tsx b/src/shared/ui/Header/HeaderTitle/HeaderTitle.tsx new file mode 100644 index 0000000..9819d8d --- /dev/null +++ b/src/shared/ui/Header/HeaderTitle/HeaderTitle.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { usePathname } from "next/navigation"; + +const HeaderTitle = () => { + const pathname = usePathname(); + const url = pathname.split("/")[1] as urlToInfoKey; + + type urlToInfoKey = "class" | "user" | "like" | "entire"; + + const urlToInfo: Record = { + class: "클래스 정보", + user: "마이페이지", + like: "찜 클래스", + entire: "클래스 리스트", + }; + + return ( +
+ {urlToInfo[url]} +
+ ); +}; + +export default HeaderTitle; diff --git a/src/shared/ui/Header/HeaderTitle/index.ts b/src/shared/ui/Header/HeaderTitle/index.ts new file mode 100644 index 0000000..a63a981 --- /dev/null +++ b/src/shared/ui/Header/HeaderTitle/index.ts @@ -0,0 +1 @@ +export { default as HeaderTitle } from "./HeaderTitle"; diff --git a/src/shared/ui/Header/Logo/Logo.tsx b/src/shared/ui/Header/Logo/Logo.tsx index fc7c824..e68bb71 100644 --- a/src/shared/ui/Header/Logo/Logo.tsx +++ b/src/shared/ui/Header/Logo/Logo.tsx @@ -1,15 +1,50 @@ +"use client"; + +import { useEffect, useState } from "react"; + import Image from "next/image"; import Link from "next/link"; +import { usePathname } from "next/navigation"; const Logo = () => { + const pathname = usePathname(); + const url = pathname.split("/")[1]; + + const [isVisible, setIsVisible] = useState(true); + + // FIXME: entire 모바일 로고 보이는 버그 픽스 + useEffect(() => { + const handleResize = () => { + const shouldHide = + window.innerWidth < 768 && + (url === "class" || + url === "user" || + url === "like" || + url === "entire"); + + setIsVisible(!shouldHide); + }; + + handleResize(); // 초기 확인 + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [url]); + + if (!isVisible) { + return null; // 로고가 보이지 않을 경우 null 반환 + } + return ( -
+
sijak_logo
diff --git a/src/shared/ui/Header/index.ts b/src/shared/ui/Header/index.ts index 65292cd..0838ae0 100755 --- a/src/shared/ui/Header/index.ts +++ b/src/shared/ui/Header/index.ts @@ -2,3 +2,5 @@ export { default as Header } from "./Header"; export * from "./Logo"; export * from "./HeaderDescription"; export * from "./HeaderFeatures"; +export * from "./HeaderTitle"; +export * from "./HeaderPrevious"; diff --git a/src/shared/ui/IconDialog/IconDialog.tsx b/src/shared/ui/IconDialog/IconDialog.tsx index 781334e..1706f53 100644 --- a/src/shared/ui/IconDialog/IconDialog.tsx +++ b/src/shared/ui/IconDialog/IconDialog.tsx @@ -11,23 +11,21 @@ import { import Image from "next/image"; interface IconDialogProps { + dialogTriggerItem: JSX.Element; dialogTitle: string; dialogDescription: string; renderItem: JSX.Element; } const IconDialog = ({ + dialogTriggerItem, dialogTitle, dialogDescription, renderItem, }: IconDialogProps) => { return ( - -
- share -
-
+ {dialogTriggerItem} {dialogTitle} diff --git a/src/shared/ui/ImageCarousel/ImageCarousel.tsx b/src/shared/ui/ImageCarousel/ImageCarousel.tsx index 3c28798..8ffa1ea 100644 --- a/src/shared/ui/ImageCarousel/ImageCarousel.tsx +++ b/src/shared/ui/ImageCarousel/ImageCarousel.tsx @@ -49,8 +49,12 @@ const ImageCarousel = ({ ); })} - {isPreviousIcon && } - {isNextIcon && } + {isPreviousIcon && ( + + )} + {isNextIcon && ( + + )} ); }; diff --git a/src/shared/ui/ImageDescription/ImageDescription.tsx b/src/shared/ui/ImageDescription/ImageDescription.tsx index 3b60e3d..26e5390 100644 --- a/src/shared/ui/ImageDescription/ImageDescription.tsx +++ b/src/shared/ui/ImageDescription/ImageDescription.tsx @@ -1,4 +1,5 @@ import Image from "next/image"; +import { twMerge } from "tailwind-merge"; interface ImageDescriptionProps { containerWidth: number; @@ -23,7 +24,10 @@ const ImageDescription = ({ }: ImageDescriptionProps) => { return (
{alt} diff --git a/src/shared/ui/InputLabel/InputLabel.tsx b/src/shared/ui/InputLabel/InputLabel.tsx index a580c34..4e881ae 100644 --- a/src/shared/ui/InputLabel/InputLabel.tsx +++ b/src/shared/ui/InputLabel/InputLabel.tsx @@ -63,7 +63,7 @@ const InputLabel = forwardRef( return (
-
+
{labelContent}
@@ -72,7 +72,7 @@ const InputLabel = forwardRef( placeholder={placeholder} value={value} disabled={disabled} - className="text-xl h-14 border-none shadow-none focus-visible:ring-0" + className="desktop:text-xl tablet:text-base desktop:h-14 tablet:h-11 border-none shadow-none focus-visible:ring-0 tablet:px-0 mobile:px-0" onChange={onChange} onBlur={onBlur} // onBlur 호출 ref={ref} diff --git a/src/shared/ui/LinkArrowLeft/LinkArrowLeft.tsx b/src/shared/ui/LinkArrowLeft/LinkArrowLeft.tsx new file mode 100644 index 0000000..f190d22 --- /dev/null +++ b/src/shared/ui/LinkArrowLeft/LinkArrowLeft.tsx @@ -0,0 +1,25 @@ +import Image from "next/image"; +import Link from "next/link"; + +interface LinkArrowLeftProps { + width: number; + height: number; + href: string; +} + +const LinkArrowLeft = (props: LinkArrowLeftProps) => { + const { width, height, href } = props; + + return ( + + back + + ); +}; + +export default LinkArrowLeft; diff --git a/src/shared/ui/LinkArrowLeft/index.ts b/src/shared/ui/LinkArrowLeft/index.ts new file mode 100644 index 0000000..8b4a921 --- /dev/null +++ b/src/shared/ui/LinkArrowLeft/index.ts @@ -0,0 +1 @@ +export { default as LinkArrowLeft } from "./LinkArrowLeft"; diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index 0b1fe1b..3092c51 100755 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -18,3 +18,5 @@ export * from "./ImageDescription"; export * from "./IconButton"; export * from "./UnifiedDialog"; export * from "./UnifiedTooltip"; +export * from "./LinkArrowLeft"; +export * from "./BackToPrevious"; diff --git a/tailwind.config.ts b/tailwind.config.ts index 2d6d3b5..f367ff0 100755 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -17,6 +17,7 @@ const config: Config = { fontFamily: { notoSansKr: ["var(--noto-sans-kr)"], roboto: ["var(--roboto)"], + pretendard: ["var(--font-pretendard"], }, borderRadius: { lg: "var(--radius)", @@ -30,6 +31,9 @@ const config: Config = { tooltipBackground: "#525252", buttonGrayBackground: "#F5F5F5", divGrayBackground: "#E5E5E5", + blackBackground: "#060606", + bannerBackground: "#CCCCCC", + footerBackground: "#3E3E3E", kakao: "#FEE500", purple: "#4F118C", textBlackColor: "#171717",