diff --git a/src/components/commons/Image/index.tsx b/src/components/commons/Image/index.tsx
index 09108ad..014bab4 100644
--- a/src/components/commons/Image/index.tsx
+++ b/src/components/commons/Image/index.tsx
@@ -1,12 +1,9 @@
-import styles from "./Image.module.scss";
+import { ImageInfo } from "@/type/type";
import classNames from "classnames/bind";
+import styles from "./Image.module.scss";
const cx = classNames.bind(styles);
-type ImageInfo = {
- src: string;
- alt: string;
-};
type ObjectFit = "fill" | "contain" | "cover" | "scale-down" | "none";
interface ImageProps {
diff --git a/src/components/domains/myPage/contents/Level/Level.module.scss b/src/components/domains/myPage/contents/Level/Level.module.scss
new file mode 100644
index 0000000..303f976
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/Level.module.scss
@@ -0,0 +1,15 @@
+.level {
+ padding-bottom: 10rem;
+
+ @include responsive("T") {
+ padding-bottom: 8rem;
+ }
+
+ &__level {
+ padding-bottom: 4.8rem;
+
+ &-info {
+ padding-bottom: 2.4rem;
+ }
+ }
+}
diff --git a/src/components/domains/myPage/contents/Level/LevelProgressBar/LevelProgressBar.module.scss b/src/components/domains/myPage/contents/Level/LevelProgressBar/LevelProgressBar.module.scss
new file mode 100644
index 0000000..b1612d5
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/LevelProgressBar/LevelProgressBar.module.scss
@@ -0,0 +1,57 @@
+.progress-bar {
+ &__track {
+ width: 100%;
+ height: 1rem;
+ background-color: $gray20;
+ border-radius: 0.5rem;
+
+ @include responsive("M") {
+ height: 0.8rem;
+ }
+
+ &-fill {
+ background: linear-gradient(90deg, $primary30, $primary50);
+ border-radius: 0.5rem;
+ height: 100%;
+ animation: fill-up 0.8s linear forwards;
+ }
+
+ .beginner {
+ --target-width: 20%;
+ }
+
+ .intermediate {
+ --target-width: 60%;
+ }
+
+ .expert {
+ --target-width: 100%;
+ }
+ }
+
+ &__labels {
+ @include flexbox(between, center);
+ padding-top: 1rem;
+
+ @include responsive("M") {
+ padding-top: 0.5rem;
+ }
+
+ &-el {
+ @include text-style(1.6, 500, $black30);
+
+ @include responsive("M") {
+ font-size: 1.4rem;
+ }
+ }
+ }
+}
+
+@keyframes fill-up {
+ from {
+ width: 0%;
+ }
+ to {
+ width: var(--target-width, 100%);
+ }
+}
diff --git a/src/components/domains/myPage/contents/Level/LevelProgressBar/index.tsx b/src/components/domains/myPage/contents/Level/LevelProgressBar/index.tsx
new file mode 100644
index 0000000..a611a7a
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/LevelProgressBar/index.tsx
@@ -0,0 +1,41 @@
+import Image from "@/components/commons/Image";
+import { IMAGES } from "@/constants/images";
+import { authState } from "@/recoil/authAtom";
+import classNames from "classnames/bind";
+import { useRecoilState } from "recoil";
+import styles from "./LevelProgressBar.module.scss";
+
+const cx = classNames.bind(styles);
+
+const LevelProgressBar = () => {
+ const [auth] = useRecoilState(authState);
+
+ if (!auth) {
+ return (
+
+
사용자 정보를 불러올 수 없습니다.
+
+
+ );
+ }
+
+ return (
+
+
+
+ 초보여행가
+ 중급여행가
+ 프로여행가
+
+
+ );
+};
+export default LevelProgressBar;
diff --git a/src/components/domains/myPage/contents/Level/MyBadge/Badge/Badge.module.scss b/src/components/domains/myPage/contents/Level/MyBadge/Badge/Badge.module.scss
new file mode 100644
index 0000000..87e9176
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyBadge/Badge/Badge.module.scss
@@ -0,0 +1,27 @@
+.badge {
+ @include column-flexbox();
+ gap: 1rem;
+
+ &__img {
+ border-radius: 2rem;
+ height: 8rem;
+ background-color: $gray20; // fix: temp
+
+ @include responsive("M") {
+ }
+ }
+
+ &__description {
+ @include column-flexbox();
+
+ &-title {
+ @include text-style(1.8, 700, $black30);
+ }
+
+ &-criteria {
+ @include text-style(1.4, 500, $black30);
+ word-break: keep-all;
+ text-align: center;
+ }
+ }
+}
diff --git a/src/components/domains/myPage/contents/Level/MyBadge/Badge/index.tsx b/src/components/domains/myPage/contents/Level/MyBadge/Badge/index.tsx
new file mode 100644
index 0000000..82842d5
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyBadge/Badge/index.tsx
@@ -0,0 +1,27 @@
+import Image from "@/components/commons/Image";
+
+import classNames from "classnames/bind";
+import styles from "./Badge.module.scss";
+
+const cx = classNames.bind(styles);
+
+interface BadgeProps {
+ img: string;
+ title: string;
+ criteria: string;
+}
+
+const Badge = ({ img, title, criteria }: BadgeProps) => {
+ return (
+
+
+
+
+
+ {title}
+ {criteria}
+
+
+ );
+};
+export default Badge;
diff --git a/src/components/domains/myPage/contents/Level/MyBadge/MyBadge.module.scss b/src/components/domains/myPage/contents/Level/MyBadge/MyBadge.module.scss
new file mode 100644
index 0000000..772076e
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyBadge/MyBadge.module.scss
@@ -0,0 +1,22 @@
+.my-badge {
+ &__title {
+ @include text-style(2.6, 700, $black30);
+ padding-bottom: 3.2rem;
+
+ @include responsive("M") {
+ font-size: 2.4rem;
+ padding-bottom: 2.4rem;
+ }
+ }
+
+ &__list {
+ display: grid;
+ grid-template-columns: repeat(4, 8rem);
+ justify-content: space-between;
+ row-gap: 2rem;
+
+ @include responsive("M") {
+ grid-template-columns: repeat(3, 8rem);
+ }
+ }
+}
diff --git a/src/components/domains/myPage/contents/Level/MyBadge/index.tsx b/src/components/domains/myPage/contents/Level/MyBadge/index.tsx
new file mode 100644
index 0000000..a4208ac
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyBadge/index.tsx
@@ -0,0 +1,34 @@
+import tempImg from "@/assets/images/child.svg";
+import Badge from "./Badge";
+
+import classNames from "classnames/bind";
+import styles from "./MyBadge.module.scss";
+
+const cx = classNames.bind(styles);
+
+const mockData = [
+ { id: 1, img: tempImg, title: "여행입문자", criteria: "첫 여행지를 작성한 여행가" },
+ { id: 2, img: tempImg, title: "여행입문자", criteria: "여행지를 n개 이상 작성한 여행가" },
+ { id: 3, img: tempImg, title: "여행입문자", criteria: "첫 여행지를 작성한 여행가" },
+ { id: 5, img: tempImg, title: "여행입문자", criteria: "첫 여행지를 작성한 여행가" },
+ { id: 6, img: tempImg, title: "여행입문자", criteria: "첫 여행지를 작성한 여행가" },
+ { id: 4, img: tempImg, title: "여행입문자", criteria: "첫 여행지를 작성한 여행가" },
+ { id: 7, img: tempImg, title: "여행입문자", criteria: "첫 여행지를 작성한 여행가" },
+ { id: 8, img: tempImg, title: "여행입문자", criteria: "첫 여행지를 작성한 여행가" },
+];
+
+const MyBadge = () => {
+ return (
+
+
내 뱃지
+
+ {mockData.map(({ id, img, title, criteria }) => (
+ -
+
+
+ ))}
+
+
+ );
+};
+export default MyBadge;
diff --git a/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/AllLevelInfoModal.module.scss b/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/AllLevelInfoModal.module.scss
new file mode 100644
index 0000000..ee6570f
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/AllLevelInfoModal.module.scss
@@ -0,0 +1,52 @@
+.all-level-info-modal {
+ @include column-flexbox();
+ gap: 0.32rem;
+ background-color: white;
+ border-radius: 1rem;
+ width: 64rem;
+ padding: 2.4rem;
+
+ @include responsive("M") {
+ width: calc(100% - 4rem);
+ padding: 2rem;
+ }
+
+ &__close-btn {
+ align-self: flex-end;
+ width: 2.4rem;
+ height: 2.4rem;
+ cursor: pointer;
+
+ @include responsive("M") {
+ width: 2rem;
+ height: 2rem;
+ }
+ }
+
+ &__header {
+ @include flexbox();
+
+ &-logo {
+ width: 8.4rem;
+
+ @include responsive("M") {
+ width: 6rem;
+ }
+ }
+
+ &-title {
+ @include text-style(1.6, 700, $black30);
+ text-align: center;
+
+ @include responsive("M") {
+ font-size: 1.2rem;
+ }
+ }
+ }
+
+ &__level-info-list {
+ &-element:not(:last-child) {
+ border-bottom: 0.1rem solid $gray20;
+ }
+ }
+}
diff --git a/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/LevelInfoBox/LevelInfoBox.module.scss b/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/LevelInfoBox/LevelInfoBox.module.scss
new file mode 100644
index 0000000..227a0e8
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/LevelInfoBox/LevelInfoBox.module.scss
@@ -0,0 +1,83 @@
+.level-info-box {
+ display: grid;
+ grid-template-areas:
+ "level requirement"
+ ". description";
+ justify-content: start;
+ align-items: center;
+ padding: 3rem 0;
+ gap: 1rem 1.4rem;
+
+ @include responsive("M") {
+ padding: 2.4rem 0;
+ gap: 0.8rem 1rem;
+ }
+
+ &__level {
+ @include flexbox();
+ gap: 0.7rem;
+ grid-area: level;
+
+ @include responsive("M") {
+ gap: 0.4rem;
+ }
+
+ &-img {
+ grid-area: img;
+ width: 4rem;
+ height: 4rem;
+ border-radius: 50%;
+ box-shadow: 0px 3px 16px 0px rgba(0, 0, 0, 0.08);
+ overflow: hidden;
+
+ @include responsive("M") {
+ width: 3.6rem;
+ height: 3.6rem;
+ }
+ }
+
+ &-name {
+ @include text-style(1.4, 700, $black30);
+
+ @include responsive("M") {
+ font-size: 1.2rem;
+ }
+ }
+ }
+
+ &__requirement {
+ @include text-style(1.4, 600, $black30);
+ grid-area: requirement;
+
+ @include responsive("M") {
+ font-size: 1.2rem;
+ }
+
+ &-additionalInfo {
+ @include text-style(1.1, 500, $black30);
+ display: block;
+
+ @include responsive("M") {
+ font-size: 1rem;
+ }
+ }
+ }
+
+ &__description {
+ @include text-style(1.3, 500, $black30);
+ grid-area: description;
+
+ @include responsive("M") {
+ font-size: 1.2rem;
+ }
+
+ &-additionalInfo {
+ @include text-style(1.1, 500, $black30);
+ display: block;
+
+ @include responsive("M") {
+ font-size: 1rem;
+ }
+ }
+ }
+}
diff --git a/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/LevelInfoBox/index.tsx b/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/LevelInfoBox/index.tsx
new file mode 100644
index 0000000..8102abc
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/LevelInfoBox/index.tsx
@@ -0,0 +1,41 @@
+import Image from "@/components/commons/Image";
+import { UserLevelInfo } from "@/type/type";
+
+import classNames from "classnames/bind";
+import styles from "./LevelInfoBox.module.scss";
+
+const cx = classNames.bind(styles);
+
+interface LevelInfoBoxProps {
+ info: UserLevelInfo;
+}
+
+const LevelInfoBox = ({ info }: LevelInfoBoxProps) => {
+ const { level, imgInfo, requirement, benefits } = info;
+
+ return (
+
+
+
+
+ {requirement.description}
+ {requirement.additionalInfo && (
+ {info.requirement.additionalInfo}
+ )}
+
+
+
+ {benefits.description}
+ {benefits.additionalInfo && (
+ {benefits.additionalInfo}
+ )}
+
+
+ );
+};
+export default LevelInfoBox;
diff --git a/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/index.tsx b/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/index.tsx
new file mode 100644
index 0000000..56813dc
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyLevel/AllLevelInfoModal/index.tsx
@@ -0,0 +1,41 @@
+import Image from "@/components/commons/Image";
+import Modal from "@/components/commons/Modal";
+import { IMAGES } from "@/constants/images";
+import { USER_LEVEL_INFO_LIST } from "@/constants/userlevelInfoList";
+import LevelInfoBox from "./LevelInfoBox";
+
+import classNames from "classnames/bind";
+import styles from "./AllLevelInfoModal.module.scss";
+
+const cx = classNames.bind(styles);
+
+interface AllLevelInfoModalProps {
+ isModalOpen: boolean;
+ onModalClose: () => void;
+}
+
+const AllLevelInfoModal = ({ isModalOpen, onModalClose }: AllLevelInfoModalProps) => {
+ return (
+
+
+
+
+
+ {USER_LEVEL_INFO_LIST.map((info) => (
+ -
+
+
+ ))}
+
+
+
+ );
+};
+export default AllLevelInfoModal;
diff --git a/src/components/domains/myPage/contents/Level/MyLevel/MyLevel.module.scss b/src/components/domains/myPage/contents/Level/MyLevel/MyLevel.module.scss
new file mode 100644
index 0000000..73bfcbc
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyLevel/MyLevel.module.scss
@@ -0,0 +1,43 @@
+.my-level {
+ &__all-info {
+ @include flexbox(stretch, center);
+ @include text-style(2.4, 700, $black30);
+ gap: 0.5rem;
+
+ @include responsive("M") {
+ font-size: 2rem;
+ gap: 0.3rem;
+ }
+
+ &-question-mark {
+ @include flexbox();
+ @include text-style(1.6, 600, $gray30);
+ border: 0.1rem solid $gray30;
+ border-radius: 50%;
+ width: 2.4rem;
+ height: 2.4rem;
+ cursor: pointer;
+
+ @include responsive("M") {
+ font-size: 1.2rem;
+ width: 2rem;
+ height: 2rem;
+ }
+ }
+ }
+
+ &__my-info {
+ @include text-style(3.2, 500, $black30);
+ padding-top: 2rem;
+
+ @include responsive("M") {
+ font-size: 2.4rem;
+ padding-top: 1.6rem;
+ }
+
+ &--highlight {
+ font-weight: 700;
+ color: $primary40;
+ }
+ }
+}
diff --git a/src/components/domains/myPage/contents/Level/MyLevel/index.tsx b/src/components/domains/myPage/contents/Level/MyLevel/index.tsx
new file mode 100644
index 0000000..972d5b1
--- /dev/null
+++ b/src/components/domains/myPage/contents/Level/MyLevel/index.tsx
@@ -0,0 +1,49 @@
+import { useState } from "react";
+
+import Image from "@/components/commons/Image";
+import { IMAGES } from "@/constants/images";
+import { authState } from "@/recoil/authAtom";
+import { useRecoilState } from "recoil";
+
+import classNames from "classnames/bind";
+import AllLevelInfoModal from "./AllLevelInfoModal";
+import styles from "./MyLevel.module.scss";
+
+const cx = classNames.bind(styles);
+
+const MyLevel = () => {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [auth] = useRecoilState(authState);
+
+ if (!auth) {
+ return (
+
+
사용자 정보를 불러올 수 없습니다.
+
+
+ );
+ }
+
+ const { nickname, role } = auth;
+
+ return (
+ <>
+
+
+ 내 등급
+
+
+
+ {nickname}
+ {" 님은"} {role}
+ {" 입니다."}
+
+
+
+ setIsModalOpen(false)} />
+ >
+ );
+};
+export default MyLevel;
diff --git a/src/components/domains/myPage/contents/Level/index.tsx b/src/components/domains/myPage/contents/Level/index.tsx
index 9aefa77..b38802d 100644
--- a/src/components/domains/myPage/contents/Level/index.tsx
+++ b/src/components/domains/myPage/contents/Level/index.tsx
@@ -1,8 +1,27 @@
+import LevelProgressBar from "./LevelProgressBar";
+import MyBadge from "./MyBadge";
+import MyLevel from "./MyLevel";
+
+import classNames from "classnames/bind";
+import styles from "./Level.module.scss";
+
+const cx = classNames.bind(styles);
+
const Level = () => {
return (
- <>
- Level Component
- >
+
);
};
export default Level;
diff --git a/src/constants/userlevelInfoList.ts b/src/constants/userlevelInfoList.ts
new file mode 100644
index 0000000..0f84f37
--- /dev/null
+++ b/src/constants/userlevelInfoList.ts
@@ -0,0 +1,40 @@
+import { UserLevelInfo } from "@/type/type";
+import { IMAGES } from "./images";
+
+export const USER_LEVEL_INFO_LIST: UserLevelInfo[] = [
+ {
+ id: 1,
+ level: "초보여행자",
+ imgInfo: IMAGES.PartyIcon,
+ requirement: {
+ description: "코스메이커 회원가입 시",
+ },
+ benefits: {
+ description: "여행지 등록/수정 요청 가능",
+ },
+ },
+ {
+ id: 2,
+ level: "중급여행자",
+ imgInfo: IMAGES.PartyIcon,
+ requirement: {
+ description: "리뷰 댓글 50개 작성 시",
+ },
+ benefits: {
+ description: "코스 작성 가능",
+ },
+ },
+ {
+ id: 3,
+ level: "프로여행자",
+ imgInfo: IMAGES.PartyIcon,
+ requirement: {
+ description: "당월 작성된 게시글 중 조회수 1등, 찜수 1등, 좋아요 1등 코스/여행지 작성자",
+ additionalInfo: "(매월 말일 기준으로 산정)",
+ },
+ benefits: {
+ description: "여행지 방문 시 특별 혜택",
+ additionalInfo: "(방문 시 로그인한 나의 등급 화면 제시 필요, 캡처한 이미지는 인정되지 않습니다.)",
+ },
+ },
+];
diff --git a/src/type/type.d.ts b/src/type/type.d.ts
index 571818f..5835111 100644
--- a/src/type/type.d.ts
+++ b/src/type/type.d.ts
@@ -2,6 +2,11 @@ import { CourseReview } from "@/api/course/type";
import { DestinationReview } from "@/api/destination/type";
import { tagResponseDto } from "@/api/tag/type";
+export type ImageInfo = {
+ src: string;
+ alt: string;
+};
+
export interface TabBarIconProps {
title: string;
color: string;
@@ -81,3 +86,14 @@ export interface RefinedReview {
export interface RequestOptions {
requireAuth: boolean;
}
+
+export interface UserLevelInfo {
+ id: number;
+ level: string;
+ imgInfo: ImageInfo;
+ requirement: { description: string; additionalInfo?: string };
+ benefits: {
+ description: string;
+ additionalInfo?: string;
+ };
+}