Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions apps/web/src/apis/mentor/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
MentoringApprovalStatus,
type MentoringItem,
} from "@/types/mentor";
import type { CountryCode } from "@/types/university";
import { axiosInstance } from "@/utils/axiosInstance";

// QueryKeys for mentor domain
Expand Down Expand Up @@ -75,10 +76,11 @@ export interface PostApplyMentoringResponse {
}

export interface PostMentorApplicationRequest {
interestedCountries: string[];
country: string;
universityName: string;
studyStatus: "STUDYING" | "PLANNING" | "COMPLETED";
preparationStatus: "AFTER_EXCHANGE";
universitySelectType: "CATALOG";
country: CountryCode;
universityId: number;
term: string;
verificationFile: File;
}

Expand Down Expand Up @@ -127,17 +129,18 @@ export const mentorApi = {
postMentorApplication: async (body: PostMentorApplicationRequest): Promise<void> => {
const formData = new FormData();
const applicationData = {
interestedCountries: body.interestedCountries,
preparationStatus: body.preparationStatus,
universitySelectType: body.universitySelectType,
country: body.country,
universityName: body.universityName,
studyStatus: body.studyStatus,
universityId: body.universityId,
term: body.term,
};
formData.append(
"mentorApplicationRequest",
new Blob([JSON.stringify(applicationData)], { type: "application/json" }),
);
formData.append("file", body.verificationFile);
const res = await axiosInstance.post<void>("/mentor/verification", formData, {
const res = await axiosInstance.post<void>("/mentees/mentor-applications", formData, {
headers: { "Content-Type": "multipart/form-data" },
});
return res.data;
Expand Down
14 changes: 4 additions & 10 deletions apps/web/src/app/my/_ui/MyProfileContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useDeleteUserAccount, usePostLogout } from "@/apis/Auth";
import { type MyInfoResponse, useGetMyInfo } from "@/apis/MyPage";
import LinkedTextWithIcon from "@/components/ui/LinkedTextWithIcon";
import ProfileWithBadge from "@/components/ui/ProfileWithBadge";
import { infoToastOptions } from "@/lib/toast/options";
import useAuthStore from "@/lib/zustand/useAuthStore";
import { IconLikeFill } from "@/public/svgs/mentor";
import {
Expand Down Expand Up @@ -85,17 +84,12 @@ const MyProfileContent = () => {
<div className="w-full cursor-pointer rounded-lg bg-secondary-500 py-2 text-center text-white typo-medium-2">
<Link href={"/my/modify"}>프로필 변경</Link>
</div>
{/* <Link className="w-full" href={"/my/apply-mentor"}>
<button className="w-full rounded-lg bg-secondary-800 py-2 typo-medium-2 text-white">멘토 회원 전환</button>
</Link> */}
<button
onClick={() => {
toast("조금만 기다려주세요. [업데이트 중]", infoToastOptions);
}}
className="w-full rounded-lg bg-secondary-800 py-2 text-white typo-medium-2"
<Link
href="/my/apply-mentor"
className="w-full rounded-lg bg-secondary-800 py-2 text-center text-white typo-medium-2"
>
멘토 회원 전환
</button>
</Link>
</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,22 @@ const CompletionScreen = () => {
</div>

{/* 타이틀 */}
<h1 className="mb-4 text-center text-primary typo-bold-1">증명서 첨부 완료</h1>
<h1 className="mb-4 text-center text-primary typo-bold-1">멘토 전환 신청 완료</h1>

{/* 설명 */}
<p className="mb-12 text-center text-k-600 typo-regular-2">
승인은 최대 7일이 소요되며
관리자 검토가 완료되면
<br />
마이페이지에서 확인할 수 있습니다.
멘토 회원으로 전환돼요.
</p>

{/* 버튼들 */}
<div className="w-full max-w-sm space-y-3">
<BlockBtn
onClick={() => router.push("/")}
onClick={() => router.push("/my")}
className="hover:bg-primary-50 border border-primary bg-white text-primary"
>
홈으로 이동하기
</BlockBtn>
<BlockBtn onClick={() => router.push("/mentor/modify")} className="bg-primary text-white">
멘토 프로필 작성하기
마이페이지로 이동하기
</BlockBtn>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,49 @@ import { useFormContext } from "react-hook-form";

import BlockBtn from "@/components/button/BlockBtn";
import { mentorRegionList } from "@/constants/regions";
import type { MentorApplicationFormData } from "../../_lib/schema";
import { COUNTRY_CODE_MAP } from "@/constants/university";
import type { CountryCode } from "@/types/university";
import type { MentorApplicationFormInputData } from "../../_lib/schema";

type InterestCountriesScreenProps = {
onNext: () => void;
};

const countryCodeByName = Object.fromEntries(
Object.entries(COUNTRY_CODE_MAP).map(([code, countryName]) => [countryName, code]),
) as Record<string, CountryCode>;

const InterestCountriesScreen = ({ onNext }: InterestCountriesScreenProps) => {
const {
watch,
setValue,
trigger,
formState: { errors },
} = useFormContext<MentorApplicationFormData>();
} = useFormContext<MentorApplicationFormInputData>();

const [selectedRegion, setSelectedRegion] = useState<string>("미주권");
const selectedCountries = watch("interestedCountries") || [];
const selectedCountryCode = watch("country");

const selectedCountryName = selectedCountryCode ? COUNTRY_CODE_MAP[selectedCountryCode] : "";

const handleNext = async () => {
const isValid = await trigger("interestedCountries");
const isValid = await trigger("country");
if (isValid) {
onNext();
}
};

const removeCountry = (country: string) => {
setValue(
"interestedCountries",
selectedCountries.filter((c) => c !== country),
);
};
const selectCountry = (country: string) => {
const countryCode = countryCodeByName[country];
if (!countryCode) return;

const toggleCountry = (country: string) => {
if (selectedCountries.includes(country)) {
setValue(
"interestedCountries",
selectedCountries.filter((c) => c !== country),
);
} else {
setValue("interestedCountries", [...selectedCountries, country]);
}
setValue("country", countryCode, { shouldValidate: true });
setValue("universityId", 0);
setValue("term", "");
};

const currentRegion = mentorRegionList.find((r) => r.name === selectedRegion);
const currentCountries = currentRegion?.countries.filter((country) => countryCodeByName[country]) ?? [];

return (
<div className="pb-28">
Expand All @@ -58,31 +58,30 @@ const InterestCountriesScreen = ({ onNext }: InterestCountriesScreenProps) => {
나의
<span className="text-primary"> 수학 국가</span>를
<br />
선택해주세요
선택해주세요.
</span>
</div>

{/* Selected Countries Tags */}
{selectedCountries.length > 0 && (
{/* Selected Country Tag */}
{selectedCountryName && (
<div className="mt-5 grid grid-cols-3 gap-3">
{selectedCountries.map((country) => (
<button
key={country}
className="relative h-10 rounded bg-primary-100 text-center text-k-800 typo-medium-2"
onClick={() => removeCountry(country)}
type="button"
>
{country}
<span className="absolute right-0 top-0 p-1 leading-none">✕</span>
</button>
))}
<button
className="relative h-10 rounded bg-primary-100 text-center text-k-800 typo-medium-2"
onClick={() => {
setValue("country", "" as CountryCode, { shouldValidate: true });
setValue("universityId", 0);
setValue("term", "");
}}
type="button"
>
{selectedCountryName}
<span className="absolute right-0 top-0 p-1 leading-none">✕</span>
</button>
</div>
)}

{/* Error Message */}
{errors.interestedCountries && (
<p className="mt-2 text-red-500 typo-regular-2">{errors.interestedCountries.message}</p>
)}
{errors.country && <p className="mt-2 text-red-500 typo-regular-2">{errors.country.message}</p>}

{/* Region Tabs - Large Icon Buttons */}
<div className="mt-10 grid grid-cols-3 gap-4">
Expand Down Expand Up @@ -114,14 +113,14 @@ const InterestCountriesScreen = ({ onNext }: InterestCountriesScreenProps) => {

{/* Country Buttons - Only show current region's countries */}
<div className="mt-8 grid grid-cols-3 gap-3">
{currentRegion?.countries.map((country) => (
{currentCountries.map((country) => (
<button
key={country}
className={clsx("h-10 rounded border-none transition-colors typo-medium-2", {
"bg-k-50 text-k-600 hover:bg-k-100": !selectedCountries.includes(country),
"bg-primary-100 text-k-800": selectedCountries.includes(country),
"bg-k-50 text-k-600 hover:bg-k-100": selectedCountryName !== country,
"bg-primary-100 text-k-800": selectedCountryName === country,
})}
onClick={() => toggleCountry(country)}
onClick={() => selectCountry(country)}
type="button"
>
{country}
Expand All @@ -132,7 +131,7 @@ const InterestCountriesScreen = ({ onNext }: InterestCountriesScreenProps) => {

<div className="fixed bottom-0 left-0 right-0 w-full bg-white pb-14">
<div className="mx-auto w-full max-w-app px-5">
<BlockBtn className="mb-[29px]" disabled={selectedCountries.length === 0} onClick={handleNext}>
<BlockBtn className="mb-[29px]" disabled={!selectedCountryCode} onClick={handleNext}>
다음
</BlockBtn>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useFormContext } from "react-hook-form";

import BlockBtn from "@/components/button/BlockBtn";
import { IconPrepare1, IconPrepare2, IconPrepare3 } from "@/public/svgs/auth";
import type { MentorApplicationFormData } from "../../_lib/schema";
import type { MentorApplicationFormInputData } from "../../_lib/schema";

type StudyStatusScreenProps = {
onNext: () => void;
Expand All @@ -17,12 +17,12 @@ const StudyStatusScreen = ({ onNext }: StudyStatusScreenProps) => {
setValue,
trigger,
formState: { errors },
} = useFormContext<MentorApplicationFormData>();
} = useFormContext<MentorApplicationFormInputData>();

const studyStatus = watch("studyStatus");
const preparationStatus = watch("preparationStatus");

const handleNext = async () => {
const isValid = await trigger("studyStatus");
const isValid = await trigger("preparationStatus");
if (isValid) {
onNext();
}
Expand All @@ -40,45 +40,48 @@ const StudyStatusScreen = ({ onNext }: StudyStatusScreenProps) => {
</span>
</div>

{errors.studyStatus && <p className="mt-2 text-red-500 typo-regular-2">{errors.studyStatus.message}</p>}
{errors.preparationStatus && (
<p className="mt-2 text-red-500 typo-regular-2">{errors.preparationStatus.message}</p>
)}

<div className="mt-10">
<div className="flex flex-col gap-5">
{/* 지원 솔커 - 비활성화 */}
<StatusChoiceButton
status="PLANNING"
status="BEFORE_EXCHANGE"
description="교환학생을 준비 중이신가요?"
name="지원 슬커"
name="준비 중"
icon={<IconPrepare1 />}
isSelected={studyStatus === "PLANNING"}
onClick={() => {}} // 클릭 불가
isSelected={false}
onClick={() => {}}
disabled
/>

<StatusChoiceButton
status="STUDYING"
status="DURING_EXCHANGE"
description="합격했거나 수학 중이신가요?"
name="수학 중"
icon={<IconPrepare2 />}
isSelected={studyStatus === "STUDYING"}
onClick={() => setValue("studyStatus", "STUDYING")}
isSelected={false}
onClick={() => {}}
disabled
/>

<StatusChoiceButton
status="COMPLETED"
status="AFTER_EXCHANGE"
description="교환학생 후 귀국한 학생이신가요?"
name="수학 완료"
icon={<IconPrepare3 />}
isSelected={studyStatus === "COMPLETED"}
onClick={() => setValue("studyStatus", "COMPLETED")}
isSelected={preparationStatus === "AFTER_EXCHANGE"}
onClick={() => setValue("preparationStatus", "AFTER_EXCHANGE", { shouldValidate: true })}
/>
</div>
</div>
</div>

<div className="fixed bottom-0 left-0 right-0 w-full bg-white pb-14">
<div className="mx-auto w-full max-w-app px-5">
<BlockBtn className="mb-[29px]" disabled={!studyStatus} onClick={handleNext}>
<BlockBtn className="mb-[29px]" disabled={!preparationStatus} onClick={handleNext}>
다음
</BlockBtn>
</div>
Expand Down
Loading
Loading