From ad6659b99e5818e7a00486ed33004f3118d7848e Mon Sep 17 00:00:00 2001 From: YuminPark Date: Tue, 23 Jun 2026 15:34:17 +0900 Subject: [PATCH 01/11] =?UTF-8?q?#37=20[CHORE]=20=EC=99=B8=EB=B6=80=20?= =?UTF-8?q?=EA=B5=AC=EA=B8=80=ED=8F=BC=20=EB=A7=81=ED=81=AC=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/instructor/choose/ui/DraftCheckSection.tsx | 9 +++++++-- .../instructor/revision/ui/RevisionCategorySection.tsx | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/widgets/instructor/choose/ui/DraftCheckSection.tsx b/src/widgets/instructor/choose/ui/DraftCheckSection.tsx index 3c85338..15d1543 100644 --- a/src/widgets/instructor/choose/ui/DraftCheckSection.tsx +++ b/src/widgets/instructor/choose/ui/DraftCheckSection.tsx @@ -53,9 +53,14 @@ const DraftCheckSection = ({ drafts, selectedIndex, onSelect }: DraftCheckSectio ); })} - + ); }; diff --git a/src/widgets/instructor/revision/ui/RevisionCategorySection.tsx b/src/widgets/instructor/revision/ui/RevisionCategorySection.tsx index 8c846df..2502fb8 100644 --- a/src/widgets/instructor/revision/ui/RevisionCategorySection.tsx +++ b/src/widgets/instructor/revision/ui/RevisionCategorySection.tsx @@ -39,9 +39,14 @@ const RevisionCategorySection = ({ /{maxRevisionCount}) {remainingRevisionCount === 0 && ( - + )}

시안 수정은 총 3회 수정이 가능합니다.

From b92677b7954ace2202f47acd0303d6906c4d1f60 Mon Sep 17 00:00:00 2001 From: YuminPark Date: Tue, 23 Jun 2026 15:49:15 +0900 Subject: [PATCH 02/11] =?UTF-8?q?#37=20[FEAT]=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=B4=EB=A6=84=20=EC=BF=A0=ED=82=A4=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.ts | 9 +++++- src/app/instructor/my/page.tsx | 10 +++++-- src/app/instructor/page.tsx | 9 ++++-- src/features/login/model/useLoginForm.ts | 7 ++++- src/shared/lib/auth/client.ts | 29 ++++++++++++++++--- src/shared/ui/Header.tsx | 17 ++++++++++- .../instructor/my/ui/MyInfoSection.tsx | 23 +++++++++++++-- 7 files changed, 90 insertions(+), 14 deletions(-) diff --git a/next.config.ts b/next.config.ts index 566197c..2b92da0 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,8 +1,15 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ reactCompiler: true, + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "ditda-public-bucket.s3.ap-northeast-2.amazonaws.com", + }, + ], + }, turbopack: { rules: { "*.svg": { diff --git a/src/app/instructor/my/page.tsx b/src/app/instructor/my/page.tsx index b8b2e26..a31f1b5 100644 --- a/src/app/instructor/my/page.tsx +++ b/src/app/instructor/my/page.tsx @@ -1,11 +1,17 @@ +import { cookies } from "next/headers"; + import { CommissionsHistorySection, MyInfoSection } from "@/widgets/instructor/my"; -const page = () => { +const page = async () => { + const cookieStore = await cookies(); + const name = cookieStore.get("userName")?.value ?? ""; + const profileImageUrl = cookieStore.get("userProfileImageUrl")?.value; + return (

마이페이지

- +
diff --git a/src/app/instructor/page.tsx b/src/app/instructor/page.tsx index 929fcc6..fbd4625 100644 --- a/src/app/instructor/page.tsx +++ b/src/app/instructor/page.tsx @@ -1,13 +1,18 @@ +import { cookies } from "next/headers"; + import { DraftSubmissionStatusSection, MatchingCommissionsSection, ModifyingCommissionsSection, } from "@/widgets/instructor/home"; -const page = () => { +const page = async () => { + const cookieStore = await cookies(); + const name = cookieStore.get("userName")?.value ?? ""; + return (
-

다현님, 어서오세요!

+

{name}님, 어서오세요!

diff --git a/src/features/login/model/useLoginForm.ts b/src/features/login/model/useLoginForm.ts index 7b41219..151e73c 100644 --- a/src/features/login/model/useLoginForm.ts +++ b/src/features/login/model/useLoginForm.ts @@ -59,7 +59,12 @@ export const useLoginForm = () => { throw new Error("사용자 유형을 확인할 수 없습니다"); } - setClientAuth({ accessToken: result.accessToken, role: userRole }); + setClientAuth({ + accessToken: result.accessToken, + role: userRole, + name: result.name, + profileImageUrl: result.profileImageUrl, + }); router.push(getClientUserHomePath(userRole)); } catch (error) { setErrorMessage(error instanceof Error ? error.message : "요청 처리 중 문제가 발생했습니다"); diff --git a/src/shared/lib/auth/client.ts b/src/shared/lib/auth/client.ts index 255dafd..9058a0b 100644 --- a/src/shared/lib/auth/client.ts +++ b/src/shared/lib/auth/client.ts @@ -2,6 +2,8 @@ export type ClientUserRole = "designer" | "instructor"; const ACCESS_TOKEN_COOKIE_NAME = "accessToken"; const USER_ROLE_COOKIE_NAME = "userRole"; +const USER_NAME_COOKIE_NAME = "userName"; +const USER_PROFILE_IMAGE_COOKIE_NAME = "userProfileImageUrl"; const ACCESS_TOKEN_MAX_AGE_SECONDS = 60 * 60; const getCookieValue = (name: string) => { @@ -51,6 +53,8 @@ const parseJwtPayload = (token: string): Record | null => { }; export const getClientAccessToken = () => getCookieValue(ACCESS_TOKEN_COOKIE_NAME); +export const getClientUserName = () => getCookieValue(USER_NAME_COOKIE_NAME); +export const getClientProfileImageUrl = () => getCookieValue(USER_PROFILE_IMAGE_COOKIE_NAME); export const setClientAccessToken = (accessToken: string) => { document.cookie = `${ACCESS_TOKEN_COOKIE_NAME}=${encodeURIComponent( @@ -73,23 +77,40 @@ export const getClientUserRoleFromAccessToken = (accessToken: string) => { export const setClientAuth = ({ accessToken, role, + name, + profileImageUrl, }: { accessToken: string; role?: ClientUserRole; + name?: string; + profileImageUrl?: string; }) => { setClientAccessToken(accessToken); if (role == null) { document.cookie = `${USER_ROLE_COOKIE_NAME}=; ${createCookieOptions(0)}`; - return; + } else { + document.cookie = `${USER_ROLE_COOKIE_NAME}=${encodeURIComponent(role)}; ${createCookieOptions( + ACCESS_TOKEN_MAX_AGE_SECONDS, + )}`; } - document.cookie = `${USER_ROLE_COOKIE_NAME}=${encodeURIComponent(role)}; ${createCookieOptions( - ACCESS_TOKEN_MAX_AGE_SECONDS, - )}`; + if (name != null) { + document.cookie = `${USER_NAME_COOKIE_NAME}=${encodeURIComponent(name)}; ${createCookieOptions( + ACCESS_TOKEN_MAX_AGE_SECONDS, + )}`; + } + + if (profileImageUrl != null) { + document.cookie = `${USER_PROFILE_IMAGE_COOKIE_NAME}=${encodeURIComponent(profileImageUrl)}; ${createCookieOptions( + ACCESS_TOKEN_MAX_AGE_SECONDS, + )}`; + } }; export const clearClientAuth = () => { document.cookie = `${ACCESS_TOKEN_COOKIE_NAME}=; ${createCookieOptions(0)}`; document.cookie = `${USER_ROLE_COOKIE_NAME}=; ${createCookieOptions(0)}`; + document.cookie = `${USER_NAME_COOKIE_NAME}=; ${createCookieOptions(0)}`; + document.cookie = `${USER_PROFILE_IMAGE_COOKIE_NAME}=; ${createCookieOptions(0)}`; }; diff --git a/src/shared/ui/Header.tsx b/src/shared/ui/Header.tsx index c6091dc..bcb5253 100644 --- a/src/shared/ui/Header.tsx +++ b/src/shared/ui/Header.tsx @@ -1,5 +1,6 @@ "use client"; +import Image from "next/image"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; @@ -7,6 +8,7 @@ import { EnterIcon, ProfileCircleIcon } from "@/shared/assets/icons"; import { PurpleLogo } from "@/shared/assets/logos"; import { type ClientUserRole, + getClientProfileImageUrl, getClientUserRoleFromAccessToken, normalizeClientUserRole, } from "@/shared/lib/auth/client"; @@ -14,6 +16,7 @@ import { interface AuthState { isLoggedIn: boolean; role: ClientUserRole | null; + profileImageUrl: string | null; } const ACCESS_TOKEN_COOKIE_NAME = "accessToken"; @@ -38,6 +41,7 @@ const Header = () => { const [authState, setAuthState] = useState({ isLoggedIn: false, role: null, + profileImageUrl: null, }); useEffect(() => { @@ -51,6 +55,7 @@ const Header = () => { setAuthState({ isLoggedIn: accessToken != null && role != null, role, + profileImageUrl: getClientProfileImageUrl() ?? null, }); }; @@ -76,7 +81,17 @@ const Header = () => {
{authState.isLoggedIn ? ( - + {authState.profileImageUrl != null ? ( + 프로필 + ) : ( + + )} 내 계정 ) : ( diff --git a/src/widgets/instructor/my/ui/MyInfoSection.tsx b/src/widgets/instructor/my/ui/MyInfoSection.tsx index b80fbae..a278ef9 100644 --- a/src/widgets/instructor/my/ui/MyInfoSection.tsx +++ b/src/widgets/instructor/my/ui/MyInfoSection.tsx @@ -1,13 +1,30 @@ +import Image from "next/image"; + import { ProfileCircleIcon } from "@/shared/assets/icons"; import { myInfoData } from "@/widgets/instructor/my/model/my"; -const MyInfoSection = () => { - const { name, stats } = myInfoData; +interface MyInfoSectionProps { + name: string; + profileImageUrl?: string; +} + +const MyInfoSection = ({ name, profileImageUrl }: MyInfoSectionProps) => { + const { stats } = myInfoData; return (
- + {profileImageUrl != null ? ( + 프로필 + ) : ( + + )}

{name}

From 8c1fb824e6601339608dbf68b1ee56255c0e4f7b Mon Sep 17 00:00:00 2001 From: YuminPark Date: Tue, 23 Jun 2026 15:57:21 +0900 Subject: [PATCH 03/11] =?UTF-8?q?#37=20[MOD]=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=EB=A1=9C=EA=B3=A0=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0=EB=B3=84=20=ED=99=88=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/Header.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/shared/ui/Header.tsx b/src/shared/ui/Header.tsx index bcb5253..df89e78 100644 --- a/src/shared/ui/Header.tsx +++ b/src/shared/ui/Header.tsx @@ -2,6 +2,7 @@ import Image from "next/image"; import Link from "next/link"; +import { usePathname } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import { EnterIcon, ProfileCircleIcon } from "@/shared/assets/icons"; @@ -65,6 +66,15 @@ const Header = () => { return () => window.removeEventListener("focus", syncAuthState); }, []); + const pathname = usePathname(); + + const logoHref = useMemo(() => { + if (!authState.isLoggedIn) return "/"; + if (pathname.startsWith("/instructor")) return "/instructor"; + if (pathname.startsWith("/designer")) return "/designer"; + return "/"; + }, [authState.isLoggedIn, pathname]); + const accountHref = useMemo(() => { if (authState.role == null) return "/login"; @@ -73,7 +83,9 @@ const Header = () => { return (
- + + +

이용방식 안내

1:1 문의하기

From 400f2b6f470f8f1254a21b3b42ae9e130dd9ba60 Mon Sep 17 00:00:00 2001 From: YuminPark Date: Tue, 23 Jun 2026 16:26:29 +0900 Subject: [PATCH 04/11] =?UTF-8?q?#37=20[FEAT]=20=EC=83=88=20=EC=99=B8?= =?UTF-8?q?=EC=A3=BC=20=EC=9E=91=EC=84=B1=20=ED=94=8C=EB=9E=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/instructor/write/api/getPlans.ts | 22 +++++++++++ src/features/instructor/write/config/write.ts | 38 +++---------------- src/features/instructor/write/index.ts | 2 + .../instructor/write/model/writeFormStore.ts | 9 ++--- .../write/ui/PaymentModal/Step1.tsx | 6 +-- .../write/ui/PaymentModal/Step2.tsx | 3 +- .../instructor/write/ui/PlanChooseCard.tsx | 17 +++++---- .../instructor/write/ui/PlanChooseSection.tsx | 20 +++++++--- 8 files changed, 62 insertions(+), 55 deletions(-) create mode 100644 src/features/instructor/write/api/getPlans.ts diff --git a/src/features/instructor/write/api/getPlans.ts b/src/features/instructor/write/api/getPlans.ts new file mode 100644 index 0000000..984779e --- /dev/null +++ b/src/features/instructor/write/api/getPlans.ts @@ -0,0 +1,22 @@ +import type { PlanType } from "@/features/instructor/write/config/write"; +import { api, createApiPath } from "@/shared/api/client"; +import type { ApiResponse } from "@/shared/api/types"; + +export type Plan = { + code: PlanType; + designerCount: number; + price: number; + description: string; +}; + +type GetPlansResult = { + plans: Plan[]; +}; + +export const getPlans = async (): Promise => { + const response = await api + .get(createApiPath("/api/v1/instructors/commissions/plans")) + .json>(); + + return response.result?.plans ?? []; +}; diff --git a/src/features/instructor/write/config/write.ts b/src/features/instructor/write/config/write.ts index eef415b..ba04ac8 100644 --- a/src/features/instructor/write/config/write.ts +++ b/src/features/instructor/write/config/write.ts @@ -1,7 +1,6 @@ import React from "react"; import { OneCircleIcon, ThreeCircleIcon, TwoCircleIcon } from "@/shared/assets/icons"; -import { BadgeVariant } from "@/shared/ui/Badge"; export type WriteStep = 1 | 2 | 3; @@ -97,39 +96,12 @@ export type TermsItem = { sections?: TermsSection[]; }; -export type PlanType = "기본" | "플러스" | "맥스"; +export type PlanType = "BASIC" | "PLUS" | "MAX"; -export const PLAN_MAP: Record< - PlanType, - { label: string; size: BadgeVariant; price: string; description: string } -> = { - 기본: { - label: "기본 플랜", - size: "3인", - price: "400,000원", - description: "디자이너 3명에 대한 시안을 받아볼 수 있습니다.", - }, - 플러스: { - label: "플러스 플랜", - size: "4인", - price: "480,000원", - description: "더 다양한 디자이너의 시안을 받아볼 수 있습니다. ", - }, - 맥스: { - label: "맥스 플랜", - size: "5인", - price: "560,000원", - description: "가장 많은 디자이너의 시안을 받아볼 수 있습니다. ", - }, -}; - -/* ========================= - ENUM Mapping for API (최종x) - ========================= */ -export const PLAN_API_MAP: Record = { - 기본: "BASIC", - 플러스: "PLUS", - 맥스: "MAX", +export const PLAN_LABEL_MAP: Record = { + BASIC: "기본 플랜", + PLUS: "플러스 플랜", + MAX: "맥스 플랜", }; export const CATEGORY_API_MAP: Record = { diff --git a/src/features/instructor/write/index.ts b/src/features/instructor/write/index.ts index 8bc8831..893094f 100644 --- a/src/features/instructor/write/index.ts +++ b/src/features/instructor/write/index.ts @@ -1,3 +1,5 @@ +export type { Plan } from "./api/getPlans"; +export { getPlans } from "./api/getPlans"; export * from "./config/write"; export type { RgbaColor } from "./lib/color"; export { formatDate, getMinFinalDate, getYesterday } from "./lib/date"; diff --git a/src/features/instructor/write/model/writeFormStore.ts b/src/features/instructor/write/model/writeFormStore.ts index 79f6418..167d502 100644 --- a/src/features/instructor/write/model/writeFormStore.ts +++ b/src/features/instructor/write/model/writeFormStore.ts @@ -1,11 +1,10 @@ import { create } from "zustand"; +import type { Plan } from "@/features/instructor/write/api/getPlans"; import { CATEGORY_API_MAP, KEYWORD_API_MAP, PAGE_API_MAP, - PLAN_API_MAP, - type PlanType, SIZE_API_MAP, type WriteStep, } from "@/features/instructor/write/config/write"; @@ -45,7 +44,7 @@ interface WriteFormState { referenceFiles: UploadedFile[]; materialDescription: string; referenceDescription: string; - selectedPlan: PlanType | null; + selectedPlan: Plan | null; firstDate: Date | null; finalDate: Date | null; isTermsAgreed: boolean; @@ -65,7 +64,7 @@ interface WriteFormState { setReferenceFiles: (files: UploadedFile[]) => void; setMaterialDescription: (value: string) => void; setReferenceDescription: (value: string) => void; - setSelectedPlan: (value: PlanType | null) => void; + setSelectedPlan: (value: Plan | null) => void; setFirstDate: (value: Date | null) => void; setFinalDate: (value: Date | null) => void; setIsTermsAgreed: (value: boolean) => void; @@ -130,7 +129,7 @@ const buildOrderRequest = (state: WriteFormState): WriteOrderRequest => { })), ...(state.materialDescription ? { materialDescription: state.materialDescription } : {}), ...(state.referenceDescription ? { referenceDescription: state.referenceDescription } : {}), - plan: state.selectedPlan ? PLAN_API_MAP[state.selectedPlan] : "", + plan: state.selectedPlan?.code ?? "", dates: [ { firstDraftDeadline: state.firstDate ? toApiDate(state.firstDate) : "", diff --git a/src/features/instructor/write/ui/PaymentModal/Step1.tsx b/src/features/instructor/write/ui/PaymentModal/Step1.tsx index 4c74263..4230f20 100644 --- a/src/features/instructor/write/ui/PaymentModal/Step1.tsx +++ b/src/features/instructor/write/ui/PaymentModal/Step1.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { - PLAN_MAP, + PLAN_LABEL_MAP, SIZE_DISPLAY_MAP, TERMS_CONTENT, } from "@/features/instructor/write/config/write"; @@ -130,7 +130,7 @@ const Step1 = ({ onNext, errorMessage }: { onNext: () => void; errorMessage?: st
{selectedCategory && } {selectedSize && } - {selectedPlan && } + {selectedPlan && } {selectedPages.length > 0 && ( {selectedPages.map(p => ( @@ -170,7 +170,7 @@ const Step1 = ({ onNext, errorMessage }: { onNext: () => void; errorMessage?: st

최종 금액

- {selectedPlan ? PLAN_MAP[selectedPlan].price : "-"} + {selectedPlan ? `${selectedPlan.price.toLocaleString("ko-KR")}원` : "-"}

{errorMessage && ( diff --git a/src/features/instructor/write/ui/PaymentModal/Step2.tsx b/src/features/instructor/write/ui/PaymentModal/Step2.tsx index 9f240a0..668e8d5 100644 --- a/src/features/instructor/write/ui/PaymentModal/Step2.tsx +++ b/src/features/instructor/write/ui/PaymentModal/Step2.tsx @@ -2,7 +2,6 @@ import { useRouter } from "next/navigation"; -import { PLAN_MAP } from "@/features/instructor/write/config/write"; import { useWriteFormStore } from "@/features/instructor/write/model/writeFormStore"; import { ArrowLeftIcon, ExclamationMarkCircleIcon } from "@/shared/assets/icons"; import Button from "@/shared/ui/Button"; @@ -39,7 +38,7 @@ const Step2 = ({ onBack }: { onBack: () => void }) => {

이체 금액

- {selectedPlan ? PLAN_MAP[selectedPlan].price : "-"} + {selectedPlan ? `${selectedPlan.price.toLocaleString("ko-KR")}원` : "-"}

diff --git a/src/features/instructor/write/ui/PlanChooseCard.tsx b/src/features/instructor/write/ui/PlanChooseCard.tsx index b69fa14..e9ddf1b 100644 --- a/src/features/instructor/write/ui/PlanChooseCard.tsx +++ b/src/features/instructor/write/ui/PlanChooseCard.tsx @@ -1,14 +1,17 @@ -import { PLAN_MAP, PlanType } from "@/features/instructor/write/config/write"; -import Badge from "@/shared/ui/Badge"; +import type { Plan } from "@/features/instructor/write/api/getPlans"; +import { PLAN_LABEL_MAP } from "@/features/instructor/write/config/write"; +import Badge, { type BadgeVariant } from "@/shared/ui/Badge"; interface PlanChooseCardProps { - plan: PlanType; + plan: Plan; isSelected?: boolean; onClick?: () => void; } const PlanChooseCard = ({ plan, isSelected = false, onClick }: PlanChooseCardProps) => { - const { label, size, price, description } = PLAN_MAP[plan]; + const label = PLAN_LABEL_MAP[plan.code]; + const badgeVariant = `${plan.designerCount}인` as BadgeVariant; + const formattedPrice = `${plan.price.toLocaleString("ko-KR")}원`; return (
{label} - +
-

{description}

+

{plan.description}

-

{price}

+

{formattedPrice}

); diff --git a/src/widgets/instructor/write/ui/PlanChooseSection.tsx b/src/widgets/instructor/write/ui/PlanChooseSection.tsx index 43840d7..446ca28 100644 --- a/src/widgets/instructor/write/ui/PlanChooseSection.tsx +++ b/src/widgets/instructor/write/ui/PlanChooseSection.tsx @@ -1,11 +1,21 @@ "use client"; -import { PlanChooseCard, type PlanType, useWriteFormStore } from "@/features/instructor/write"; +import { useEffect, useState } from "react"; -const PLANS: PlanType[] = ["기본", "플러스", "맥스"]; +import { + getPlans, + type Plan, + PlanChooseCard, + useWriteFormStore, +} from "@/features/instructor/write"; const PlanChooseSection = () => { const { selectedPlan, setSelectedPlan } = useWriteFormStore(); + const [plans, setPlans] = useState([]); + + useEffect(() => { + getPlans().then(setPlans).catch(console.error); + }, []); return (
@@ -16,11 +26,11 @@ const PlanChooseSection = () => {
- {PLANS.map(plan => ( + {plans.map(plan => ( setSelectedPlan(plan)} /> ))} From 496e327629d914f009390b2d4fa7dbcd81fbcd2c Mon Sep 17 00:00:00 2001 From: YuminPark Date: Tue, 23 Jun 2026 16:55:13 +0900 Subject: [PATCH 05/11] =?UTF-8?q?#37=20[MOD]=20=EA=B0=95=EC=82=AC=20?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=97=86=EC=9D=84=20=EB=95=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/ui/DraftSubmissionStatusSection.tsx | 16 +++++++++++----- .../home/ui/MatchingCommissionsSection.tsx | 16 +++++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx b/src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx index 4d13e45..74f3f2c 100644 --- a/src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx +++ b/src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx @@ -34,11 +34,17 @@ const DraftSubmissionStatusSection = () => { ))}
-
- - - -
+ {pageItems.length === 0 ? ( +
+ 진행중인 외주가 없습니다 +
+ ) : ( +
+ + + +
+ )}
); diff --git a/src/widgets/instructor/home/ui/MatchingCommissionsSection.tsx b/src/widgets/instructor/home/ui/MatchingCommissionsSection.tsx index 125a567..a99f941 100644 --- a/src/widgets/instructor/home/ui/MatchingCommissionsSection.tsx +++ b/src/widgets/instructor/home/ui/MatchingCommissionsSection.tsx @@ -33,11 +33,17 @@ const MatchingCommissionsSection = () => { ))} -
- - - -
+ {pageItems.length === 0 ? ( +
+ 진행중인 외주가 없습니다 +
+ ) : ( +
+ + + +
+ )} ); From ba2b27c11e47693f2d5895258697ee282c10d6f5 Mon Sep 17 00:00:00 2001 From: YuminPark Date: Tue, 23 Jun 2026 16:57:51 +0900 Subject: [PATCH 06/11] =?UTF-8?q?#37=20[CHORE]=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=97=86=EC=9D=84=20=EB=95=8C=20=EB=A9=98=ED=8A=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/instructor/home/model/home.ts | 109 +----------------- .../home/ui/DraftSubmissionStatusSection.tsx | 2 +- .../home/ui/MatchingCommissionsSection.tsx | 2 +- .../home/ui/ModifyingCommissionsSection.tsx | 16 ++- 4 files changed, 16 insertions(+), 113 deletions(-) diff --git a/src/features/instructor/home/model/home.ts b/src/features/instructor/home/model/home.ts index 247c951..e526c80 100644 --- a/src/features/instructor/home/model/home.ts +++ b/src/features/instructor/home/model/home.ts @@ -14,43 +14,7 @@ export const CATEGORY_DISPLAY_MAP: Record = { FLYER_TEXTBOOK_COVER_INNER: "교재 외지/내지", }; -export const draftSubmissionStatusData: DraftSubmissionItem[] = [ - { - commissionId: 11, - title: "해커스톡 왕초보 영어 - 기초 문법편", - category: "FLYER_TEXTBOOK_COVER_INNER", - draftSubmission: { submitted: 4, total: 4 }, - firstDraftDeadline: "2026-06-10", - }, - { - commissionId: 12, - title: "토익 실전 모의고사 - Part 5/6", - category: "FLYER_TEXTBOOK_COVER_INNER", - draftSubmission: { submitted: 3, total: 5 }, - firstDraftDeadline: "2026-06-18", - }, - { - commissionId: 13, - title: "수능 영어 독해 - 빈칸추론 완성", - category: "FLYER_TEXTBOOK_COVER_INNER", - draftSubmission: { submitted: 0, total: 3 }, - firstDraftDeadline: "2026-06-22", - }, - { - commissionId: 14, - title: "중학 수학 개념서 - 1학기 과정", - category: "FLYER_TEXTBOOK_COVER_INNER", - draftSubmission: { submitted: 2, total: 4 }, - firstDraftDeadline: "2026-07-01", - }, - { - commissionId: 15, - title: "고등 국어 문학 - 현대시 집중", - category: "FLYER_TEXTBOOK_COVER_INNER", - draftSubmission: { submitted: 5, total: 5 }, - firstDraftDeadline: "2026-06-28", - }, -]; +export const draftSubmissionStatusData: DraftSubmissionItem[] = []; // [강사] [대시보드 조회] 매칭 중인 외주 조회 export type MatchingItem = { @@ -63,38 +27,7 @@ export type MatchingItem = { }; }; -export const matchingStatusData: MatchingItem[] = [ - { - commissionId: 34, - title: "중등 과학 탐구 — 물질과 에너지", - matching: { matched: 3, total: 5 }, - finalDeadline: "2026-06-17", - }, - { - commissionId: 35, - title: "고등 수학 II — 미적분 집중 완성", - matching: { matched: 2, total: 5 }, - finalDeadline: "2026-06-23", - }, - { - commissionId: 36, - title: "중등 영어 — 독해 및 문법 완성", - matching: { matched: 3, total: 4 }, - finalDeadline: "2026-07-05", - }, - { - commissionId: 37, - title: "초등 과학 — 생물과 환경 탐구", - matching: { matched: 1, total: 3 }, - finalDeadline: "2026-06-30", - }, - { - commissionId: 38, - title: "고등 물리학 I — 역학과 에너지", - matching: { matched: 4, total: 5 }, - finalDeadline: "2026-07-15", - }, -]; +export const matchingStatusData: MatchingItem[] = []; // [강사] [대시보드 조회] 수정 중인 외주 조회 export type ModifyingItem = { @@ -105,40 +38,4 @@ export type ModifyingItem = { finalDeadline: string; }; -export const modifyingStatusData: ModifyingItem[] = [ - { - commissionId: 42, - title: "중등 수학 — 방정식과 함수 개념서", - isSubmitted: false, - hasUpdate: true, - finalDeadline: "2026-06-20", - }, - { - commissionId: 43, - title: "고등 영어 독해 — 수능 유형별 완성", - isSubmitted: true, - hasUpdate: false, - finalDeadline: "2026-06-28", - }, - { - commissionId: 44, - title: "초등 국어 — 받아쓰기 및 독해 기초", - isSubmitted: false, - hasUpdate: false, - finalDeadline: "2026-07-03", - }, - { - commissionId: 45, - title: "한국사 능력검정 — 중급 핵심 요약", - isSubmitted: false, - hasUpdate: true, - finalDeadline: "2026-07-10", - }, - { - commissionId: 46, - title: "고등 화학 I — 원소와 화학 반응", - isSubmitted: false, - hasUpdate: false, - finalDeadline: "2026-07-18", - }, -]; +export const modifyingStatusData: ModifyingItem[] = []; diff --git a/src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx b/src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx index 74f3f2c..1ddf71b 100644 --- a/src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx +++ b/src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx @@ -36,7 +36,7 @@ const DraftSubmissionStatusSection = () => { {pageItems.length === 0 ? (
- 진행중인 외주가 없습니다 + 제출된 시안이 없습니다
) : (
diff --git a/src/widgets/instructor/home/ui/MatchingCommissionsSection.tsx b/src/widgets/instructor/home/ui/MatchingCommissionsSection.tsx index a99f941..573466b 100644 --- a/src/widgets/instructor/home/ui/MatchingCommissionsSection.tsx +++ b/src/widgets/instructor/home/ui/MatchingCommissionsSection.tsx @@ -35,7 +35,7 @@ const MatchingCommissionsSection = () => {
{pageItems.length === 0 ? (
- 진행중인 외주가 없습니다 + 매칭 중인 외주가 없습니다
) : (
diff --git a/src/widgets/instructor/home/ui/ModifyingCommissionsSection.tsx b/src/widgets/instructor/home/ui/ModifyingCommissionsSection.tsx index 2399a76..f884411 100644 --- a/src/widgets/instructor/home/ui/ModifyingCommissionsSection.tsx +++ b/src/widgets/instructor/home/ui/ModifyingCommissionsSection.tsx @@ -33,11 +33,17 @@ const ModifyingCommissionsSection = () => { ))}
-
- - - -
+ {pageItems.length === 0 ? ( +
+ 수정 중인 외주가 없습니다 +
+ ) : ( +
+ + + +
+ )} ); From c757da975053cb922a70f7961ad47b53988bb571 Mon Sep 17 00:00:00 2001 From: YuminPark Date: Tue, 23 Jun 2026 21:31:03 +0900 Subject: [PATCH 07/11] =?UTF-8?q?#37=20[FEAT]=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=A4=91=EC=9D=B8=20=EC=99=B8=EC=A3=BC=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/instructor/home/api/getRevisions.ts | 15 +++++++++++++++ src/features/instructor/home/index.ts | 8 ++------ src/features/instructor/home/model/home.ts | 4 +--- .../home/ui/ModifyingCommissionsRow.tsx | 10 ++++++---- src/shared/ui/Header.tsx | 2 +- .../home/ui/ModifyingCommissionsSection.tsx | 15 ++++++++++++--- 6 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 src/features/instructor/home/api/getRevisions.ts diff --git a/src/features/instructor/home/api/getRevisions.ts b/src/features/instructor/home/api/getRevisions.ts new file mode 100644 index 0000000..b02cfdc --- /dev/null +++ b/src/features/instructor/home/api/getRevisions.ts @@ -0,0 +1,15 @@ +import type { ModifyingItem } from "@/features/instructor/home/model/home"; +import { api, createApiPath } from "@/shared/api/client"; +import type { ApiResponse } from "@/shared/api/types"; + +type GetRevisionsResult = { + commissions: ModifyingItem[]; +}; + +export const getRevisions = async (): Promise => { + const response = await api + .get(createApiPath("/api/v1/instructors/dashboards/revisions")) + .json>(); + + return response.result?.commissions ?? []; +}; diff --git a/src/features/instructor/home/index.ts b/src/features/instructor/home/index.ts index 6eaf5dc..971304d 100644 --- a/src/features/instructor/home/index.ts +++ b/src/features/instructor/home/index.ts @@ -1,11 +1,7 @@ +export { getRevisions } from "./api/getRevisions"; export { getDDay } from "./lib/getDDay"; export type { DraftSubmissionItem, MatchingItem, ModifyingItem } from "./model/home"; -export { - CATEGORY_DISPLAY_MAP, - draftSubmissionStatusData, - matchingStatusData, - modifyingStatusData, -} from "./model/home"; +export { CATEGORY_DISPLAY_MAP, draftSubmissionStatusData, matchingStatusData } from "./model/home"; export { default as CommissionsHeader } from "./ui/CommissionsHeader"; export { default as DraftSubmissionStatusRow } from "./ui/DraftSubmissionStatusRow"; export { default as MatchingCommissionsRow } from "./ui/MatchingCommissionsRow"; diff --git a/src/features/instructor/home/model/home.ts b/src/features/instructor/home/model/home.ts index e526c80..8aab488 100644 --- a/src/features/instructor/home/model/home.ts +++ b/src/features/instructor/home/model/home.ts @@ -34,8 +34,6 @@ export type ModifyingItem = { commissionId: number; title: string; isSubmitted: boolean; - hasUpdate: boolean; + hasUpdated: boolean; finalDeadline: string; }; - -export const modifyingStatusData: ModifyingItem[] = []; diff --git a/src/features/instructor/home/ui/ModifyingCommissionsRow.tsx b/src/features/instructor/home/ui/ModifyingCommissionsRow.tsx index 9573f50..80eeaeb 100644 --- a/src/features/instructor/home/ui/ModifyingCommissionsRow.tsx +++ b/src/features/instructor/home/ui/ModifyingCommissionsRow.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useRouter } from "next/navigation"; import { getDDay } from "@/features/instructor/home/lib/getDDay"; @@ -8,15 +10,15 @@ import Tag from "@/shared/ui/Tag"; const ModifyingCommissionsRow = ({ item }: { item: ModifyingItem }) => { const router = useRouter(); - const { commissionId, title, finalDeadline, isSubmitted, hasUpdate } = item; + const { commissionId, title, finalDeadline, isSubmitted, hasUpdated } = item; return (
-
+
-
- {hasUpdate &&
} +
+ {hasUpdated &&
}

{title}

diff --git a/src/shared/ui/Header.tsx b/src/shared/ui/Header.tsx index df89e78..c40265b 100644 --- a/src/shared/ui/Header.tsx +++ b/src/shared/ui/Header.tsx @@ -87,7 +87,7 @@ const Header = () => {
-

이용방식 안내

+ 이용방식 안내

1:1 문의하기

FAQ

diff --git a/src/widgets/instructor/home/ui/ModifyingCommissionsSection.tsx b/src/widgets/instructor/home/ui/ModifyingCommissionsSection.tsx index f884411..9c3e9e2 100644 --- a/src/widgets/instructor/home/ui/ModifyingCommissionsSection.tsx +++ b/src/widgets/instructor/home/ui/ModifyingCommissionsSection.tsx @@ -1,18 +1,27 @@ "use client"; +import { useEffect, useState } from "react"; + import { CommissionsHeader, ModifyingCommissionsRow, - modifyingStatusData, + type ModifyingItem, } from "@/features/instructor/home"; +import { getRevisions } from "@/features/instructor/home/api/getRevisions"; import { NextButton, PrevButton } from "@/shared/assets/icons"; import usePagination from "@/shared/lib/hooks/usePagination"; import PageIndicator from "@/shared/ui/PageIndicator"; import { MODIFYING_ITEMS_PER_PAGE } from "@/widgets/instructor/home/config/home"; const ModifyingCommissionsSection = () => { + const [items, setItems] = useState([]); + + useEffect(() => { + getRevisions().then(setItems); + }, []); + const { current, totalPages, pageItems, handlePrev, handleNext } = usePagination( - modifyingStatusData, + items, MODIFYING_ITEMS_PER_PAGE, ); @@ -35,7 +44,7 @@ const ModifyingCommissionsSection = () => {
{pageItems.length === 0 ? (
- 수정 중인 외주가 없습니다 + 진행중인 외주가 없습니다
) : (
From e168e8977c8dda6ffb7d2c44f0c376b51dba9825 Mon Sep 17 00:00:00 2001 From: YuminPark Date: Tue, 23 Jun 2026 21:40:45 +0900 Subject: [PATCH 08/11] =?UTF-8?q?#37=20[FEAT]=20=EC=8B=9C=EC=95=88=20?= =?UTF-8?q?=EC=A0=9C=EC=B6=9C=20=ED=98=84=ED=99=A9=20=EB=B0=8F=20=EB=A7=A4?= =?UTF-8?q?=EC=B9=AD=20=EC=A4=91=EC=9D=B8=20=EC=99=B8=EC=A3=BC=20API=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/api/getDraftSubmissions.ts | 15 +++++++++++++++ .../home/api/getMatchingCommissions.ts | 15 +++++++++++++++ src/features/instructor/home/index.ts | 4 +++- src/features/instructor/home/model/home.ts | 7 ++----- .../home/ui/DraftSubmissionStatusRow.tsx | 4 ++-- .../home/ui/MatchingCommissionsRow.tsx | 4 ++-- .../home/ui/DraftSubmissionStatusSection.tsx | 17 ++++++++++++----- .../home/ui/MatchingCommissionsSection.tsx | 19 ++++++++++++++----- 8 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 src/features/instructor/home/api/getDraftSubmissions.ts create mode 100644 src/features/instructor/home/api/getMatchingCommissions.ts diff --git a/src/features/instructor/home/api/getDraftSubmissions.ts b/src/features/instructor/home/api/getDraftSubmissions.ts new file mode 100644 index 0000000..839a17a --- /dev/null +++ b/src/features/instructor/home/api/getDraftSubmissions.ts @@ -0,0 +1,15 @@ +import type { DraftSubmissionItem } from "@/features/instructor/home/model/home"; +import { api, createApiPath } from "@/shared/api/client"; +import type { ApiResponse } from "@/shared/api/types"; + +type GetDraftSubmissionsResult = { + commissions: DraftSubmissionItem[]; +}; + +export const getDraftSubmissions = async (): Promise => { + const response = await api + .get(createApiPath("/api/v1/instructors/dashboards/draft-submissions")) + .json>(); + + return response.result?.commissions ?? []; +}; diff --git a/src/features/instructor/home/api/getMatchingCommissions.ts b/src/features/instructor/home/api/getMatchingCommissions.ts new file mode 100644 index 0000000..2595d20 --- /dev/null +++ b/src/features/instructor/home/api/getMatchingCommissions.ts @@ -0,0 +1,15 @@ +import type { MatchingItem } from "@/features/instructor/home/model/home"; +import { api, createApiPath } from "@/shared/api/client"; +import type { ApiResponse } from "@/shared/api/types"; + +type GetMatchingCommissionsResult = { + commissions: MatchingItem[]; +}; + +export const getMatchingCommissions = async (): Promise => { + const response = await api + .get(createApiPath("/api/v1/instructors/dashboards/matchings")) + .json>(); + + return response.result?.commissions ?? []; +}; diff --git a/src/features/instructor/home/index.ts b/src/features/instructor/home/index.ts index 971304d..2604fbf 100644 --- a/src/features/instructor/home/index.ts +++ b/src/features/instructor/home/index.ts @@ -1,7 +1,9 @@ +export { getDraftSubmissions } from "./api/getDraftSubmissions"; +export { getMatchingCommissions } from "./api/getMatchingCommissions"; export { getRevisions } from "./api/getRevisions"; export { getDDay } from "./lib/getDDay"; export type { DraftSubmissionItem, MatchingItem, ModifyingItem } from "./model/home"; -export { CATEGORY_DISPLAY_MAP, draftSubmissionStatusData, matchingStatusData } from "./model/home"; +export { CATEGORY_DISPLAY_MAP } from "./model/home"; export { default as CommissionsHeader } from "./ui/CommissionsHeader"; export { default as DraftSubmissionStatusRow } from "./ui/DraftSubmissionStatusRow"; export { default as MatchingCommissionsRow } from "./ui/MatchingCommissionsRow"; diff --git a/src/features/instructor/home/model/home.ts b/src/features/instructor/home/model/home.ts index 8aab488..b6ef18e 100644 --- a/src/features/instructor/home/model/home.ts +++ b/src/features/instructor/home/model/home.ts @@ -7,6 +7,7 @@ export type DraftSubmissionItem = { submitted: number; total: number; }; + isViewable: boolean; firstDraftDeadline: string; }; @@ -14,21 +15,17 @@ export const CATEGORY_DISPLAY_MAP: Record = { FLYER_TEXTBOOK_COVER_INNER: "교재 외지/내지", }; -export const draftSubmissionStatusData: DraftSubmissionItem[] = []; - // [강사] [대시보드 조회] 매칭 중인 외주 조회 export type MatchingItem = { commissionId: number; title: string; - finalDeadline: string; + applicationDeadline: string; matching: { matched: number; total: number; }; }; -export const matchingStatusData: MatchingItem[] = []; - // [강사] [대시보드 조회] 수정 중인 외주 조회 export type ModifyingItem = { commissionId: number; diff --git a/src/features/instructor/home/ui/DraftSubmissionStatusRow.tsx b/src/features/instructor/home/ui/DraftSubmissionStatusRow.tsx index 8290162..5216396 100644 --- a/src/features/instructor/home/ui/DraftSubmissionStatusRow.tsx +++ b/src/features/instructor/home/ui/DraftSubmissionStatusRow.tsx @@ -7,7 +7,7 @@ import Button from "@/shared/ui/Button"; import Tag from "@/shared/ui/Tag"; const DraftSubmissionStatusRow = ({ item }: { item: DraftSubmissionItem }) => { - const { commissionId, title, category, draftSubmission, firstDraftDeadline } = item; + const { commissionId, title, category, draftSubmission, isViewable, firstDraftDeadline } = item; const { submitted, total } = draftSubmission; const categoryLabel = CATEGORY_DISPLAY_MAP[category] ?? category; const router = useRouter(); @@ -37,7 +37,7 @@ const DraftSubmissionStatusRow = ({ item }: { item: DraftSubmissionItem }) => { ({submitted}/{total})

- {submitted === total ? ( + {isViewable ? (