diff --git a/.gitignore b/.gitignore
index 7ceb59f8..b5a8a278 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
+mocks
# Editor directories and files
.vscode/*
diff --git a/package-lock.json b/package-lock.json
index 4a751573..48c536c8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.1",
+ "tailwind-scrollbar-hide": "^4.0.0",
"tailwindcss": "^4.1.8"
},
"devDependencies": {
@@ -4109,6 +4110,15 @@
"url": "https://opencollective.com/synckit"
}
},
+ "node_modules/tailwind-scrollbar-hide": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/tailwind-scrollbar-hide/-/tailwind-scrollbar-hide-4.0.0.tgz",
+ "integrity": "sha512-gobtvVcThB2Dxhy0EeYSS1RKQJ5baDFkamkhwBvzvevwX6L4XQfpZ3me9s25Ss1ecFVT5jPYJ50n+7xTBJG9WQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.8.tgz",
diff --git a/package.json b/package.json
index fa551def..d109a749 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.1",
+ "tailwind-scrollbar-hide": "^4.0.0",
"tailwindcss": "^4.1.8"
},
"devDependencies": {
diff --git a/src/assets/icons/.gitkeep b/src/assets/icons/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/assets/icons/ic_close.svg b/src/assets/icons/ic_close.svg
new file mode 100644
index 00000000..50f36dc1
--- /dev/null
+++ b/src/assets/icons/ic_close.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/components/common/.gitkeep b/src/components/common/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/components/common/NotificationModal/NotificationCard.tsx b/src/components/common/NotificationModal/NotificationCard.tsx
new file mode 100644
index 00000000..741a1c13
--- /dev/null
+++ b/src/components/common/NotificationModal/NotificationCard.tsx
@@ -0,0 +1,48 @@
+import calculateTimeDifference from '@/utils/calculateTimeDefference';
+import formatWorkTime from '@/utils/formatWorkTime';
+
+interface NotificationCardProps {
+ status: 'accepted' | 'rejected'; // 공고 지원 상태
+ restaurantName: string; // 음식점 이름
+ startsAt: string; // 공고 시작 시간 (ISO 8601 문자열)
+ workHour: number; // 근무 시간 (시간 단위)
+ createdAt: string; // 알림 생성 시간 (ISO 8601 문자열)
+}
+
+/*
+ * 사용자가 공고에 지원한 결과를 알리는 알림 카드 컴포넌트입니다.
+ * 상태에 따라 '승인' 혹은 '거절'로 표시되며,
+ * 가게 이름과 알바 시간, 생성 시간을 보여줍니다.
+ */
+
+export default function NotificationCard({
+ status,
+ restaurantName,
+ startsAt,
+ workHour,
+ createdAt,
+}: NotificationCardProps) {
+ const formattedTime = formatWorkTime({
+ startsAt,
+ workHour,
+ });
+ const formattedCreatedAt = calculateTimeDifference(createdAt);
+ const formattedStatus = status === 'accepted' ? '승인' : '거절';
+ const formattedStatusClass =
+ status === 'accepted' ? 'text-blue-20' : 'text-red-20';
+ return (
+
+ {status === 'accepted' ? (
+
+ ) : (
+
+ )}
+
+ {restaurantName} ({formattedTime}) 공고 지원이{' '}
+ {formattedStatus}
+ 되었어요.
+
+
{formattedCreatedAt}
+
+ );
+}
diff --git a/src/components/common/NotificationModal/NotificationModal.tsx b/src/components/common/NotificationModal/NotificationModal.tsx
new file mode 100644
index 00000000..1f9508f8
--- /dev/null
+++ b/src/components/common/NotificationModal/NotificationModal.tsx
@@ -0,0 +1,57 @@
+import { useNavigate } from 'react-router-dom';
+import NotificationCard from './NotificationCard';
+import close from '@/assets/icons/ic_close.svg';
+
+interface NotificationItem {
+ createdAt: string; // 생성 일시 (ISO 8601 문자열)
+ result: 'accepted' | 'rejected'; // 결과 상태
+ read: boolean; // 읽음 여부
+ shop: {
+ item: {
+ name: string; // 가게 이름
+ };
+ };
+ notice: {
+ item: {
+ startsAt: string; // 근무 시작 시간 (ISO 8601 문자열)
+ workhour: number; // 근무 시간 (시간 단위)
+ };
+ };
+}
+interface NotificationModalProps {
+ data: NotificationItem[]; // 알림 데이터 배열
+ count: number; // 알림 개수
+}
+
+/*
+ * 알림 모달 컴포넌트입니다.
+ * 알림 데이터들을 카드로 표시하며, 스크롤 가능합니다.
+ * PC/Tablet에서는 모달 형식으로, 바깥을 클릭하거나 esc를 통해 창을 닫을 수 있습니다.
+ * 모바일에서는 닫기 버튼으로 창을 닫을 수 있습니다.
+ */
+
+export default function NotificationModal({ data, count }: NotificationModalProps) {
+ const navigate = useNavigate();
+ return (
+
+
+
알림 {count}개
+
+
+
+ {data.map((data, index) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/index.css b/src/index.css
index 7c5d0cb0..86ab5ca7 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,4 +1,5 @@
@import 'tailwindcss';
+@import 'tailwind-scrollbar-hide/v4';
@font-face {
font-family: 'SpoqaHanSansNeo-Regular';
@@ -96,5 +97,6 @@
--text-body1: 16px;
--text-body2: 14px;
--text-caption: 12px;
+ --shadow-custom: 0px 2px 8px 0px rgba(120, 116, 134, 0.25);
--spacing: 0.0625rem;
}
diff --git a/src/utils/.gitkeep b/src/utils/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/utils/calculateTimeDefference.ts b/src/utils/calculateTimeDefference.ts
new file mode 100644
index 00000000..90901985
--- /dev/null
+++ b/src/utils/calculateTimeDefference.ts
@@ -0,0 +1,26 @@
+/* 주어진 날짜로부터 현재까지의 경과 시간을 사람이 읽기 좋은 문자열로 반환하는 함수 */
+export default function calculateTimeDifference(createdAt: string | number): string {
+ const currentDate = new Date();
+ const createdDate = new Date(createdAt);
+
+ const timeDifference = currentDate.getTime() - createdDate.getTime();
+ const minutes = Math.floor(timeDifference / (1000 * 60));
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+ const months = Math.floor(days / 30);
+ const years = Math.floor(months / 12);
+
+ if (minutes < 1) {
+ return '방금';
+ } else if (minutes <= 59) {
+ return `${minutes}분 전`;
+ } else if (hours <= 23) {
+ return `${hours}시간 전`;
+ } else if (days <= 30) {
+ return `${days}일 전`;
+ } else if (months <= 11) {
+ return `${months}달 전`;
+ } else {
+ return `${years}년 전`;
+ }
+}
\ No newline at end of file
diff --git a/src/utils/formatWorkTime.ts b/src/utils/formatWorkTime.ts
new file mode 100644
index 00000000..92b4c5b7
--- /dev/null
+++ b/src/utils/formatWorkTime.ts
@@ -0,0 +1,29 @@
+interface workTime {
+ startsAt: string | number; // 근무 시작 시간 (ISO 8601 형식 문자열)
+ workHour: number; // 근무 시간 (시간 단위)
+}
+
+/* 주어진 근무 시작 시간과 근무 시간을 기반으로 근무 시작~종료 시간을 "YYYY-MM-DD HH:mm~HH:mm" 형식의 문자열로 반환하는 함수 */
+export default function formatWorkTime({
+ startsAt,
+ workHour,
+}: workTime): string {
+ const date = new Date(startsAt);
+
+ const kstDate = new Date(date.getTime() + 9 * 60 * 60 * 1000);
+
+ /* 시각이 24시를 넘으면 다음날로 설정되어 자동으로 처리 */
+ const endDate = new Date(kstDate);
+ endDate.setHours(endDate.getHours() + workHour);
+
+ const year = kstDate.getFullYear();
+ const month = String(kstDate.getMonth() + 1).padStart(2, '0');
+ const day = String(kstDate.getDate()).padStart(2, '0');
+ const startHours = String(kstDate.getHours()).padStart(2, '0');
+ const startMinutes = String(kstDate.getMinutes()).padStart(2, '0');
+
+ const endHours = String(endDate.getHours()).padStart(2, '0');
+ const endMinutes = String(endDate.getMinutes()).padStart(2, '0');
+
+ return `${year}-${month}-${day} ${startHours}:${startMinutes}~${endHours}:${endMinutes} `;
+}