Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4b28d49
feat: 프로필 생성, 수정 페이지 추가(임시)
9g-g9 Aug 2, 2026
8c2c92d
feat: 프로필 업로드, 수정 추가, 이미지 업로드 임시 추가, refactor: type 하나로 병합
9g-g9 Aug 2, 2026
e634f83
fix: formField 기능 확장 및 profile formField 사용
9g-g9 Aug 2, 2026
d7830b9
fix: 프로필 수정 방식 및 디자인 수정
9g-g9 Aug 2, 2026
97fe065
Merge branch 'dev' of https://github.com/4roro-moving/moving-frontend…
9g-g9 Aug 3, 2026
b643b5e
Merge branch 'dev' of https://github.com/4roro-moving/moving-frontend…
9g-g9 Aug 3, 2026
f082e57
feat: oAuth 계정 생성 시 전화번호 입력, 수정 시 비밀번호 숨김 처리
9g-g9 Aug 3, 2026
47869ff
feat: useMoverAuthReady customer 와 맞춤, 비밀번호 수정 틀릴 시 focus 추가
9g-g9 Aug 3, 2026
fc23375
feat: loadingFallback 파일 분리 및 프로필 생성, 수정 스켈레톤 UI 추가
9g-g9 Aug 3, 2026
b4c861a
feat: header 반응형 적용, apiError 응답 구조 수정
9g-g9 Aug 3, 2026
4b04725
feat: customer basic info 수정 추가
9g-g9 Aug 4, 2026
fb1d407
fix: header focus trap, 프로필 수정 시 toast 추가, 전화번호 검증 느슨하게 변경
9g-g9 Aug 4, 2026
f6dc473
fix: 일반 로그인 프로필 생성 시 전화번호 노출 오류 수정
9g-g9 Aug 4, 2026
d088993
fix: 리뷰 사항 수정
9g-g9 Aug 4, 2026
15c44c3
fix: phone schema 수정 및 signup 도 해당 검증 사용 변경
9g-g9 Aug 4, 2026
a3b162b
fix: 접근 불가 시 안내 문구 수정, 프로필 정보 못불러왔을 때 상태 컴포넌트 생성
9g-g9 Aug 4, 2026
1dd372a
fix: imageUploader a11y 적용
9g-g9 Aug 4, 2026
c32c0a9
Merge branch 'dev' of https://github.com/4roro-moving/moving-frontend…
9g-g9 Aug 4, 2026
db3c1bf
fix: 충돌수정 및 side nav 컴포넌트 수정
9g-g9 Aug 5, 2026
8c8e26b
fix: chip 공통 사용, dirty reset 추가, side nav 수정
9g-g9 Aug 5, 2026
060c8af
feat: side nav 애니메이션 추가, chip hover border 추가
9g-g9 Aug 5, 2026
38bfe5b
fix: mover sent 활성 상태 제외
9g-g9 Aug 5, 2026
4634639
Merge branch 'dev' of https://github.com/4roro-moving/moving-frontend…
9g-g9 Aug 5, 2026
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: 5 additions & 14 deletions src/app/(customer)/(protected)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,20 @@ import type { ReactNode } from "react";
import { usePathname } from "next/navigation";

import RoleGuard from "@/components/auth/RoleGuard";
import { PageHeader } from "@/components/common/PageHeader";
import FavoriteMoversLoadingSkeleton from "@/components/mover/favorites/FavoriteMoversLoadingSkeleton";
import { FAVORITE_MOVERS_CONTENT_CLASSNAME } from "@/components/mover/favorites/FavoriteMoversContent";
import { APP_ROUTES } from "@/lib/constants/appRoutes";
import { getCustomerProtectedLoadingFallback } from "@/lib/loading/getCustomerProtectedLoadingFallback";

interface CustomerProtectedLayoutProps {
children: ReactNode;
}

const CustomerProtectedLayout = ({ children }: CustomerProtectedLayoutProps) => {
const pathname = usePathname();
const loadingFallback =
pathname === APP_ROUTES.MOVERS.FAVORITES ? (
<div className="bg-background-subtle flex w-full flex-col">
<PageHeader title="찜한 기사님" />
<div className={FAVORITE_MOVERS_CONTENT_CLASSNAME}>
<FavoriteMoversLoadingSkeleton />
</div>
</div>
) : null;

return (
<RoleGuard allowedRole="CUSTOMER" loadingFallback={loadingFallback}>
<RoleGuard
allowedRole="CUSTOMER"
loadingFallback={getCustomerProtectedLoadingFallback(pathname)}
>
{children}
</RoleGuard>
);
Expand Down
14 changes: 14 additions & 0 deletions src/app/(customer)/(protected)/profile/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Metadata } from "next";

import CustomerProfileEditView from "@/components/profile/CustomerProfileEditView";

export const metadata: Metadata = {
title: "프로필 수정 | 무빙",
description: "무빙 고객 프로필 수정",
};

const CustomerProfileEditPage = () => {
return <CustomerProfileEditView />;
};

export default CustomerProfileEditPage;
13 changes: 2 additions & 11 deletions src/app/(customer)/(protected)/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import type { Metadata } from "next";

import { Text } from "@/components/common/Text";
import CustomerProfileCreateView from "@/components/profile/CustomerProfileCreateView";

export const metadata: Metadata = {
title: "프로필 등록 | 무빙",
description: "무빙 고객 프로필 등록",
};

const CustomerProfilePage = () => {
return (
<div className="flex flex-1 flex-col items-center justify-center px-24 py-80">
<Text as="h1" variant="2lg-bold" className="text-text-primary">
고객 프로필 등록
</Text>
<Text as="p" variant="md-medium" className="text-text-description mt-12">
프로필 등록 UI는 추후 연결됩니다.
</Text>
</div>
);
return <CustomerProfileCreateView />;
};

export default CustomerProfilePage;
12 changes: 11 additions & 1 deletion src/app/(mover)/(protected)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
"use client";

import type { ReactNode } from "react";
import { usePathname } from "next/navigation";

import RoleGuard from "@/components/auth/RoleGuard";
import { getMoverProtectedLoadingFallback } from "@/lib/loading/getMoverProtectedLoadingFallback";

interface MoverProtectedLayoutProps {
children: ReactNode;
}

const MoverProtectedLayout = ({ children }: MoverProtectedLayoutProps) => {
return <RoleGuard allowedRole="MOVER">{children}</RoleGuard>;
const pathname = usePathname();

return (
<RoleGuard allowedRole="MOVER" loadingFallback={getMoverProtectedLoadingFallback(pathname)}>
{children}
</RoleGuard>
);
};

export default MoverProtectedLayout;
14 changes: 14 additions & 0 deletions src/app/(mover)/(protected)/mover/basic/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Metadata } from "next";

import MoverBasicInfoEditView from "@/components/profile/MoverBasicInfoEditView";

export const metadata: Metadata = {
title: "기본정보 수정 | 무빙",
description: "무빙 기사님 기본정보 수정",
};

const MoverBasicInfoEditPage = () => {
return <MoverBasicInfoEditView />;
};

export default MoverBasicInfoEditPage;
14 changes: 14 additions & 0 deletions src/app/(mover)/(protected)/mover/profile/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Metadata } from "next";

import MoverProfileEditView from "@/components/profile/MoverProfileEditView";

export const metadata: Metadata = {
title: "기사 프로필 수정 | 무빙",
description: "무빙 기사님 프로필 수정",
};

const MoverProfileEditPage = () => {
return <MoverProfileEditView />;
};

export default MoverProfileEditPage;
13 changes: 2 additions & 11 deletions src/app/(mover)/(protected)/mover/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import type { Metadata } from "next";

import { Text } from "@/components/common/Text";
import MoverProfileCreateView from "@/components/profile/MoverProfileCreateView";

export const metadata: Metadata = {
title: "기사 프로필 등록 | 무빙",
description: "무빙 기사님 프로필 등록",
};

const MoverProfilePage = () => {
return (
<div className="flex flex-1 flex-col items-center justify-center px-24 py-80">
<Text as="h1" variant="2lg-bold" className="text-text-primary">
기사님 프로필 등록
</Text>
<Text as="p" variant="md-medium" className="text-text-description mt-12">
프로필 등록 UI는 추후 연결됩니다.
</Text>
</div>
);
return <MoverProfileCreateView />;
};

export default MoverProfilePage;
36 changes: 21 additions & 15 deletions src/components/auth/MoverAuthGate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,55 @@ import { usePathname, useRouter } from "next/navigation";
import { useEffect, type ReactNode } from "react";

import EstimatesQueryStatus from "@/components/estimate/EstimatesQueryStatus";
import { useCustomerAuthReady } from "@/hooks/useCustomerAuthReady";
import { buildLoginPath } from "@/lib/auth/redirect";
import { APP_ROUTES } from "@/lib/constants/appRoutes";
import { useMoverAuthReady } from "@/hooks/useMoverAuthReady";
import { buildLoginPath, getRoleHomePath } from "@/lib/auth/redirect";

interface MoverAuthGateProps {
children: ReactNode;
/** 세션 확인 중 표시. 기본은 공통 로딩 메시지 */
loadingMessage?: string;
/** 세션 확인 중 커스텀 UI. 있으면 loadingMessage 대신 사용 */
loadingFallback?: ReactNode;
}

/**
* 기사님 전용 영역 가드
* - 세션 복구 중: 로딩
* - 비로그인: 로그인 페이지로 이동 (?redirect=)
* - 고객: 기사님 찾기(고객 홈)로 이동
* - 비로그인: 기사 로그인 페이지로 이동 (?redirect=)
* - MOVER 아님(고객·ADMIN·역할 미확정): getRoleHomePath로 이동
*
* // 2026.07.31 정슬기 - [추가] CustomerAuthGate와 대칭인 기사님 가드
*
* ADMIN 홈 경로는 getRoleHomePath의 임시 정책(기사님 찾기)을 그대로 사용합니다.
*/
export default function MoverAuthGate({
const MoverAuthGate = ({
children,
loadingMessage = "로그인 상태를 확인하는 중입니다.",
}: MoverAuthGateProps) {
loadingFallback,
}: MoverAuthGateProps) => {
const router = useRouter();
const pathname = usePathname();
const { isPending, isAuthenticated, isMover } = useCustomerAuthReady();
const canAccess = !isPending && isAuthenticated && isMover;
const { isPending, isAuthenticated, isMover, canFetch, user } = useMoverAuthReady();

useEffect(() => {
if (isPending) return;

if (!isAuthenticated) {
const search = typeof window !== "undefined" ? window.location.search : "";
router.replace(buildLoginPath(`${pathname}${search}`));
router.replace(buildLoginPath(`${pathname}${search}`, "mover"));
return;
}

if (!isMover) {
router.replace(APP_ROUTES.MOVERS.ROOT);
router.replace(getRoleHomePath(user?.role));
}
}, [isPending, isAuthenticated, isMover, pathname, router]);
}, [isPending, isAuthenticated, isMover, user?.role, pathname, router]);

if (isPending || !canAccess) {
return <EstimatesQueryStatus message={loadingMessage} />;
if (isPending || !canFetch) {
return loadingFallback ?? <EstimatesQueryStatus message={loadingMessage} />;
}

return children;
}
};

export default MoverAuthGate;
3 changes: 2 additions & 1 deletion src/components/common/Chip/SelectableChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const selectableChipVariants = cva(
},
selected: {
true: "border-border-brand bg-background-brand-muted text-text-brand",
false: "border-border-muted bg-background-subtle text-text-secondary",
false:
"border-border-muted bg-background-subtle text-text-secondary hover:border-border-brand",
},
},
defaultVariants: {
Expand Down
72 changes: 62 additions & 10 deletions src/components/common/FormField/FormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,87 @@ const FORM_FIELD_LABEL_VARIANTS = {

interface FormFieldProps {
label: string;
labelFor: string;
/** 단일 input/textarea와 연결. Chip group이면 생략하고 labelId 사용 */
labelFor?: string;
/** Chip group 등 — 시각 라벨을 aria-labelledby로 연결할 때 */
labelId?: string;
children: ReactNode;
className?: string;
/** default: 프로필 등록·수정 폼 등 / compact: 모달 폼 / auth: 로그인·회원가입 */
variant?: FormFieldVariant;
/** FORM_FIELD_LABEL_VARIANTS에 해당하지 않는 라벨 타이포그래피에 사용 */
/** FORM_FIELD_LABEL_VARIANTS에 해당하지 않는 라벨 타이포에 사용 */
labelVariant?: TextVariantProp;
required?: boolean;
/** 라벨 아래 부가 안내 문구 */
description?: string;
}

const FormField = ({
label,
labelFor,
labelId,
children,
className,
variant = "default",
labelVariant,
required = false,
description,
}: FormFieldProps) => {
const isAuth = variant === "auth";
const resolvedLabelVariant = labelVariant ?? FORM_FIELD_LABEL_VARIANTS[variant];
const labelClassName = isAuth ? "text-text-secondary" : "text-text-tertiary";

const labelContent = (
<>
{label}
{required ? (
<>
<Text
as="span"
variant={resolvedLabelVariant}
className="text-text-brand"
aria-hidden="true"
>
*
</Text>
<span className="sr-only">필수</span>
</>
) : null}
</>
);
Comment thread
9g-g9 marked this conversation as resolved.

return (
<div className={cn("flex flex-col gap-10", className)}>
<Text
as="label"
htmlFor={labelFor}
variant={labelVariant ?? FORM_FIELD_LABEL_VARIANTS[variant]}
className={isAuth ? "text-text-secondary" : "text-text-tertiary"}
>
{label}
</Text>
<div className="flex w-full flex-col gap-4">
{labelFor ? (
<Text
as="label"
htmlFor={labelFor}
variant={resolvedLabelVariant}
className={cn(labelClassName, "flex items-center gap-4")}
>
{labelContent}
</Text>
) : (
<Text
as="p"
id={labelId}
variant={resolvedLabelVariant}
className={cn(labelClassName, "flex items-center gap-4")}
>
{labelContent}
</Text>
)}
{description ? (
<Text
as="p"
variant={{ base: "xs-regular", md: "lg-regular" }}
className="text-text-subtle"
>
{description}
</Text>
) : null}
</div>
{children}
</div>
);
Expand Down
Loading