Skip to content
Merged
4 changes: 2 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default tseslint.config(
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
'react/react-in-jsx-scope': 'off',
'no-console': 'warn',
'no-console': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
curly: 'error',
Expand All @@ -52,7 +52,7 @@ export default tseslint.config(
},
],
'react/self-closing-comp': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'react/jsx-pascal-case': 'error',
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'prettier/prettier': [
Expand Down
5 changes: 3 additions & 2 deletions src/api/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios';

import { HTTP_STATUS } from '@/api/constant/httpStatus';

const axiosInstance = axios.create({
Expand Down Expand Up @@ -28,11 +29,11 @@ axiosInstance.interceptors.response.use(
const { status } = error.response;

if (status === HTTP_STATUS.UNAUTHORIZED) {
console.warn('인증 실패');
// 인증 실패 처리
}

if (status === HTTP_STATUS.INTERNAL_SERVER_ERROR) {
console.error('서버 오류가 발생');
// 서버 오류 처리
}
}
return Promise.reject(error);
Expand Down
4 changes: 2 additions & 2 deletions src/api/constant/queryKey.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const QUERY_KEY = {
OVERALL_TODO: ['overallTodo'],
ENTIRE_TODO: ['entireTodo'],
MANDALART_CORE_GOALS: (mandalartId: number) => ['mandalartCoreGoals', mandalartId],
MANDALART_SUB_GOALS: (mandalartId: number, coreGoalId?: number, cycle?: string) =>
['mandalartSubGoals', mandalartId, coreGoalId, cycle].filter(Boolean),
Expand All @@ -22,6 +22,6 @@ export const QUERY_KEY = {
PERSONA: 'persona',
UPDATE_SUB_GOAL: (id: number) => ['updateSubGoal', id],
DELETE_SUB_GOAL: (id: number) => ['deleteSubGoal', id],
OVERALL_GOAL: ['overallGoal'],
ENTIRE_GOAL: ['entireGoal'],
USER_INFO: ['userInfo'],
} as const;
2 changes: 1 addition & 1 deletion src/api/domain/edit/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const useUpdateSubGoal = (mandalartId: number) => {

return useMutation({
mutationFn: (data: UpdateSubGoalRequest) => updateSubGoal(mandalartId, data),
onSuccess: (_) => {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [...QUERY_KEY.MANDAL_ALL, mandalartId],
exact: true,
Expand Down
18 changes: 18 additions & 0 deletions src/api/domain/entireTodo/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useMutation, type UseMutationOptions } from '@tanstack/react-query';

import {
postEntireTodo,
type CreateEntireTodoRequest,
type CreateEntireTodoResponse,
} from '@/api/domain/entireTodo';
import { QUERY_KEY } from '@/api/constant/queryKey';

export const useCreateEntireTodo = (
options?: UseMutationOptions<CreateEntireTodoResponse, unknown, CreateEntireTodoRequest>,
) => {
return useMutation<CreateEntireTodoResponse, unknown, CreateEntireTodoRequest>({
mutationKey: [...QUERY_KEY.ENTIRE_TODO, 'create'],
mutationFn: postEntireTodo,
...options,
});
};
23 changes: 0 additions & 23 deletions src/api/domain/entireTodo/hook/useCreateMandalart.tsx

This file was deleted.

29 changes: 16 additions & 13 deletions src/api/domain/entireTodo/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import axiosInstance from '@/api/axiosInstance';
import { END_POINT } from '@/api/constant/endPoint';
import type { BaseResponse } from '@/type/api';
import type {
CreateOverallTodoRequest,
CreateOverallTodoResponse,
} from '@/api/domain/entireTodo/type/entireTodo';

export const postOverallTodo = async (
body: CreateOverallTodoRequest,
): Promise<BaseResponse<CreateOverallTodoResponse>> => {
const res = await axiosInstance.post<BaseResponse<CreateOverallTodoResponse>>(
END_POINT.MANDALART,
body,
);
return res.data;
export interface CreateEntireTodoRequest {
title: string;
}

export interface CreateEntireTodoResponse {
id: number;
title: string;
}

export const postEntireTodo = async (
body: CreateEntireTodoRequest,
): Promise<CreateEntireTodoResponse> => {
const res = await axiosInstance.post<{
data: CreateEntireTodoResponse;
}>(END_POINT.MANDALART, body);
return res.data.data;
};
8 changes: 0 additions & 8 deletions src/api/domain/entireTodo/type/entireTodo.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/api/domain/lowerTodo/hook/useOverallGoal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { QUERY_KEY } from '@/api/constant/queryKey';

export const useOverallGoal = (mandalartId: number) => {
return useQuery({
queryKey: [QUERY_KEY.OVERALL_GOAL, mandalartId],
queryKey: [QUERY_KEY.ENTIRE_GOAL, mandalartId],
queryFn: () => getOverallGoal(mandalartId),
enabled: !!mandalartId,
});
Expand Down
2 changes: 1 addition & 1 deletion src/api/domain/myTodo/hook/useMyMandal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type MandalCoreGoalsResponse = BaseResponse<{

export const useGetMandalAll = (mandalartId: number) => {
return useQuery({
queryKey: [QUERY_KEY.OVERALL_TODO, mandalartId],
queryKey: [QUERY_KEY.ENTIRE_GOAL, mandalartId],
queryFn: () => getMandalAll(mandalartId),
});
};
Expand Down
2 changes: 1 addition & 1 deletion src/api/domain/upperTodo/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { QUERY_KEY } from '@/api/constant/queryKey';

export const useGetMandalAll = (mandalartId: number) => {
return useQuery({
queryKey: [QUERY_KEY.OVERALL_TODO, mandalartId],
queryKey: [QUERY_KEY.ENTIRE_GOAL, mandalartId],
queryFn: () => getMandalAll(mandalartId),
});
};
Expand Down
2 changes: 1 addition & 1 deletion src/common/component/CycleDropDown/CycleDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type CycleDropDownProps = {
const CycleDropDown = ({ initialType = 'DAILY', onChange }: CycleDropDownProps) => {
const [isOpen, setIsOpen] = useState(false);
const [selectedType, setSelectedType] = useState<DisplayCycleType>(
(Object.entries(CYCLE_MAPPING).find(([_, v]) => v === initialType)?.[0] as DisplayCycleType) ||
(Object.entries(CYCLE_MAPPING).find(([, v]) => v === initialType)?.[0] as DisplayCycleType) ||
'매일',
);

Expand Down
19 changes: 16 additions & 3 deletions src/common/component/GoButton/GoButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@ import { goButtonContainer, goIcon } from '@/common/component/GoButton/GoButton.

type GoButtonProps = {
isActive: boolean;
disabled?: boolean;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
type?: 'button' | 'submit' | 'reset';
};

const GoButton = ({ isActive = true, onClick }: GoButtonProps) => {
const state = isActive ? 'active' : 'disabled';
const GoButton = ({
isActive = true,
disabled = false,
onClick,
type = 'button',
}: GoButtonProps) => {
const state = isActive && !disabled ? 'active' : 'disabled';
const isDisabled = disabled || !isActive;

return (
<button className={goButtonContainer({ state })} onClick={onClick}>
<button
className={goButtonContainer({ state })}
onClick={onClick}
disabled={isDisabled}
type={type}
>
<IcBigNext className={goIcon({ state })} />
</button>
);
Expand Down
5 changes: 4 additions & 1 deletion src/common/component/Loading/Loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import loadingAnimation from '@/assets/lottie/loading.json';
import * as styles from '@/common/component/Loading/Loading.css';

type LoadingProps = {
type: 'goal' | 'todo' | 'history';
type: 'goal' | 'todo' | 'history' | 'entireTodo';
};

const Loading = ({ type }: LoadingProps) => {
Expand All @@ -20,6 +20,9 @@ const Loading = ({ type }: LoadingProps) => {
case 'history':
message = '내가 한 일을 불러오고 있어요';
break;
case 'entireTodo':
message = '목표를 작성중입니다';
break;
}

return (
Expand Down
17 changes: 10 additions & 7 deletions src/common/component/UserModal/UserModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef } from 'react';
import { useCallback, useEffect, useRef } from 'react';

import { IcDivider } from '@/assets/svg';
import * as styles from '@/common/component/UserModal/UserModal.css';
Expand All @@ -14,11 +14,14 @@ const UserModal = ({ onClose }: UserModalProps) => {
const { data: user, isLoading, isError } = useGetUser();
const { mutate: logoutMutate } = usePostLogout();

const handleClickOutside = (e: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
onClose();
}
};
const handleClickOutside = useCallback(
(e: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
onClose();
}
},
[onClose],
);

const handleLogout = () => {
logoutMutate(undefined, {
Expand All @@ -39,7 +42,7 @@ const UserModal = ({ onClose }: UserModalProps) => {
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
}, [handleClickOutside]);

if (isLoading || isError || !user) {
return null;
Expand Down
21 changes: 10 additions & 11 deletions src/common/hook/useTypingEffect.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5) requestAnimationFrame 정확한 취소 관리와 duration <= 0 방어 로직 부분 언마운트/예외 케이스 안정성이 올라갔다는 점에서 좋다고 생각합니다 !

Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';

const useTypingEffect = (fullText: string, duration: number) => {
const [displayedText, setDisplayedText] = useState('');
const rafIdRef = useRef<number | null>(null);

useEffect(() => {
let startTime: number | null = null;
const charArray = Array.from(fullText);
const totalChars = charArray.length;
let isMounted = true;

const step = (timestamp: number) => {
if (!isMounted) {
return;
}
if (startTime === null) {
startTime = timestamp;
}

const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
const charsToShow = Math.floor(progress * totalChars);
const progress = duration <= 0 ? 1 : Math.min(elapsed / duration, 1);
const charsToShow = Math.round(progress * totalChars);

setDisplayedText(charArray.slice(0, charsToShow).join(''));

if (progress < 1) {
requestAnimationFrame(step);
rafIdRef.current = requestAnimationFrame(step);
}
};

const rafId = requestAnimationFrame(step);
rafIdRef.current = requestAnimationFrame(step);

return () => {
isMounted = false;
cancelAnimationFrame(rafId);
if (rafIdRef.current !== null) {
cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = null;
}
};
}, [fullText, duration]);

Expand Down
7 changes: 4 additions & 3 deletions src/page/home/hook/useFadeInOnView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ export const useFadeInOnView = <T extends HTMLElement>({
}

return () => {
if (ref.current) {
observer.unobserve(ref.current);
if (current) {
observer.unobserve(current);
}
observer.disconnect();
};
}, []);
}, [rootMargin, threshold]);

return { ref, visible };
};
1 change: 1 addition & 0 deletions src/page/intro/Intro.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useLocation, useNavigate } from 'react-router-dom';

import * as styles from '@/page/intro/Intro.css';

const MESSAGE = {
Expand Down
4 changes: 1 addition & 3 deletions src/page/signup/SignUp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ const SignUp = () => {
onSuccess: () => {
navigate(PATH.INTRO);
},
onError: (err) => {
console.error('회원가입 실패:', err);
},
onError: () => {},
});
};

Expand Down
17 changes: 5 additions & 12 deletions src/page/todo/entireTodo/Todo.css.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
import { style } from '@vanilla-extract/css';

import { colors } from '@/style/token';
import { fonts } from '@/style/token/typography.css';
import { colors, fonts, layout } from '@/style/token';

export const todoContainer = style({
...layout.columnCenter,
height: '100vh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.bg_black01,
position: 'relative',
overflow: 'hidden',
});

export const todoTitle = style({
color: colors.white01,
...fonts.display01,
color: colors.white01,
textAlign: 'center',
whiteSpace: 'pre-line',
marginBottom: '5.6rem',
position: 'relative',
zIndex: 1,
height: '15.2rem',
});

export const todoInputContainer = style({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
...layout.rowCenter,
gap: '2rem',
position: 'relative',
});
Loading