-
-
알림 {notificationCount}개
-
-
-
-
-
- {SORTED_ALERTS.length === 0 ? (
-
- ) : (
-
- {SORTED_ALERTS.map(alert => (
-
- ))}
-
- )}
+
+
+
+
알림 {notificationCount}개
+
- )}
+ {SORTED_ALERTS.length === 0 ? (
+
+ ) : (
+
+ {SORTED_ALERTS.map(alert => (
+
+ ))}
+
+ )}
+
>
);
}
diff --git a/src/components/ui/modal/notification/NotificationMessage.tsx b/src/components/ui/modal/notification/NotificationMessage.tsx
index d0e97a9..c36e517 100644
--- a/src/components/ui/modal/notification/NotificationMessage.tsx
+++ b/src/components/ui/modal/notification/NotificationMessage.tsx
@@ -37,16 +37,16 @@ export default function NotificationMessage({
{`${shopName} (${DATE_RANGE.date} ${DATE_RANGE.startTime} ~
${DATE_RANGE.endTime}) 공고 지원이 `}
{RESULT_TEXT}
diff --git a/src/components/ui/modal/notification/ResultBadge.tsx b/src/components/ui/modal/notification/ResultBadge.tsx
index 9ba4d06..df64c37 100644
--- a/src/components/ui/modal/notification/ResultBadge.tsx
+++ b/src/components/ui/modal/notification/ResultBadge.tsx
@@ -1,19 +1,26 @@
import Icon from '@/components/ui/icon/icon';
export interface ResultBadgeProps {
- result: 'accepted' | 'rejected';
+ result: 'accepted' | 'rejected' | null;
}
-const ICON_COLORS: Record = {
+
+// null은 객체에서 빼고, default 색상을 따로 둔다
+const ICON_COLORS: Record<'accepted' | 'rejected', string> = {
accepted: 'bg-blue-200',
rejected: 'bg-red-400',
};
+const DEFAULT_COLOR = 'bg-gray-300';
export default function ResultBadge({ result }: ResultBadgeProps) {
+ const color = result ? ICON_COLORS[result] : DEFAULT_COLOR;
+
return (
);
}
diff --git a/src/components/ui/table/Table.stories.tsx b/src/components/ui/table/Table.stories.tsx
index 04c4e95..f9f139b 100644
--- a/src/components/ui/table/Table.stories.tsx
+++ b/src/components/ui/table/Table.stories.tsx
@@ -176,6 +176,9 @@ function TableWithTestApi({ userRole }: { userRole: UserRole }) {
limit={limit}
offset={offset}
onPageChange={setOffset}
+ onStatusUpdate={() => {}}
+ shopId=''
+ noticeId=''
/>
);
}
diff --git a/src/components/ui/table/Table.tsx b/src/components/ui/table/Table.tsx
index ad1016d..37d562a 100644
--- a/src/components/ui/table/Table.tsx
+++ b/src/components/ui/table/Table.tsx
@@ -1,4 +1,5 @@
import { Pagination } from '@/components/ui';
+import { StatusType } from '@/components/ui/badge/StatusBadge';
import { TableRowProps } from '@/components/ui/table/TableRowProps';
import { cn } from '@/lib/utils/cn';
import { UserRole } from '@/types/user';
@@ -12,6 +13,11 @@ interface TableProps {
limit: number;
offset: number;
onPageChange: (newOffset: number) => void;
+ onStatusUpdate: (id: string, newStatus: StatusType, shopId?: string, noticeId?: string) => void;
+ shopId?: string;
+ noticeId?: string;
+ applyNotice?: (shopId: string, noticeId: string) => void;
+ cancelNotice?: (shopId: string, noticeId: string) => void;
}
export default function Table({
@@ -22,15 +28,16 @@ export default function Table({
limit,
offset,
onPageChange,
+ onStatusUpdate,
+ shopId,
+ noticeId,
}: TableProps) {
return (
-
-
-
- {userRole === 'employer' ? '신청자 목록' : '신청 내역'}
-
-
-
+ <>
+
+ {userRole === 'employer' ? '신청자 목록' : '신청 내역'}
+
+
@@ -39,7 +46,7 @@ export default function Table({
| 0 && index < headers.length - 1 && 'w-[245px]',
@@ -53,16 +60,26 @@ export default function Table({
|
{tableData.map(row => (
-
+
))}
-
+ {offset >= 2 && (
+
+ )}
-
+ >
);
}
diff --git a/src/components/ui/table/TableRow.tsx b/src/components/ui/table/TableRow.tsx
index af17530..7e80861 100644
--- a/src/components/ui/table/TableRow.tsx
+++ b/src/components/ui/table/TableRow.tsx
@@ -1,6 +1,9 @@
import { StatusBadge } from '@/components/ui/badge';
import { StatusType } from '@/components/ui/badge/StatusBadge';
+import { Button } from '@/components/ui/button';
+import { Modal } from '@/components/ui/modal';
import { TableRowProps } from '@/components/ui/table/TableRowProps';
+import axiosInstance from '@/lib/axios';
import { cn } from '@/lib/utils/cn';
import { getTime } from '@/lib/utils/dateFormatter';
import { UserRole } from '@/types/user';
@@ -9,47 +12,104 @@ import { useState } from 'react';
interface TableTypeVariant {
rowData: TableRowProps;
userRole: UserRole;
+ onStatusUpdate: (id: string, newStatus: StatusType) => void;
+ shopId?: string;
+ noticeId?: string;
}
const TD_BASE = 'border-b border-r px-3 py-5 text-base gap-3 md:border-r-0';
const TD_STATUS = 'border-b px-2 py-[9px]';
-export default function TableRow({ rowData, userRole: userRole }: TableTypeVariant) {
+export default function TableRow({ rowData, userRole, onStatusUpdate }: TableTypeVariant) {
const { date, startTime, endTime, duration } = getTime(rowData.startsAt, rowData.workhour);
const [status, setStatus] = useState
(rowData.status as StatusType);
- const handleStatusChange = (id: string, newStatus: StatusType) => {
- setStatus(newStatus);
+ const [modalOpen, setModalOpen] = useState(false);
+ const [modalAction, setModalAction] = useState(null);
+
+ const handleClick = (action: StatusType) => {
+ setModalAction(action);
+ setModalOpen(true);
};
- const handleApprove = () => setStatus('accepted');
- const handleReject = () => setStatus('rejected');
+ if (!rowData.shopId || !rowData.noticeId) {
+ alert('잘못된 신청 정보입니다.');
+ return;
+ }
+
+ const handleStatusChange = async () => {
+ if (!modalAction) return;
+
+ try {
+ await axiosInstance.put(
+ `/shops/${rowData.shopId}/notices/${rowData.noticeId}/applications/${rowData.id}`,
+ { status: modalAction }
+ );
+
+ setStatus(modalAction);
+
+ onStatusUpdate(rowData.id, modalAction);
+ } catch (error) {
+ alert(error instanceof Error ? error.message : '상태 변경 실패');
+ } finally {
+ setModalOpen(false);
+ setModalAction(null);
+ }
+ };
return (
-
- | {rowData.name} |
-
- {userRole === 'employee' ? (
- <>
- {`${date} ${startTime} ~ ${date} ${endTime} (${duration})`} |
- {rowData.hourlyPay} |
- >
- ) : (
- <>
- {rowData.bio} |
- {rowData.phone} |
- >
- )}
-
-
- |
-
+ <>
+
+ | {rowData.name} |
+
+ {userRole === 'employee' ? (
+ <>
+ {`${date} ${startTime} ~ ${date} ${endTime} (${duration})`} |
+ {rowData.hourlyPay} |
+ >
+ ) : (
+ <>
+ {rowData.bio} |
+ {rowData.phone} |
+ >
+ )}
+
+
+ {status === 'pending' && userRole === 'employer' ? (
+
+
+
+
+ ) : (
+
+ )}
+ |
+
+
+ setModalOpen(false)}
+ variant='warning'
+ title={`신청을 ${modalAction === 'accepted' ? '승인' : '거절'}하시겠어요?`}
+ primaryText='확인'
+ secondaryText='취소'
+ onPrimary={handleStatusChange}
+ onSecondary={() => setModalOpen(false)}
+ />
+ >
);
}
diff --git a/src/components/ui/table/TableRowProps.tsx b/src/components/ui/table/TableRowProps.tsx
index 5375c61..6205358 100644
--- a/src/components/ui/table/TableRowProps.tsx
+++ b/src/components/ui/table/TableRowProps.tsx
@@ -7,4 +7,7 @@ export type TableRowProps = {
status: string | JSX.Element;
bio: string;
phone: string;
+ userId?: string;
+ shopId: string;
+ noticeId: string;
};
diff --git a/src/context/authProvider.tsx b/src/context/authProvider.tsx
index 2c13bd7..7b7af45 100644
--- a/src/context/authProvider.tsx
+++ b/src/context/authProvider.tsx
@@ -34,7 +34,7 @@ export const AuthContext = createContext(null);
const TOKEN_KEY = 'thejulge_token';
const USER_ID_KEY = 'thejulge_user_id';
const EXPIRES_KEY = 'thejulge_expires_at';
-const EXPIRES_DURATION_MS = 10 * 60 * 1000; // 10분
+const EXPIRES_DURATION_MS = 1000 * 60 * 1000; // 10분
/** storage helpers */
const isBrowser = () => typeof window !== 'undefined';
diff --git a/src/context/notificationContext/index.tsx b/src/context/notificationContext/index.tsx
new file mode 100644
index 0000000..dc30b3c
--- /dev/null
+++ b/src/context/notificationContext/index.tsx
@@ -0,0 +1,42 @@
+import { Alert } from '@/components/ui/modal/notification/Notification';
+import axiosInstance from '@/lib/axios';
+import { createContext, ReactNode, useContext, useState } from 'react';
+
+interface NotificationContextType {
+ alerts: Alert[];
+ fetchAlerts: () => Promise;
+ addAlert: (alert: Alert) => void;
+ markAsRead: (id: string) => void;
+}
+
+const NotificationContext = createContext(undefined);
+
+export const NotificationProvider = ({ children }: { children: ReactNode }) => {
+ const [alerts, setAlerts] = useState([]);
+
+ const fetchAlerts = async () => {
+ const res = await axiosInstance.get('/users/me/alerts');
+ setAlerts(res.data);
+ };
+
+ const addAlert = (alert: Alert) => {
+ setAlerts(prev => [alert, ...prev]);
+ };
+
+ const markAsRead = (id: string) => {
+ setAlerts(prev => prev.map(a => (a.id === id ? { ...a, read: true } : a)));
+ axiosInstance.put(`/users/me/alerts/${id}`); // 서버에도 반영
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useNotification = () => {
+ const context = useContext(NotificationContext);
+ if (!context) throw new Error('useNotification must be used within NotificationProvider');
+ return context;
+};
diff --git a/src/context/userApplicationsProvider.tsx b/src/context/userApplicationsProvider.tsx
index 157f930..a927a83 100644
--- a/src/context/userApplicationsProvider.tsx
+++ b/src/context/userApplicationsProvider.tsx
@@ -1,4 +1,4 @@
-// context/UserApplicationsProvider.tsx
+
import { getAllUserApplications, postApplication, putApplication } from '@/api/applications';
import useAuth from '@/hooks/useAuth';
import { ApiResponse } from '@/types/api';
@@ -81,8 +81,13 @@ export const UserApplicationsProvider = ({ children }: { children: ReactNode })
setError('로그인이 필요합니다.');
return;
}
- await postApplication(shopId, noticeId);
- await fetchAllApplications(); // 최신화 반영
+
+ try {
+ await postApplication(shopId, noticeId);
+ await fetchAllApplications(); // 최신화
+ } catch {
+ setError('신청 중 오류가 발생했습니다.');
+ }
},
[user, fetchAllApplications]
);
diff --git a/src/pages/employer/shops/[shopId]/notices/[noticeId]/edit.tsx b/src/pages/employer/shops/[shopId]/notices/[noticeId]/edit.tsx
index 9917ef7..8746d63 100644
--- a/src/pages/employer/shops/[shopId]/notices/[noticeId]/edit.tsx
+++ b/src/pages/employer/shops/[shopId]/notices/[noticeId]/edit.tsx
@@ -1,6 +1,8 @@
+import { Container } from '@/components/layout';
import { Button, DateInput, Input, Modal, TimeInput } from '@/components/ui';
import useAuth from '@/hooks/useAuth';
import axiosInstance from '@/lib/axios';
+import { TimeValue } from '@/types/calendar';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
@@ -18,7 +20,7 @@ const EmployerNoticeEditPage = () => {
const [wage, setWage] = useState('');
const [date, setDate] = useState(null);
- const [time, setTime] = useState(null);
+ const [time, setTime] = useState(null);
const [workhour, setWorkhour] = useState();
const [description, setDescription] = useState('');
@@ -45,8 +47,11 @@ const EmployerNoticeEditPage = () => {
setDescription(notice.description);
const startDate = new Date(notice.startsAt);
+ setTime({
+ date: startDate,
+ period: startDate.getHours() >= 12 ? '오후' : '오전',
+ });
setDate(startDate);
- setTime(startDate);
} catch {
alert('공고 정보를 불러오는 중 오류가 발생했습니다.');
router.back();
@@ -65,7 +70,7 @@ const EmployerNoticeEditPage = () => {
if (!user?.shop || !noticeId) return;
const combinedDateTime = new Date(date);
- combinedDateTime.setHours(time.getHours(), time.getMinutes(), 0, 0);
+ combinedDateTime.setHours(time.date.getHours(), time.date.getMinutes(), 0, 0);
const payload: NoticePayload = {
hourlyPay: Number(wage),
@@ -93,7 +98,7 @@ const EmployerNoticeEditPage = () => {
if (!user?.shop) return null;
return (
-
+
);
};
diff --git a/src/pages/employer/shops/[shopId]/notices/[noticeId]/index.tsx b/src/pages/employer/shops/[shopId]/notices/[noticeId]/index.tsx
index 7e384db..ffee448 100644
--- a/src/pages/employer/shops/[shopId]/notices/[noticeId]/index.tsx
+++ b/src/pages/employer/shops/[shopId]/notices/[noticeId]/index.tsx
@@ -1,3 +1,4 @@
+import { Container } from '@/components/layout';
import { Button, Modal, Notice, Table } from '@/components/ui';
import { TableRowProps } from '@/components/ui/table/TableRowProps';
import useAuth from '@/hooks/useAuth';
@@ -87,17 +88,15 @@ export const getServerSideProps: GetServerSideProps<{ notice: NoticeCard }> = as
const noticeRes = await axiosInstance.get(`shops/${shopId}/notices/${noticeId}`);
return { props: { notice: toNoticeCard(noticeRes.data) } };
} catch {
- return {
- notFound: true,
- };
+ return { notFound: true };
}
};
const EmployerNoticeDetailPage = ({ notice }: { notice: NoticeCard }) => {
const headers = ['신청자', '소개', '전화번호', '상태'];
const [data, setData] = useState([]);
-
const [offset, setOffset] = useState(0);
+ const [total, setTotal] = useState(0); // ✅ 전체 개수
const limit = 5;
const { role, isLogin, user } = useAuth();
@@ -109,7 +108,6 @@ const EmployerNoticeDetailPage = ({ notice }: { notice: NoticeCard }) => {
const isOwner = user?.shop?.item.id === notice.shopId;
const canEdit = useMemo(() => status === 'open' && isOwner, [status, isOwner]);
- // 공고 편집하기
const handleEditClick = useCallback(() => {
if (!canEdit) return;
@@ -149,14 +147,17 @@ const EmployerNoticeDetailPage = ({ notice }: { notice: NoticeCard }) => {
router.push(`/employer/shops/${notice.shopId}/notices/${notice.id}/edit`);
}, [canEdit, isLogin, role, user, notice, router]);
- // 신청자 불러오기
-
+ // 신청자 불러오기 (페이지네이션)
useEffect(() => {
const fetchApplications = async () => {
- const res = await axiosInstance.get<{ items: ApplicationTableApiResponse[] }>(
- `/shops/${notice.shopId}/notices/${notice.id}/applications`,
- { params: { offset, limit } }
- );
+ const res = await axiosInstance.get<{
+ items: ApplicationTableApiResponse[];
+ total?: number;
+ count?: number;
+ hasNext?: boolean;
+ }>(`/shops/${notice.shopId}/notices/${notice.id}/applications`, {
+ params: { offset, limit },
+ });
const tableData: TableRowProps[] = res.data.items.map(app => {
const userItem = app.item.user?.item;
@@ -173,17 +174,33 @@ const EmployerNoticeDetailPage = ({ notice }: { notice: NoticeCard }) => {
? `${noticeItem.hourlyPay.toLocaleString()}원`
: '정보 없음',
status: app.item.status,
+ userId: userItem?.id,
+ shopId: notice.shopId,
+ noticeId: notice.id,
};
});
setData(tableData);
+
+ // total 적용
+ const apiTotal = res.data.total ?? res.data.count;
+ if (typeof apiTotal === 'number') {
+ setTotal(apiTotal);
+ } else {
+ const hasNext = res.data.hasNext ?? tableData.length === limit;
+ const guessed = offset + tableData.length + (hasNext ? 1 : 0);
+ setTotal(guessed);
+ }
};
fetchApplications();
}, [notice.shopId, notice.id, offset, limit]);
+ // 첫 페이지에서도 페이지네이션 보이도록 표시용 offset
+ const displayOffset = total > limit ? (offset === 0 ? 2 : offset) : offset;
+
return (
-
+ <>
-
-
+
+
+
+ setData(prev => prev.map(row => (row.id === id ? { ...row, status: newStatus } : row)))
+ }
+ shopId={notice.shopId}
+ noticeId={notice.id}
+ />
+
+ >
);
};
diff --git a/src/pages/employer/shops/[shopId]/notices/register/index.tsx b/src/pages/employer/shops/[shopId]/notices/register/index.tsx
index 3ec25b4..0900a43 100644
--- a/src/pages/employer/shops/[shopId]/notices/register/index.tsx
+++ b/src/pages/employer/shops/[shopId]/notices/register/index.tsx
@@ -1,14 +1,19 @@
+import { Container } from '@/components/layout';
import { Button, DateInput, Input, Modal, TimeInput } from '@/components/ui';
import useAuth from '@/hooks/useAuth';
import axiosInstance from '@/lib/axios';
+import { cn } from '@/lib/utils/cn';
+import { TimeValue } from '@/types/calendar';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
+
interface NoticeLoad {
hourlyPay: number;
startsAt: string;
workhour: number;
description: string;
}
+
const EmployerNoticeRegisterPage = () => {
const router = useRouter();
const { user } = useAuth();
@@ -19,6 +24,7 @@ const EmployerNoticeRegisterPage = () => {
const [workhour, setWorkhour] = useState();
const [description, setDescription] = useState('');
+ const [pastTimeModal, setPastTimeModal] = useState(false);
const [accessModal, setAccessModal] = useState(false);
const [successModal, setSuccessModal] = useState(false);
const [modalHandler, setModalHandler] = useState<() => void>(() => () => {});
@@ -33,9 +39,31 @@ const EmployerNoticeRegisterPage = () => {
e.preventDefault();
if (!date || !time || !wage || !workhour || !description) return;
if (!user?.shop) return;
- const combinedDateTime = new Date(date);
- combinedDateTime.setHours(time.getHours(), time.getMinutes(), 0, 0);
- const payload: NoticeLoad = {
+
+ const now = new Date();
+ const period = time.getHours() >= 12 ? '오후' : '오전';
+ const hours24 =
+ period === '오후' && time!.getHours() !== 12
+ ? time!.getHours() + 12
+ : period === '오전' && time!.getHours() === 12
+ ? 0
+ : time!.getHours();
+
+ const combinedDateTime = new Date(
+ date.getFullYear(),
+ date.getMonth(),
+ date.getDate(),
+ hours24,
+ time.getMinutes(),
+ 0
+ );
+
+ if (combinedDateTime < now) {
+ setPastTimeModal(true);
+ return;
+ }
+
+ const noticeLoad: NoticeLoad = {
hourlyPay: Number(wage),
startsAt: combinedDateTime.toISOString(),
workhour,
@@ -43,7 +71,7 @@ const EmployerNoticeRegisterPage = () => {
};
try {
- const response = await axiosInstance.post(`/shops/${user.shop.item.id}/notices`, payload);
+ const response = await axiosInstance.post(`/shops/${user.shop.item.id}/notices`, noticeLoad);
const noticeId = response.data.item.id;
if (!noticeId) {
@@ -64,8 +92,8 @@ const EmployerNoticeRegisterPage = () => {
};
return (
-
-
공고 등록
+
+ 공고 등록
);
};
+
export default EmployerNoticeRegisterPage;
diff --git a/src/pages/my-profile/index.tsx b/src/pages/my-profile/index.tsx
index 4c5ef50..60b531b 100644
--- a/src/pages/my-profile/index.tsx
+++ b/src/pages/my-profile/index.tsx
@@ -1,13 +1,18 @@
+// src/pages/my-profile/index.tsx
import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useMemo, useState } from 'react';
+
+import { Container } from '@/components/layout'; // ✅ my-shop과 동일 패턴
import Frame from '@/components/layout/frame/frame';
import Button from '@/components/ui/button/button';
import Table from '@/components/ui/table/Table';
-import type { TableRowProps } from '@/components/ui/table/TableRowProps';
+
import { ICONS, ICON_SIZES } from '@/constants/icon';
import { useUserApplications } from '@/context/userApplicationsProvider';
import useAuth from '@/hooks/useAuth';
+
+import type { TableRowProps } from '@/components/ui/table/TableRowProps';
import type { ApiResponse } from '@/types/api';
import type { ApplicationItem } from '@/types/applications';
import type { User, UserType } from '@/types/user';
@@ -33,12 +38,13 @@ export default function MyProfileDetailPage() {
const headers: string[] = ['가게명', '근무일시', '시급', '상태'];
const userType: UserType = 'employee';
- // 서버 응답 → TableRowProps 매핑
+ // 서버 응답 → TableRowProps 매핑 (shopId/noticeId 포함)
const rows: TableRowProps[] = useMemo(() => {
return applications.map((app: ApiResponse
) => {
const a = app.item;
const status =
a.status === 'accepted' ? 'approved' : a.status === 'rejected' ? 'rejected' : 'pending';
+
return {
id: a.id,
name: a.shop.item.name,
@@ -46,126 +52,159 @@ export default function MyProfileDetailPage() {
startsAt: a.notice.item.startsAt,
workhour: a.notice.item.workhour,
status,
- // employee 표에서는 미사용 — 타입만 충족
bio: '',
phone: '',
+ // ✅ TableRow가 요구하는 키 채움
+ shopId: a.shop.item.id,
+ noticeId: a.notice.item.id,
};
});
}, [applications]);
- const pagedRows = useMemo(() => rows.slice(offset, offset + limit), [rows, offset]);
+ // 로딩 중에도 마지막 성공 데이터를 유지 (화면 흔들림 방지)
+ const [stableRows, setStableRows] = useState([]);
+ const [stableTotal, setStableTotal] = useState(0);
+ useEffect(() => {
+ if (rows.length > 0) {
+ setStableRows(rows);
+ setStableTotal(applications.length);
+ }
+ }, [rows, applications.length]);
- // rows 변화 시 첫 페이지로 리셋 (페이지네이션 UX 보강)
+ // rows 변화 시 첫 페이지로 리셋
useEffect(() => {
setOffset(0);
}, [rows.length]);
- return (
-
-
-
내 프로필
-
- {/* 프로필이 없으면 등록 프레임 */}
- {profileIsEmpty ? (
-
- ) : (
- // 프로필 카드(피그마 스타일)
-
-
-
-
이름
-
- {user?.name || '—'}
-
-
- {/* 연락처 */}
-
-
- {user?.phone || '—'}
-
+ const currentRows = rows.length > 0 ? rows : stableRows;
+ const currentTotal = rows.length > 0 ? applications.length : stableTotal;
- {/* 선호 지역 */}
-
-
- 선호 지역: {(user?.address as string) || '—'}
-
+ const pagedRows = useMemo(() => currentRows.slice(offset, offset + limit), [currentRows, offset]);
+ const displayOffset = currentTotal > limit ? (offset === 0 ? 2 : offset) : offset;
+ return (
+ <>
+ {/* ───────────────── 상단 영역 ───────────────── */}
+ {profileIsEmpty ? (
+ // ✅ 2중 컨테이너 제거: Frame만 단독 렌더
+
+ ) : (
+ // ✅ 공용 Container로 감싸서 my-shop과 동일한 레이아웃 패턴
+
+
+
+ 내 프로필
+
- {/* 소개 */}
- {user?.bio && (
-
- {user.bio}
+
+
+
+
이름
+
+ {user?.name || '—'}
- )}
-
- {/* 우상단 편집 버튼 */}
-
-
+ {/* 연락처 */}
+
+
+ {user?.phone || '—'}
+
+
+ {/* 선호 지역 */}
+
+
+
+ 선호 지역: {(user?.address as string) || '—'}
+
+
+
+ {/* 소개 */}
+ {user?.bio && (
+
+ {user.bio}
+
+ )}
+
+
+ {/* 우상단 편집 버튼 */}
+
+
+
-
-
- )}
-
+
+
+
+ )}
- {/* 신청 내역 — 프로필 있고 로그인 상태일 때만 */}
+ {/* ──────────────── 하단: 신청 내역 ──────────────── */}
{!profileIsEmpty && isLogin && (
-
- {isLoading ? (
- 불러오는 중…
- ) : rows.length === 0 ? (
-
-
-
+
+ {isLoading && currentRows.length === 0 ? (
+
+
+
신청 내역
+
+
+
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+
+
+
+ ) : currentRows.length === 0 ? (
+ // ✅ 2중 컨테이너 제거: Frame만 단독 렌더
+
) : (
-
+
{}} // ✅ 이 페이지에서는 상태 변경 없음(필수 prop 무해한 no-op)
/>
-
+
)}
)}
-
+ >
);
}
diff --git a/src/pages/my-profile/register.tsx b/src/pages/my-profile/register.tsx
index 3a68053..ad5f6f3 100644
--- a/src/pages/my-profile/register.tsx
+++ b/src/pages/my-profile/register.tsx
@@ -9,12 +9,11 @@ import { useEffect, useState } from 'react';
import { ADDRESS_CODE, type AddressCode } from '@/constants/dropdown';
-/** 폼 타입 */
type ProfileForm = {
name: string;
phone: string;
region: AddressCode | '';
- bio: string;
+ bio: string; // bio는 선택
};
export default function MyProfileRegisterPage() {
@@ -33,15 +32,13 @@ export default function MyProfileRegisterPage() {
const [regionErrorMessage, setRegionErrorMessage] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
- const [isDoneOpen, setIsDoneOpen] = useState(false); // 완료 모달
- const [isCancelOpen, setIsCancelOpen] = useState(false); // 취소 확인 모달
+ const [isDoneOpen, setIsDoneOpen] = useState(false);
+ const [isCancelOpen, setIsCancelOpen] = useState(false);
- // 로그인 가드
useEffect(() => {
if (!isLogin) router.replace('/login');
}, [isLogin, router]);
- // 기존 값 프리필(컨텍스트 user 사용)
useEffect(() => {
if (!isLogin || !user) return;
setFormState({
@@ -76,12 +73,11 @@ export default function MyProfileRegisterPage() {
setIsSubmitting(true);
try {
- // 서버 반영 + 컨텍스트 동기화
await updateUser({
name: formState.name.trim(),
phone: formState.phone.trim(),
address: formState.region,
- bio: formState.bio,
+ bio: formState.bio, // 선택값: 빈 문자열 가능
});
setIsDoneOpen(true);
} finally {
@@ -91,7 +87,7 @@ export default function MyProfileRegisterPage() {
return (
- {/* 우상단 닫기(X) 버튼 */}
+ {/* 닫기 */}
+
내 프로필