Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 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
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default [
{
rules: {
'prettier/prettier': 'warn',
'no-console': ['error', { allow: ['warn', 'error'] }],
},
},
tsConfig,
Expand Down
12 changes: 10 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import './App.css';
import '@/App.css';

import { useMemo } from 'react';
import { RouterProvider } from 'react-router-dom';

import { DeviceTokenProvider } from '@/providers/deviceTokenProvider';
import { alarmKeys } from '@/queryKey/queryKey';
import router from '@/routes/routes';

function App() {
return <RouterProvider router={router} />;
const refetchKeys = useMemo(() => [alarmKeys.all.queryKey], []);
return (
<DeviceTokenProvider refetchKeys={refetchKeys}>
<RouterProvider router={router} />
</DeviceTokenProvider>
);
}

export default App;
22 changes: 9 additions & 13 deletions src/api/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,26 @@ axiosInstance.interceptors.response.use(
const refreshResponse = await refresh();

if (refreshResponse.code === '200') {
console.log('refreshToken이 재발급 되었습니다');
isRedirecting = false;

return axiosInstance(error.config);
}
} catch (errors) {
if (axios.isAxiosError(errors)) {
const refreshError = error as AxiosError<IRefreshResponse>;
const refreshError = errors as AxiosError<IRefreshResponse>;
if (refreshError.response?.data.message === 'The token is null.') {
console.log('refreshToken이 없습니다. 로그인 페이지로 이동합니다.');
console.error('refreshToken이 없습니다. 로그인 페이지로 이동합니다.');
void logout();
} else if (refreshError.response?.data.message === 'The token is invalid.') {
console.log('refreshToken이 만료되었습니다. 로그인 페이지로 이동합니다.');
logout();
console.error('refreshToken이 만료되었습니다. 로그인 페이지로 이동합니다.');
void logout();
} else {
if (refreshError.response?.data.message === 'Incorrect password.') {
alert('Your email or password is incorrect.');
} else {
console.log('알 수 없는 오류가 발생했습니다', errors);
logout();
}
console.error('알 수 없는 오류가 발생했습니다', errors);
void logout();
}
} else {
console.log('알 수 없는 오류가 발생했습니다', errors);
logout();
console.error('알 수 없는 오류가 발생했습니다', errors);
void logout();
}

return Promise.reject(errors);
Expand Down
19 changes: 1 addition & 18 deletions src/components/auth/commonAuthInput.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import type { InputHTMLAttributes } from 'react';
import React from 'react';

import formatInputNumber from '@/utils/formatPhoneNumber';

import Button from '@/components/common/Button';

import AlertCircle from '@/assets/icons/alert-circle_Fill.svg?react';
Expand Down Expand Up @@ -58,21 +56,6 @@ const CommonAuthInput = React.forwardRef<HTMLInputElement, TCommonAuthInputProps
className={`flex-1 relative flex bg-default-gray-100 rounded-[4px] max-h-[56px] min-w-0 h-[8vh] pl-[16px] text-default-gray-800 focus:outline-none focus:ring-0
${error ? 'border-[2px] border-warning caret-warning' : `${validation ? 'border-primary-500 border-[2px] ' : 'border-default-gray-700 border-[1px]'}`}
`}
onChange={(e) => {
const rawValue = e.target.value;
const formatted = type === 'phoneNum' ? formatInputNumber(rawValue) : rawValue;

// 외부에서 넘긴 onChange 핸들러에 적용된 값 전달
if (rest.onChange) {
rest.onChange({
...e,
target: {
...e.target,
value: formatted,
},
});
}
}}
{...rest}
/>
{short && <div className="flex px-[16px] py-[8px] text-default-gray-100 w-[80px]" />}
Expand All @@ -96,7 +79,7 @@ const CommonAuthInput = React.forwardRef<HTMLInputElement, TCommonAuthInputProps
className={`hover:!cursor-default ${validation ? 'hover:!bg-primary-500 hover:!text-[#ffffff]' : 'hover:!bg-default-gray-400'}`}
/>
)}
{error && <div className="absolute top-[62px] font-caption text-warning left-[16px] select-none">{errorMessage}</div>}
{error && <div className="absolute top-[58px] font-caption text-warning left-[16px] select-none">{errorMessage}</div>}
{error && <AlertCircle fill="#ff517c" className={`absolute ${button || short || validationState ? 'right-30' : ' right-3'}`} />}
</div>
);
Expand Down
32 changes: 18 additions & 14 deletions src/components/common/PasswordEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState } from 'react';
import type { AxiosError } from 'axios';
import { z } from 'zod';

import { useAccount } from '@/hooks/auth/useAccount';
Expand Down Expand Up @@ -28,16 +29,7 @@ export default function PasswordEditSection() {
setIsEditing(false);
};

const { mutate: changePw, isPending } = useChangePassword({
onSuccess: () => {
alert('비밀번호가 변경되었습니다.');
handleCancel();
},
onError: (err) => {
const msg = (err as any)?.response?.data?.message ?? '비밀번호 변경에 실패했습니다.';
alert(msg);
},
});
const { mutate: changePw, isPending } = useChangePassword();

// 제출
const handleSubmit = () => {
Expand All @@ -58,10 +50,22 @@ export default function PasswordEditSection() {
if (Object.keys(nextErrors).length > 0) return;

// 제출
changePw({
currentPassword: currentPw,
newPassword: newPw,
});
changePw(
{
currentPassword: currentPw,
newPassword: newPw,
},
{
onSuccess: () => {
alert('비밀번호가 변경되었습니다.');
handleCancel();
},
onError: (err: AxiosError) => {
const msg = (err as any)?.response?.data?.message ?? '비밀번호 변경에 실패했습니다.';
alert(msg);
},
},
);
};

// 공통 인풋 스타일
Expand Down
1 change: 0 additions & 1 deletion src/components/dateCourse/dateCourse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ function DateCourse({ defaultOpen = false }: { defaultOpen?: boolean }) {
} else {
setIsBookmarked(!isBookmarked);
}
// console.log('북마크 해제');
};

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/dateCourse/dateCourseSearchFilterOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function DateCourseSearchFilterOption({ options, type, value, onC
mode="search"
onSearchClick={handleSearch}
placeholder="ex: 서울시 강남구"
className="w-full"
className="!w-full min-w-full"
value={inputValue}
onChange={handleInputChange}
/>
Expand Down
1 change: 0 additions & 1 deletion src/components/modal/dateCourseSearchFilterModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export default function DateCourseSearchFilterModal({ onClose }: TDateCourseSear
const [errorMessages, setErrorMessages] = useState<string[]>(Array(7).fill(''));

const handleSearch = () => {
console.log('선택된 필터:', answers);
onClose();
};

Expand Down
4 changes: 4 additions & 0 deletions src/components/modal/regionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { useUserRegion } from '@/hooks/home/useUserRegion';
import EditableInputBox from '@/components/common/EditableInputBox';
import Modal from '@/components/common/modal';

import { queryClient } from '@/api/queryClient';
import { homeKeys } from '@/queryKey/queryKey';

interface IRegionModalProps {
onClose: () => void;
}
Expand All @@ -23,6 +26,7 @@ function RegionModal({ onClose }: IRegionModalProps) {
},
{
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: homeKeys.userRegion().queryKey });
onClose();
},
},
Expand Down
35 changes: 20 additions & 15 deletions src/components/settingTab/AlarmSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { useGetAlarmSettings, usePatchAlarmSettings } from '@/hooks/settingAlarm

import ToggleSwitch from '@/components/common/ToggleSwitch';

import { queryClient } from '@/api/queryClient';
import { alarmKeys } from '@/queryKey/queryKey';

type TAlarmType = 'email' | 'push' | 'sms';

interface IAlarmSettingState {
Expand Down Expand Up @@ -36,22 +39,24 @@ export default function AlarmSetting() {

// 토글 핸들러
const handleToggle = (key: TAlarmType) => {
const prev = alarmSetting;
const next = { ...prev, [key]: !prev[key] };
setAlarmSetting(next);

patchAlarm(
{
emailAlarm: next.email,
pushAlarm: next.push,
smsAlarm: next.sms,
},
{
onError: () => setAlarmSetting(prev),
},
);
setAlarmSetting((prev) => {
const next = { ...prev, [key]: !prev[key] };
patchAlarm(
{
emailAlarm: next.email,
pushAlarm: next.push,
smsAlarm: next.sms,
},
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: alarmKeys.alarmSettings().queryKey });
},
onError: () => setAlarmSetting(prev),
},
);
return next;
});
};

const items: { label: string; key: TAlarmType }[] = [
{ label: 'Email 알람', key: 'email' },
{ label: '푸쉬 알람', key: 'push' },
Expand Down
72 changes: 37 additions & 35 deletions src/components/settingTab/InfoSetting.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';

import { TERMS_URL } from '@/constants/policies';

import { QUERY_KEYS, useAccount } from '@/hooks/auth/useAccount';
import { useAccount } from '@/hooks/auth/useAccount';

import EditableInputBox from '../common/EditableInputBox';
import PasswordEditSection from '../common/PasswordEdit';

import { queryClient } from '@/api/queryClient';
import ChevronForward from '@/assets/icons/default_arrows/chevron_forward.svg?react';

const getApiErrorMessage = (err: any, fallback: string) => err?.response?.data?.message ?? (err?.response?.status === 401 ? '로그인이 필요합니다.' : fallback);
import { memberKeys } from '@/queryKey/queryKey';

export default function InfoSetting() {
const qc = useQueryClient();
const { useGetMemberInfo, useChangeNickname, useResetPreferences } = useAccount();

const { data: memberData, isLoading: infoLoading, isError: infoError } = useGetMemberInfo();
Expand All @@ -32,40 +30,35 @@ export default function InfoSetting() {
}
}, [apiNickname]);

const { mutate: changeNickname, isPending: nickPending } = useChangeNickname({
onSuccess: (res) => {
if (res?.isSuccess) {
const next = res.result.username;
setNickname(next);
setInitialNickname(next);
localStorage.setItem('nickname', next);

qc.invalidateQueries({ queryKey: QUERY_KEYS.memberInfo });
qc.invalidateQueries({ queryKey: QUERY_KEYS.memberGrade });
qc.setQueryData(['userGrade'], (old: any) => (old ? { ...old, result: { ...old.result, username: next } } : old));
} else {
alert(res?.message ?? '닉네임 변경에 실패했습니다.');
}
},
onError: (err: any) => alert(getApiErrorMessage(err, '닉네임 변경에 실패했습니다.')),
});

const { mutate: resetPref, isPending: resetPending } = useResetPreferences({
onSuccess: (res) => {
if (res?.isSuccess) {
alert('취향 데이터가 초기화되었습니다.');
} else {
alert(res?.message ?? '초기화에 실패했습니다.');
}
},
onError: (err: any) => alert(getApiErrorMessage(err, '초기화에 실패했습니다.')),
});
const { mutate: changeNickname, isPending: nickPending } = useChangeNickname();

const { mutate: resetPref, isPending: resetPending } = useResetPreferences();

const handleSubmitNickname = () => {
const trimmed = nickname.trim();
if (!trimmed) return alert('닉네임을 입력해 주세요.');
if (trimmed === initialNickname || nickPending) return;
changeNickname({ username: trimmed });
changeNickname(
{ username: trimmed },
{
onSuccess: (res) => {
if (res?.isSuccess) {
const next = res.result.username;
setNickname(next);
setInitialNickname(next);
localStorage.setItem('nickname', next);

queryClient.invalidateQueries({ queryKey: memberKeys.all.queryKey });
queryClient.setQueryData(memberKeys.memberGrade.queryKey, (old: any) =>
old?.result ? { ...old, result: { ...old.result, username: next } } : old,
);
} else {
alert(res?.message ?? '닉네임 변경에 실패했습니다.');
}
},
onError: () => alert('닉네임 변경에 실패했습니다.'),
},
);
};

const handleCancelNickname = () => {
Expand All @@ -75,7 +68,16 @@ export default function InfoSetting() {
const handleResetPreferences = () => {
if (resetPending) return;
if (!confirm('정말 초기화할까요? 되돌릴 수 없습니다.')) return;
resetPref();
resetPref(undefined, {
onSuccess: (res: any) => {
if (res?.isSuccess) {
alert('취향 데이터가 초기화되었습니다.');
} else {
alert(res?.message ?? '초기화에 실패했습니다.');
}
},
onError: () => alert('초기화에 실패했습니다.'),
});
};

return (
Expand Down
Loading