Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a97775b
feat: FCM 연결 및 모달 생성, API 연결
yeonjin719 Aug 5, 2025
d63ffd1
fix: navigate 삭제
yeonjin719 Aug 5, 2025
b8e138a
fix: 안쓰는 import 삭제
yeonjin719 Aug 5, 2025
ea72f93
fix: 오타 수정
yeonjin719 Aug 5, 2025
3535bc4
fix: 코드 리뷰 피드백 반영
yeonjin719 Aug 5, 2025
0fa5d08
fix: 빌드 에러 수정
yeonjin719 Aug 5, 2025
d9c050b
fix: 에러 페이지 import 명 변경, default 값 통일
yeonjin719 Aug 5, 2025
fb760b9
fix: 설정 모달 반응형 구현 (#89)
Seojegyeong Aug 5, 2025
aa782d9
Merge branch 'develop' into feature/#87
yeonjin719 Aug 5, 2025
6fdc4c2
Merge pull request #88 from WithTime12/feature/#87
yeonjin719 Aug 5, 2025
e8bd25e
fix: 사파리 폰트 설정 추가
yeonjin719 Aug 10, 2025
6e983be
feat: 날씨, 지역 제외 api 연결 완료
SEBINorSEBOUT Aug 12, 2025
ec6d06a
feat: home 화면 API 연결
yeonjin719 Aug 12, 2025
41b76e5
fix: 코드 리뷰 반영
yeonjin719 Aug 12, 2025
ed6ad3f
fix: import 오류 수정
yeonjin719 Aug 12, 2025
9e61971
fix: 안쓰는 라이브러리 삭제
yeonjin719 Aug 12, 2025
a7dc908
feat: 공지사항 API 연결
yeonjin719 Aug 12, 2025
9df3483
fix: 코드리뷰 반영
yeonjin719 Aug 12, 2025
3b98c27
fix: 코드 리뷰 반영
yeonjin719 Aug 12, 2025
4e12a14
Merge pull request #94 from WithTime12/bugfix/#93
yeonjin719 Aug 12, 2025
3f9d4ab
feat: 메인 화면 캐릭터 사진 추가
yeonjin719 Aug 12, 2025
914dbdf
fix: 코드 리뷰 반영
yeonjin719 Aug 12, 2025
4315e1a
feat: 배너 이미지 추가
yeonjin719 Aug 12, 2025
c1b8da1
feat: 에러 수정
yeonjin719 Aug 13, 2025
c6fd58b
Merge pull request #96 from WithTime12/feature/#91
yeonjin719 Aug 13, 2025
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
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import tsConfig from './eslint/typescript.mjs';

export default [
{
ignores: ['src/vite-env.d.ts'],
ignores: ['src/vite-env.d.ts', 'public/firebase-messaging-sw.js'],
},
{
files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'],
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"chart.js": "^4.5.0",
"chartjs-plugin-datalabels": "^2.2.0",
"clsx": "^2.1.1",
"firebase": "^12.0.0",
"lodash": "^4.17.21",
"lodash.throttle": "^4.1.1",
"path": "^0.12.7",
Expand All @@ -30,7 +31,9 @@
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.60.0",
"react-intersection-observer": "^9.16.0",
"react-router-dom": "^7.6.0",
"react-spinners": "^0.17.0",
"sonner": "^2.0.3",
"tailwindcss": "^4.1.7",
"wordcloud": "^1.2.3",
Expand Down
42 changes: 42 additions & 0 deletions public/firebase-messaging-sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/// <reference lib="webworker" />
/* eslint-env serviceworker */
/* global firebase importScripts clients */
importScripts('https://www.gstatic.com/firebasejs/10.13.2/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/10.13.2/firebase-messaging-compat.js');

firebase.initializeApp({
apiKey: 'AIzaSyAjZqK2lhCOeX_P2Sf-_2IGEFlORchcO5w',
authDomain: 'withtime-ff471.firebaseapp.com',
projectId: 'withtime-ff471',
storageBucket: 'withtime-ff471.firebasestorage.app',
messagingSenderId: '47995224236',
appId: '1:47995224236:web:85371605ce4a6659529f09',
measurementId: 'G-5E8Q23LL4H',
});

const messaging = firebase.messaging();

self.addEventListener('push', function (event) {
try {
const payload = event.data.json();
const title = payload.notification.title;

const options = {
body: payload.notification.body,
icon: payload.notification.icon,
data: payload.notification.click_action,
};

event.waitUntil(self.registration.showNotification(title, options));
} catch (error) {
console.error('Push event error:', error);
}
});

self.addEventListener('notificationclick', function (event) {
console.log(event.notification);

event.notification.close();

event.waitUntil(clients.openWindow(event.notification.data).catch((error) => console.error('Failed to open window:', error)));
});
10 changes: 10 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard-dynamic-subset.css');

html, body, button, input, select, textarea {
font-family: 'Pretendard',
-apple-system, BlinkMacSystemFont, system-ui,
'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans KR', 'Apple SD Gothic Neo', 'Malgun Gothic',
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
sans-serif;
}
18 changes: 18 additions & 0 deletions src/api/alarm/alarm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { TRequestGetAlarm, TRequestPostDeviceToken, TResponseGetAlarm, TResponsePostDeviceToken } from '@/types/alarm/alarm';

import { axiosInstance } from '@/api/axiosInstance';

export const getAlarm = async ({ size = 5, cursor }: TRequestGetAlarm): Promise<TResponseGetAlarm> => {
const { data } = await axiosInstance.get('/api/v1/alarms', {
params: {
size,
cursor,
},
});
return data;
};

export const postDeviceToken = async ({ deviceToken }: TRequestPostDeviceToken): Promise<TResponsePostDeviceToken> => {
const { data } = await axiosInstance.post('/api/v1/alarms/device-tokens', { deviceToken });
return data;
};
2 changes: 1 addition & 1 deletion src/api/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
TSocialLoginValues,
} from '@/types/auth/auth';

import { axiosInstance } from '../axiosInstance';
import { axiosInstance } from '@/api/axiosInstance';

export const defaultSignup = async ({ email, password, username, gender, phoneNumber, birth, socialId }: TSignupValues): Promise<TSignupResponse> => {
const { data } = await axiosInstance.post('/api/v1/auth/sign-up', { email, password, socialId, username, gender, phoneNumber, birth });
Expand Down
2 changes: 1 addition & 1 deletion src/api/course/course.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TSearchRegionResponse, TSearchRegionValues } from '@/types/dateCourse/dateCourse';

import { axiosInstance } from '../axiosInstance';
import { axiosInstance } from '@/api/axiosInstance';

export const searchRegion = async ({ keyword }: TSearchRegionValues): Promise<TSearchRegionResponse> => {
const { data } = await axiosInstance.get('/api/v1/regions/search', {
Expand Down
8 changes: 8 additions & 0 deletions src/api/home/dateCourse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { TDateCourseSavedCountResponse } from '@/types/home/dateCourse';

import { axiosInstance } from '@/api/axiosInstance';

export const getDateCourseSavedCount = async (): Promise<TDateCourseSavedCountResponse> => {
const { data } = await axiosInstance.get('/api/v1/logs/datecourses/saved-count');
return data;
};
14 changes: 14 additions & 0 deletions src/api/home/dateTimes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { TGetDateTimeStates, TMonthlyDatePlaceResponse } from '../../types/home/datePlace';

import { axiosInstance } from '@/api/axiosInstance';

// 월별 데이트 장소 수 조회 API
export const getMonthlyDatePlaceStates = async (): Promise<TMonthlyDatePlaceResponse> => {
const { data } = await axiosInstance.get('/api/v1/logs/dateplaces/monthly');
return data;
};

export const getDateTimeStates = async (): Promise<TGetDateTimeStates> => {
const { data } = await axiosInstance.get('/api/v1/logs/datecourses/average');
return data;
};
9 changes: 9 additions & 0 deletions src/api/home/keyword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { TWeeklyKeywordResponse } from '@/types/home/keyword';

import { axiosInstance } from '@/api/axiosInstance';

// 이번 주 인기 키워드 조회 API
export const getWeeklyKeywords = async (): Promise<TWeeklyKeywordResponse> => {
const { data } = await axiosInstance.get('/api/v1/logs/keyword/weekly');
return data;
};
9 changes: 9 additions & 0 deletions src/api/home/level.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { TUserGradeResponse } from '@/types/home/level';

import { axiosInstance } from '@/api/axiosInstance';

// 사용자 등급 조회 API
export const getUserGrade = async (): Promise<TUserGradeResponse> => {
const { data } = await axiosInstance.get('/api/v1/members/grade');
return data;
};
13 changes: 13 additions & 0 deletions src/api/home/region.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { TGetUserRegionResponse, TPatchUserRegionRequest, TPatchUserRegionResponse } from '@/types/home/region';

import { axiosInstance } from '@/api/axiosInstance';

export const patchUserRegion = async ({ regionId }: TPatchUserRegionRequest): Promise<TPatchUserRegionResponse> => {
const { data } = await axiosInstance.patch('/api/v1/regions/users', { regionId });
return data;
};

export const getUserRegion = async (): Promise<TGetUserRegionResponse> => {
const { data } = await axiosInstance.get('/api/v1/regions/users/current');
return data;
};
22 changes: 22 additions & 0 deletions src/api/home/weather.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type {
TGetPrecipitationRequest,
TGetPrecipitationResponse,
TGetWeeklyWeatherRecommendationRequest,
TGetWeeklyWeatherRecommendationResponse,
} from '@/types/home/weather';

import { axiosInstance } from '@/api/axiosInstance';

// 주간 날씨 추천 조회 API
export const getWeeklyWeatherRecommendation = async ({
regionId,
startDate,
}: TGetWeeklyWeatherRecommendationRequest): Promise<TGetWeeklyWeatherRecommendationResponse> => {
const { data } = await axiosInstance.get(`/api/v1/weather/${regionId}/weekly`, { params: { startDate } });
return data;
};

export const getPrecipitation = async ({ regionId, startDate }: TGetPrecipitationRequest): Promise<TGetPrecipitationResponse> => {
const { data } = await axiosInstance.get(`/api/v1/weather/${regionId}/precipitation`, { params: { startDate } });
return data;
};
16 changes: 4 additions & 12 deletions src/api/notice/notice.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import type { TFetchNoticeDetailResponse, TFetchNoticesResponse } from '@/types/notice/notice';
import type { TFetchNoticeDetailResponse, TFetchNoticesResponse, TRequestGetNoticeRequest } from '@/types/notice/notice';

import { axiosInstance } from '../axiosInstance';
import { axiosInstance } from '@/api/axiosInstance';

// 공지사항 전체 조회 API
export const fetchNotices = async ({
category,
page,
size,
}: {
category: 'SERVICE' | 'SYSTEM';
page: number;
size: number;
}): Promise<TFetchNoticesResponse> => {
export const fetchNotices = async ({ noticeCategory = 'SERVICE', page, size }: TRequestGetNoticeRequest): Promise<TFetchNoticesResponse> => {
const { data } = await axiosInstance.get('/api/v1/notices', {
params: { noticeCategory: category, page, size },
params: { noticeCategory: noticeCategory, page, size },
});
return data;
};
Expand Down
3 changes: 1 addition & 2 deletions src/assets/icons/weather/rain.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions src/components/alarmModal/alarm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { TAlarm } from '@/types/alarm/alarm';

import ChevronForward from '@/assets/icons/default_arrows/chevron_forward.svg?react';

function Alarm({ title }: TAlarm) {
return (
<div className="flex items-center justify-between w-full py-[24px] border-b-[2px] border-b-default-gray-400">
<div className="text-default-gray-800 text-[22px] sm:w-[500px] w-[200px] whitespace-nowrap overflow-hidden text-ellipsis">{title}</div>
<ChevronForward className="self-center" />
</div>
);
}

export default Alarm;
2 changes: 1 addition & 1 deletion src/components/common/EditableInputBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default function EditableInputBox({
`;

return (
<div className={`w-[360px] ${className}`}>
<div className={`w-full max-w-[360px] ${className}`}>
{label && <p className="font-body1 text-default-gray-700 mb-1">{label}</p>}

<div className="relative w-full">
Expand Down
31 changes: 8 additions & 23 deletions src/components/common/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//setting - ON, OFF Toggle Switch
import { useEffect, useState } from 'react';

// Props 정의
interface IToggleSwitchProps {
value?: boolean;
onChange?: (val: boolean) => void;
Expand All @@ -11,17 +9,14 @@ interface IToggleSwitchProps {
}

export default function ToggleSwitch({ value, onChange, onLabel = 'ON', offLabel = 'OFF', className = '' }: IToggleSwitchProps) {
// 내부 상태 관리 (초깃값은 props.value, 없으면 false)
const [isOn, setIsOn] = useState(value ?? false);

// 외부에서 value가 바뀌면 내부 상태도 동기화
useEffect(() => {
if (value !== undefined) {
setIsOn(value);
}
}, [value]);

// 버튼 클릭 시 상태 토글
const handleToggle = () => {
const next = !isOn;
setIsOn(next);
Expand All @@ -31,32 +26,22 @@ export default function ToggleSwitch({ value, onChange, onLabel = 'ON', offLabel
return (
<button
onClick={handleToggle}
className={`relative w-[100px] h-[40px] rounded-full flex items-center px-2 transition-colors duration-300
${isOn ? 'bg-primary-500' : 'bg-default-gray-400'} ${className}`}
className={`relative w-[80px] min-w-[80px] h-[36px] shrink-0 rounded-full flex items-center px-2 transition-colors duration-300
${isOn ? 'bg-primary-500' : 'bg-default-gray-400'} ${className}`}
>
{/* ON */}
<span
className={`absolute left-[15px] top-1/2 -translate-y-1/2 font-heading3 text-white transition-opacity duration-300 ${
isOn ? 'opacity-100' : 'opacity-0'
}`}
>
{/* 텍스트 ON */}
<span className={`absolute left-[36px] font-heading3 text-white transition-opacity duration-200 ${isOn ? 'opacity-100' : 'opacity-0'}`}>
{onLabel}
</span>

{/* OFF */}
<span
className={`absolute right-[15px] top-1/2 -translate-y-1/2 font-heading3 text-white transition-opacity duration-300 ${
!isOn ? 'opacity-100' : 'opacity-0'
}`}
>
{/* 텍스트 OFF */}
<span className={`absolute right-[36px] font-heading3 text-white transition-opacity duration-200 ${!isOn ? 'opacity-100' : 'opacity-0'}`}>
{offLabel}
</span>

{/* 원형 토글 - 좌우 슬라이드 */}
{/* 원형 토글 */}
<div
className={`w-6 h-6 bg-white rounded-full shadow-default transform transition-transform duration-300 ${
isOn ? 'translate-x-[60px]' : 'translate-x-0'
}`}
className={`w-6 h-6 bg-white rounded-full shadow-default transition-transform duration-300 ${isOn ? 'translate-x-0' : 'translate-x-[40px]'}`}
/>
</button>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/graySvgButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { ReactElement } from 'react';
import React from 'react';

import ArrowLeftCircle from '@/assets/icons/Arrow_left_circle.svg?react';
import ErrorCircle from '@/assets/icons/Error-circle_Fill.svg?react';

type TGraySVGButton = {
child?: ReactElement;
child?: React.ReactNode;
type?: 'cancle' | 'backward';
onClick: () => void;
size?: 'big' | 'default';
Expand Down
13 changes: 10 additions & 3 deletions src/components/common/modalProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

import DateCourseSearchFilterModal from '../modal/dateCourseSearchFilterModal';
import ErrorModal from '../modal/errorModal';
import SettingsModal from '../modal/SettingModal';
import AlarmModal from '@/components/modal/alarmModal';
import DateCourseSearchFilterModal from '@/components/modal/dateCourseSearchFilterModal';
import ErrorModal from '@/components/modal/errorModal';
import SettingsModal from '@/components/modal/SettingModal';

import RegionModal from '../modal/regionModal';

import useModalStore from '@/store/useModalStore';

Expand All @@ -13,12 +16,16 @@ export const MODAL_TYPES = {
ErrorModal: 'ErrorModal',
DateCourseSearchFilterModal: 'DateCourseSearchFilterModal',
SettingsModal: 'SettingsModal', //설정 모달 추가
AlarmModal: 'AlarmModal',
RegionModal: 'RegionModal',
};

export const MODAL_COMPONENTS = {
[MODAL_TYPES.ErrorModal]: ErrorModal,
[MODAL_TYPES.DateCourseSearchFilterModal]: DateCourseSearchFilterModal,
[MODAL_TYPES.SettingsModal]: SettingsModal,
[MODAL_TYPES.AlarmModal]: AlarmModal,
[MODAL_TYPES.RegionModal]: RegionModal,
};

export default function ModalProvider() {
Expand Down
Loading