Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dfaf28c
design: NameChip 컴포넌트 추가
yoouyeon Dec 11, 2025
df795df
design: 모임 선택 버튼 ui 추가
yoouyeon Dec 11, 2025
671006f
refactor: group API 함수 구조 통일
yoouyeon Dec 12, 2025
c8f4f68
refactor: 지출 용어를 bill에서 expense로 변경
yoouyeon Dec 12, 2025
86a35b3
refactor: 지출 생성 페이지 로더와 라우트 수정
yoouyeon Dec 14, 2025
82ba687
refactor: 사용하지 않는 스타일 파일 제거
yoouyeon Dec 14, 2025
1ff45a3
feat: 모임 목록 페칭 함수와 mock API 핸들러 추가
yoouyeon Dec 14, 2025
0976f09
refactor: 모임 선택 페이지에 모임 목록을 API에서 가져오는 로직 추가
yoouyeon Dec 14, 2025
f0cbf29
refactor: createExpensePageTokenLoader 파일 위치 변경
yoouyeon Dec 14, 2025
67536ea
refactor: createExpense route에 groupToken 추가
yoouyeon Dec 14, 2025
3fe4747
refactor: 모임 정보 조회 쿼리 이름과 위치 변경
yoouyeon Dec 14, 2025
656e7a0
feat: 지출 생성 페이지 로더에 모임 토큰 유효성 검사 로직 추가
yoouyeon Dec 14, 2025
d66c5cc
refactor: useAddExpenseFormArray에서 loaderData를 사용하도록 변경
yoouyeon Dec 14, 2025
beff3e0
refactor: groupToken url 전달 방식 변경
yoouyeon Dec 14, 2025
01dd8e5
refactor: 도달할 수 없는 코드 제거
yoouyeon Dec 14, 2025
677a783
feat: CreateGroupLinkButton에 aria-label 추가
yoouyeon Dec 14, 2025
6982735
refactor: EmptyButton을 실제 용도에 맞는 이름으로 변경
yoouyeon Dec 14, 2025
b891c23
fix: 런타임 에러 방지를 위한 옵셔널 체이닝 추가
yoouyeon Dec 14, 2025
50da492
refactor: queryClient 초기화를 정적으로 하도록 수정
yoouyeon Dec 28, 2025
bc7f03a
refactor: 카카오 로그인 환경변수 검증을 함수 호출 시점으로 이동
yoouyeon Dec 28, 2025
28d7fd1
chore: groupToken 에러 응답 형식 수정
yoouyeon Dec 28, 2025
e6493bf
fix: 사용하지 않는 import 제거
yoouyeon Dec 28, 2025
c467c7f
Merge branch 'develop' into feat/9-add-meeting-settlement
yoouyeon Feb 16, 2026
98e61bf
Merge branch 'develop' into feat/9-add-meeting-settlement
yoouyeon Feb 16, 2026
b282ac9
fix: groupList가 null일때도 다른 화면이 랜더링되도록 수정
yoouyeon Feb 17, 2026
6382927
test: storybook 환경에서 실제 서버 대신 msw로 요청을 보내도록 설정
yoouyeon Feb 17, 2026
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
9 changes: 9 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,14 @@ const config: StorybookConfig = {
options: {},
},
refs: {},
async viteFinal(config, { configType }) {
const { mergeConfig } = await import('vite');
return mergeConfig(config, {
// 환경변수 설정 덮어씌우기
define: {
'import.meta.env.VITE_SERVER_URL': JSON.stringify(''),
},
});
},
};
export default config;
11 changes: 1 addition & 10 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useEffect } from 'react';
import { ThemeProvider } from 'styled-components';
import { QueryClientProvider } from '@tanstack/react-query';
import GlobalStyles from '@/shared/styles/globalStyles';
Expand All @@ -7,19 +6,11 @@ import AppRouter from '@/app/Router';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import GlobalErrorBoundary from '@/app/GlobalErrorBoundary';
import Toast from '@/shared/ui/Toast';
import useApiError from '@/shared/hooks/useApiError';
import { queryClient, setupQueryClient } from '@/shared/api/queryClient';
import { queryClient } from '@/shared/api/queryClient';
import Layout from './Layout';
import 'react-toastify/dist/ReactToastify.css';

function App() {
const { handleError: handleQueryError } = useApiError({});
const { handleError: handleMutationError } = useApiError({});

useEffect(() => {
setupQueryClient(handleQueryError, handleMutationError);
}, [handleMutationError, handleQueryError]);

return (
<ThemeProvider theme={theme}>
<GlobalErrorBoundary>
Expand Down
30 changes: 15 additions & 15 deletions src/app/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@ import { ROUTE } from '@/shared/config/route';
import RouteErrorBoundary from '@/app/RouteErrorBoundary';
import RouteErrorElement from '@/app/RouteErrorElement';
import checkAuth from '@/entities/auth/lib/checkAuth';
import getGroupManagerAuth from '@/entities/auth/lib/getGroupManagerAuth';
import groupTokenUrlLoader from '@/entities/auth/lib/groupTokenUrlLoader';
import createExpensePageGuardLoader from '@/pages/CreateExpensePage/lib/createExpensePageGuardLoader';

const BillDetail = lazy(() =>
import('@/pages/billDetail/').then(({ BillDetailPage }) => ({
default: BillDetailPage,
const ExpenseDetail = lazy(() =>
import('@/pages/expenseDetail/').then(({ ExpenseDetailPage }) => ({
default: ExpenseDetailPage,
}))
);
const CharacterShare = lazy(() =>
import('@/pages/characterShare').then(({ CharacterSharePage }) => ({
default: CharacterSharePage,
}))
);
const CreateBill = lazy(() =>
import('@/pages/createBill').then(({ CreateBillPage }) => ({
default: CreateBillPage,
const CreateExpense = lazy(() =>
import('@/pages/CreateExpensePage').then(({ CreateExpensePage }) => ({
default: CreateExpensePage,
}))
);
const GroupSetup = lazy(() =>
Expand Down Expand Up @@ -87,21 +87,21 @@ function AppRouter() {
path: ROUTE.groupSetup,
element: <GroupSetup />,
},
{
path: ROUTE.createExpense,
element: <CreateExpense />,
loader: createExpensePageGuardLoader,
},
],
},
// TODO : 로그인 기능으로 변경될 예정
{
path: ROUTE.createBill,
element: <CreateBill />,
loader: getGroupManagerAuth,
},
{
path: ROUTE.billDetail,
element: <BillDetail />,
path: ROUTE.expenseDetail,
element: <ExpenseDetail />,
loader: groupTokenUrlLoader,
},
{
path: ROUTE.billDetailCharacterShare,
path: ROUTE.characterShare,
element: <CharacterShare />,
loader: groupTokenUrlLoader,
},
Expand Down
30 changes: 0 additions & 30 deletions src/entities/auth/lib/getGroupManagerAuth.ts

This file was deleted.

8 changes: 4 additions & 4 deletions src/entities/auth/lib/kakaoLogin.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const KAKAO_CLIENT_ID = import.meta.env.VITE_KAKAO_CLIENT_ID;
const KAKAO_REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI;

if (!KAKAO_CLIENT_ID || !KAKAO_REDIRECT_URI) {
throw new Error('카카오 OAuth에 필요한 환경 변수가 설정되지 않았습니다.');
}

function kakaoLogin(url?: string) {
if (!KAKAO_CLIENT_ID || !KAKAO_REDIRECT_URI) {
throw new Error('카카오 OAuth에 필요한 환경 변수가 설정되지 않았습니다.');
}

const defaultRedirectUrl = window.location.origin;
const redirectUrl = url || defaultRedirectUrl;

Expand Down
31 changes: 17 additions & 14 deletions src/entities/group/api/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@ import {
GroupHeaderResponse,
} from '@/entities/group/model/group.type';

const group = {
get: (groupToken: string): Promise<Group> =>
axiosInstance
.get(`/group?groupToken=${groupToken}`)
.then((res) => res.data),
export const getGroupList = async (): Promise<Group[]> => {
const response = await axiosInstance.get('/groups', { useMock: true }); // NOTE : API 경로 확인 필요
return response.data.groups;
};

post: async (groupData: CreateGroupData) => {
const response = await axiosInstance.post<{ groupToken: string }>(
'/group',
groupData
);
return response.data;
},
export const getGroupDetail = async (groupToken: string): Promise<Group> => {
const response = await axiosInstance.get('/group', {
params: { groupToken },
});
return response.data;
};

export const createGroup = async (groupData: CreateGroupData) => {
const response = await axiosInstance.post<{ groupToken: string }>(
'/group',
groupData
);
return response.data;
};

export const putGroupAccount = async ({
Expand All @@ -42,5 +47,3 @@ export const getGroupHeader = (
.get(`/group/header?groupToken=${groupToken}`)
.then((res) => res.data);
};

export default group;
30 changes: 30 additions & 0 deletions src/entities/group/api/groupQueries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import useQueryWithHandlers from '@/shared/hooks/useQueryWithHandlers';
import { ErrorHandlers, IgnoreBoundaryErrors } from '@/shared/types/error.type';
import { getGroupDetail, getGroupList } from './group';

export const useGetGroupList = (
errorHandlers: ErrorHandlers,
ignoreBoundaryErrors: IgnoreBoundaryErrors
) => {
const query = useQueryWithHandlers({
queryKey: ['groupList'],
queryFn: getGroupList,
errorHandlers,
ignoreBoundaryErrors,
});
return query;
};

export const useGetGroupDetail = (
groupToken: string,
errorHandlers: ErrorHandlers,
ignoreBoundaryErrors: IgnoreBoundaryErrors
) => {
const query = useQueryWithHandlers({
queryKey: ['groupDetail', groupToken],
queryFn: () => getGroupDetail(groupToken),
errorHandlers,
ignoreBoundaryErrors,
});
return query;
};
1 change: 1 addition & 0 deletions src/entities/group/model/group.type.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Member } from '@/entities/member/model/member.type';

export interface Group {
id: string;
groupName: string;
members: Member[];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ function CharacterBottomSheet({ open, setOpen }: CharacterBottomSheetProps) {
</Button>
<Button
onClick={() =>
navigate(
generatePath(ROUTE.billDetailCharacterShare, { groupToken })
)
navigate(generatePath(ROUTE.characterShare, { groupToken }))
}
>
캐릭터 보기
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { SingleExpenseForm } from '@/entities/expense/model/expense.type';

export type EditBillContext = {
export type EditExpenseContext = {
expenseId: number;
initialExpense: SingleExpenseForm;
};

export type BillStepContext = {
export type ExpenseStepContext = {
isExpenseCreated: boolean;
expenseId?: number;
initialExpense?: SingleExpenseForm;
};

export type EditBillStepContext = {
export type EditExpenseStepContext = {
isExpenseCreated: boolean;
expenseId: number;
initialExpense: SingleExpenseForm;
Expand Down
18 changes: 7 additions & 11 deletions src/features/expense-management/lib/useAddExpenseFormArray.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { useMemo, useState } from 'react';
import { useMemo } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useLoaderData } from 'react-router';
import { format } from 'date-fns';
import { zodResolver } from '@hookform/resolvers/zod';
import { Group } from '@/entities/group/model/group.type';
import {
SingleExpenseForm,
ExpenseFormSchema,
} from '@/entities/expense/model/expense.type';
import group from '@/entities/group/api/group';
import { Group } from '@/entities/group/model/group.type';

const defaultValues: SingleExpenseForm = {
amount: 0,
Expand All @@ -21,14 +20,11 @@ const defaultValues: SingleExpenseForm = {
* 지출 폼을 위한 커스텀 훅
*/
const useAddExpenseFormArray = (initialExpense?: SingleExpenseForm) => {
const { groupToken } = useLoaderData();
const [groupInfo, setGroupInfo] = useState<Group | null>(null);
const { groupData } = useLoaderData() as { groupData: Group };
const formMethods = useForm({
resolver: zodResolver(ExpenseFormSchema),
mode: 'onChange', // 폼들의 필수 입력값이 모두 입력되었을 때 '다음' 버튼을 활성화시키기 위함
defaultValues: async () => {
const groupData = await group.get(groupToken);
setGroupInfo(groupData);
// 기본 데이터가 있는 경우 (ex. 수정)
if (initialExpense) {
return {
Expand All @@ -54,28 +50,28 @@ const useAddExpenseFormArray = (initialExpense?: SingleExpenseForm) => {
});

const defaultFormValue = useMemo(() => {
if (!groupInfo) {
if (!groupData) {
return defaultValues;
}
return {
...defaultValues,
memberExpenses: groupInfo.members.map((member) => ({
memberExpenses: groupData.members.map((member) => ({
id: member.id,
name: member.name,
amount: 0,
profile: member.profile,
role: member.role,
})),
};
}, [groupInfo]);
}, [groupData]);

const fieldArrayReturns = useFieldArray({
control: formMethods.control,
name: 'expenses',
});

return {
groupInfo,
groupInfo: groupData,
formMethods,
defaultFormValue,
fieldArrayReturns,
Expand Down
4 changes: 2 additions & 2 deletions src/features/expense-management/ui/FormCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@/entities/expense/model/expense.type';
import Alert from '@/shared/ui/Alert';
import Button from '@/shared/ui/Button';
import BillDatePicker from '@/shared/ui/DatePicker';
import ExpenseDatePicker from '@/shared/ui/DatePicker';
import Text from '@/shared/ui/Text';
import FormField from '@/features/expense-management/ui/FormField';
import distributeAmount from '@/features/expense-management/lib/distributeExpense';
Expand Down Expand Up @@ -111,7 +111,7 @@ const FormCard = forwardRef<HTMLDivElement, FormCardProps>(
control={control}
name={`expenses.${index}.date`}
renderInput={({ field }) => (
<BillDatePicker
<ExpenseDatePicker
selected={new Date(field.value)}
onChange={(date) =>
field.onChange(format(date || new Date(), 'yyyy-MM-dd'))
Expand Down
18 changes: 0 additions & 18 deletions src/features/group-creation/api/useGetGroupBasicInfo.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/features/group-creation/api/usePostCreateGroup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CreateGroupData } from '@/entities/group/model/group.type';
import group from '@/entities/group/api/group';
import { createGroup } from '@/entities/group/api/group';
import { useMutation } from '@tanstack/react-query';

interface CreateGroupVariables {
Expand All @@ -8,7 +8,7 @@ interface CreateGroupVariables {

export const usePostCreateGroup = () => {
return useMutation<CreateGroupVariables, Error, CreateGroupData>({
mutationFn: (newGroup) => group.post(newGroup),
mutationFn: (newGroup) => createGroup(newGroup),
onSuccess: (response) => {
localStorage.setItem('groupToken', response?.groupToken);
},
Expand Down
Loading