Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
930d150
205 feat 이미지 전처리 기능 구현 (#206)
ff1451 Mar 21, 2026
b7f6e68
[hotfix] 하단바 너비 수정 (#208)
ff1451 Mar 23, 2026
f060de0
Reapply "[feat] 광고 배너 추가 (#200)"
ff1451 Mar 23, 2026
b84ad2f
[feat] 하단바 리디자인 (#213)
ff1451 Mar 24, 2026
b2f8251
[refactor] 광고 카드 레이아웃 밀림 수정 (#215)
ff1451 Mar 24, 2026
4540932
feat: 인앱 알림 페이지 및 토스트 구현 (#217)
ff1451 Mar 26, 2026
8fd8a64
[refactor] 도메인별 TanStack Query 훅 정리 (#219)
ff1451 Mar 26, 2026
43c267c
[refactor] mutaton query 및 hook 추가 수정 (#221)
ff1451 Mar 26, 2026
1c0393b
[fix] 모바일 환경 입력창과 키보드 간의 간격이 큰 문제 수정 (#223)
ff1451 Mar 27, 2026
448902c
[fix] 키보드 활성화 시 화면 흔들림 문제 수정 (#225)
ff1451 Mar 27, 2026
e56c032
[fix] 키보드 활성화 시 채팅 화면 전체가 흔들리는 문제 수정 (#227)
ff1451 Mar 27, 2026
e471a38
fix: 입력 포커스 중 viewport offset 고정 (#229)
ff1451 Mar 27, 2026
0c0a0bb
fix: 문서 루트 스크롤 잠금으로 빈 공간 잔류 방지 (#232)
ff1451 Mar 27, 2026
859c73d
[fix] 키보드 활성화 시 채팅 화면 상단 고정이 깨지고 빈 공간이 남는 문제 수정 (#234)
ff1451 Mar 27, 2026
b75ced4
[fix] 키보드 활성화 시 채팅 화면에서 문서 스크롤이 발생하는 문제 수정 (#236)
ff1451 Mar 27, 2026
c47b4b9
[fix] 키보드 활성화 시 채팅방이 마지막 메시지 위치를 유지하지 못하는 문제 수정 (#238)
ff1451 Mar 27, 2026
5738779
fix: mypage 연계 약관 페이지 뒤로가기 수정 (#240)
ff1451 Mar 27, 2026
488168b
Merge branch 'main' into develop
ff1451 Mar 27, 2026
b78af36
refactor: alias import 경로 정리
ff1451 Mar 27, 2026
9973713
fix: query 설정과 suspense 분기 정리
ff1451 Mar 27, 2026
7d90053
refactor: 관리자 화면 스타일 유틸 정리
ff1451 Mar 27, 2026
a0300fa
fix: 이미지 전처리 예외 처리 보강
ff1451 Mar 27, 2026
1f1a54f
fix: 헤더와 회비 화면 동작 정리
ff1451 Mar 27, 2026
7da1524
fix: 공통 유틸 안정성 개선
ff1451 Mar 27, 2026
621eab6
fix: 이미지 전처리 실패 처리를 보정
ff1451 Mar 28, 2026
9f2e88e
fix: 모집 공고 저장 후 설정 반영 순서 조정
ff1451 Mar 28, 2026
78ed6a7
fix: 부원 직책 변경 실패 처리를 보강
ff1451 Mar 28, 2026
1ca6d30
fix: 약관 링크 접근성을 개선
ff1451 Mar 28, 2026
ce31ca9
fix: 공통 쿼리와 유틸 안정성을 보완
ff1451 Mar 28, 2026
e5f31d5
[feat] 동적 버전 정보 표시 구현 (#211)
ff1451 Mar 28, 2026
78e1960
[feat] 메인화면 동아리 카드 디자인 수정 반영 (#242)
ff1451 Mar 30, 2026
f9dfaed
apiClient 코드 중복 제거 및 네이티브 브릿지 인증 동기화 중앙화 (#244)
ff1451 Mar 30, 2026
13aa6ce
[refactor] 에러 처리 유틸 및 utils 구조 정리 (#246)
ff1451 Mar 30, 2026
2f016ab
Update src/pages/Home/components/HomeClubSection.tsx
ff1451 Mar 30, 2026
0d53916
fix: 인증 세션 복구 흐름 정리
ff1451 Mar 30, 2026
a5667d7
fix: 홈 동아리 카드 레이아웃 정리
ff1451 Mar 30, 2026
027f479
[feat] 총동아리 페이지 리디자인 및 하단 오버레이 정리 (#249)
ff1451 Mar 31, 2026
63ca216
[feat] 마이페이지 관리자 카드 분리 및 채팅 미확인 배지 반영 (#251)
ff1451 Mar 31, 2026
19922eb
feat: 채팅 페이지 리디자인 (#252)
ParkSungju01 Apr 1, 2026
7253ed9
chore: conflict 해결 중 누락된 부분 수정
ff1451 Apr 1, 2026
822ad69
[refactor] 광고 렌더링 조건 수정 (#254)
ff1451 Apr 1, 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
Binary file removed public/apple-touch-icon-180x180.png
Binary file not shown.
Binary file removed public/maskable-icon-512x512.png
Binary file not shown.
Binary file removed public/pwa-192x192.png
Binary file not shown.
Binary file removed public/pwa-512x512.png
Binary file not shown.
Binary file removed public/pwa-64x64.png
Binary file not shown.
16 changes: 8 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const ManagedMemberList = lazy(() => import('./pages/Manager/ManagedMemberList')
const ManagedRecruitment = lazy(() => import('./pages/Manager/ManagedRecruitment'));
const ManagedRecruitmentForm = lazy(() => import('./pages/Manager/ManagedRecruitmentForm'));
const ManagedRecruitmentWrite = lazy(() => import('./pages/Manager/ManagedRecruitmentWrite'));
const NotificationsPage = lazy(() => import('./pages/Notifications'));
const Schedule = lazy(() => import('./pages/Schedule'));
const Timer = lazy(() => import('./pages/Timer'));
const MyPage = lazy(() => import('./pages/User/MyPage'));
Expand Down Expand Up @@ -84,6 +85,11 @@ function App() {
<Route element={<ProtectedRoute />}>
<Route element={<Layout showBottomNav />}>
<Route path="home" element={<Home />} />
<Route path="notifications" element={<NotificationsPage />} />
<Route path="council">
<Route index element={<CouncilDetail />} />
<Route path="notice/:noticeId" element={<CouncilNotice />} />
</Route>
<Route path="mypage">
<Route index element={<MyPage />} />
<Route path="manager">
Expand All @@ -97,6 +103,7 @@ function App() {
</Route>
</Route>
<Route path="timer" element={<Timer />} />
<Route path="chats" element={<ChatListPage />} />
<Route path="clubs">
<Route index element={<ClubList />} />
<Route path="search" element={<ClubSearch />} />
Expand All @@ -113,14 +120,7 @@ function App() {
<Route path="mypage/manager/:clubId/recruitment/write" element={<ManagedRecruitmentWrite />} />
<Route path="mypage/manager/:clubId/recruitment/account" element={<ManagedAccount />} />
<Route path="profile" element={<Profile />} />
<Route path="council">
<Route index element={<CouncilDetail />} />
<Route path="notice/:noticeId" element={<CouncilNotice />} />
</Route>
<Route path="chats">
<Route index element={<ChatListPage />} />
<Route path=":chatRoomId" element={<ChatRoom />} />
</Route>
<Route path="chats/:chatRoomId" element={<ChatRoom />} />
</Route>
</Route>

Expand Down
15 changes: 15 additions & 0 deletions src/apis/advertisement/entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export interface Advertisement {
id: number;
title: string;
description: string;
imageUrl: string;
linkUrl: string;
}

export interface AdvertisementsRequestParams {
count?: number;
}

export interface AdvertisementsResponse {
advertisements: Advertisement[];
}
19 changes: 19 additions & 0 deletions src/apis/advertisement/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { apiClient } from '../client';
import type { AdvertisementsRequestParams, AdvertisementsResponse } from './entity';

export const getAdvertisements = async (params: AdvertisementsRequestParams = {}) => {
const response = await apiClient.get<AdvertisementsResponse, AdvertisementsRequestParams>('advertisements', {
params,
requiresAuth: true,
});

return response;
};

export const postAdvertisementClick = async (advertisementId: number) => {
const response = await apiClient.post<void>(`advertisements/${advertisementId}/clicks`, {
requiresAuth: true,
});

return response;
};
15 changes: 15 additions & 0 deletions src/apis/advertisement/mutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { mutationOptions } from '@tanstack/react-query';
import { postAdvertisementClick } from '.';

export const advertisementMutationKeys = {
click: () => ['advertisements', 'click'] as const,
};

export const advertisementMutations = {
click: () =>
mutationOptions({
mutationKey: advertisementMutationKeys.click(),
mutationFn: postAdvertisementClick,
retry: false,
}),
};
17 changes: 17 additions & 0 deletions src/apis/advertisement/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { queryOptions } from '@tanstack/react-query';
import { getAdvertisements } from '@/apis/advertisement';

export const advertisementQueryKeys = {
all: ['advertisements'] as const,
randomBatch: (scope: string, batchIndex: number) =>
[...advertisementQueryKeys.all, scope, 'random-batch', batchIndex] as const,
};

export const advertisementQueries = {
randomBatch: (scope: string, batchIndex: number) =>
queryOptions({
queryKey: advertisementQueryKeys.randomBatch(scope, batchIndex),
queryFn: () => getAdvertisements({ count: 2 }),
staleTime: Infinity,
}),
};
18 changes: 15 additions & 3 deletions src/apis/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ApiError } from '@/interface/error';
import { isServerErrorStatus, redirectToServerErrorPage } from '@/utils/ts/errorRedirect';
import type { ApiError } from '@/utils/ts/error/apiError';
import { isServerErrorStatus, redirectToServerErrorPage } from '@/utils/ts/error/errorRedirect';
import { NORMALIZED_API_BASE_URL } from '@/utils/ts/oauth';
import { apiClient } from '../client';
import type {
Expand All @@ -10,7 +10,9 @@ import type {
SignupRequest,
} from './entity';

export const refreshAccessToken = async (): Promise<string> => {
let refreshAccessTokenPromise: Promise<string> | null = null;

const requestAccessTokenRefresh = async (): Promise<string> => {
const url = `${NORMALIZED_API_BASE_URL}/users/refresh`;

let response: Response;
Expand Down Expand Up @@ -49,6 +51,16 @@ export const refreshAccessToken = async (): Promise<string> => {
return data.accessToken;
};

export const refreshAccessToken = async (): Promise<string> => {
if (!refreshAccessTokenPromise) {
refreshAccessTokenPromise = requestAccessTokenRefresh().finally(() => {
refreshAccessTokenPromise = null;
});
}

return refreshAccessTokenPromise;
};

export const signup = async (data: SignupRequest) => {
const response = await apiClient.post('users/signup', {
body: data,
Expand Down
32 changes: 32 additions & 0 deletions src/apis/auth/mutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { mutationOptions } from '@tanstack/react-query';
import { deleteMyAccount, logout, putMyInfo, signup } from '.';

export const authMutationKeys = {
signup: () => ['signup'] as const,
logout: () => ['logout'] as const,
withdraw: () => ['withdraw'] as const,
updateMyInfo: () => ['modifyMyInfo'] as const,
};

export const authMutations = {
signup: () =>
mutationOptions({
mutationKey: authMutationKeys.signup(),
mutationFn: signup,
}),
logout: () =>
mutationOptions({
mutationKey: authMutationKeys.logout(),
mutationFn: logout,
}),
withdraw: () =>
mutationOptions({
mutationKey: authMutationKeys.withdraw(),
mutationFn: deleteMyAccount,
}),
updateMyInfo: () =>
mutationOptions({
mutationKey: authMutationKeys.updateMyInfo(),
mutationFn: putMyInfo,
}),
};
27 changes: 27 additions & 0 deletions src/apis/auth/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { queryOptions } from '@tanstack/react-query';
import { getMyInfo, getMyOAuthLinks, getSignupPrefill } from '.';

export const authQueryKeys = {
all: ['user'] as const,
myInfo: () => [...authQueryKeys.all, 'myInfo'] as const,
oauthLinks: () => [...authQueryKeys.all, 'oauthLinks'] as const,
signupPrefill: () => [...authQueryKeys.all, 'prefill'] as const,
};

export const authQueries = {
myInfo: () =>
queryOptions({
queryKey: authQueryKeys.myInfo(),
queryFn: getMyInfo,
}),
oauthLinks: () =>
queryOptions({
queryKey: authQueryKeys.oauthLinks(),
queryFn: getMyOAuthLinks,
}),
signupPrefill: () =>
queryOptions({
queryKey: authQueryKeys.signupPrefill(),
queryFn: getSignupPrefill,
}),
};
38 changes: 38 additions & 0 deletions src/apis/chat/mutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { mutationOptions } from '@tanstack/react-query';
import { postAdminChatRoom, postChatMessage, postChatMute, postChatRooms } from '@/apis/chat';

export const chatMutationKeys = {
createRoom: () => ['chat', 'createRoom'] as const,
createAdminRoom: () => ['chat', 'createAdminRoom'] as const,
sendMessage: () => ['chat', 'sendMessage'] as const,
toggleMute: (chatRoomId?: number) => ['chat', 'toggleMute', chatRoomId ?? 'unknown'] as const,
};

export const chatMutations = {
createRoom: () =>
mutationOptions({
mutationKey: chatMutationKeys.createRoom(),
mutationFn: postChatRooms,
}),
createAdminRoom: () =>
mutationOptions({
mutationKey: chatMutationKeys.createAdminRoom(),
mutationFn: postAdminChatRoom,
}),
sendMessage: () =>
mutationOptions({
mutationKey: chatMutationKeys.sendMessage(),
mutationFn: postChatMessage,
}),
toggleMute: (chatRoomId?: number) =>
mutationOptions({
mutationKey: chatMutationKeys.toggleMute(chatRoomId),
mutationFn: async () => {
if (!chatRoomId) {
throw new Error('chatRoomId is missing');
}

return postChatMute(chatRoomId);
},
}),
};
32 changes: 32 additions & 0 deletions src/apis/chat/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query';
import type { ChatMessagesResponse } from './entity';
import { getChatMessages, getChatRooms } from '.';

export const chatQueryKeys = {
all: ['chat'] as const,
rooms: () => [...chatQueryKeys.all, 'rooms'] as const,
messages: (chatRoomId: number) => [...chatQueryKeys.all, 'messages', chatRoomId] as const,
disabledMessages: () => [...chatQueryKeys.all, 'messages', 'disabled'] as const,
};

export const chatQueries = {
rooms: () =>
queryOptions({
queryKey: chatQueryKeys.rooms(),
queryFn: getChatRooms,
}),
messages: (chatRoomId?: number, limit = 20) =>
infiniteQueryOptions({
queryKey: chatRoomId ? chatQueryKeys.messages(chatRoomId) : chatQueryKeys.disabledMessages(),
queryFn: ({ pageParam }) =>
getChatMessages({
chatRoomId: chatRoomId!,
page: pageParam,
limit,
}),
initialPageParam: 1,
getNextPageParam: (lastPage: ChatMessagesResponse) =>
lastPage.currentPage < lastPage.totalPage ? lastPage.currentPage + 1 : undefined,
enabled: Boolean(chatRoomId),
}),
};
Loading
Loading