Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
061fe21
#281 build(fe): danger제거
KimKyuHoi Feb 25, 2025
1052510
#281 build(fe): react 19 업그레이드
KimKyuHoi Feb 25, 2025
2a0f5eb
#281 feat(fe): form 공용컴포넌트 구현
KimKyuHoi Feb 25, 2025
79d319b
#281 fix(fe): 모달 코드 수정
KimKyuHoi Feb 25, 2025
b002339
#281 feat(fe): 캐싱 추가
KimKyuHoi Feb 25, 2025
0a0ef80
#281 fix(fe): lint수정
KimKyuHoi Feb 25, 2025
e541de9
#281 fix(fe): serverAction변경
KimKyuHoi Feb 25, 2025
ad755d7
#281 feat(fe): stockslug에 따른 workspace id값 가져오기 구현
KimKyuHoi Feb 25, 2025
aa9817a
#281 feat(fe): api route를 활용한 클라이언트에서 fetch 요청구현
KimKyuHoi Feb 25, 2025
16dfd4b
#281 fix(fe): lint 수정
KimKyuHoi Feb 25, 2025
d4f2498
#281 feat(fe): workspace modal창 구현
KimKyuHoi Feb 25, 2025
076270e
#281 feat(fe): 워크스페이스 리스트 가져오기 server Action api 구현
KimKyuHoi Feb 25, 2025
44a6899
#281 feat(fe): 채널 생성 api 구현
KimKyuHoi Feb 25, 2025
754d138
#281 feat(fe): fetch tagkey, react-query query key 분리
KimKyuHoi Feb 25, 2025
0e40b23
#281 feat(fe): 워크스페이스 구독 로직 훅 구현
KimKyuHoi Feb 25, 2025
0315b35
#281 feat(fe): Chat provider를 통한 id값 저장 구현
KimKyuHoi Feb 25, 2025
bced8fd
#281 feat(fe): 사이드바 구현
KimKyuHoi Feb 25, 2025
ce81e3c
#281 feat(fe): 로그아웃시 localStorage id정보값 지워지도록 로직 추가
KimKyuHoi Feb 25, 2025
1a04b44
#281 build(fe): 코드 모듈화
KimKyuHoi Feb 25, 2025
507eaed
#281 feat(fe): 타입 분리
KimKyuHoi Feb 25, 2025
956f151
#281 feat(fe): useReducer 및 웹소켓 받은 데이터 배열 재조정 구현
KimKyuHoi Feb 25, 2025
f42d04f
#281 refactor(fe): 코드 모듈화 및 리팩토링
KimKyuHoi Feb 25, 2025
d3a57a1
#281 feat(fe): 무한스크롤 initialCursorId값 초기에 받지 않도록 수정
KimKyuHoi Feb 27, 2025
6eb3a0f
#281 fix(fe): 쿼리키 설정
KimKyuHoi Feb 27, 2025
b261edd
#281 feat(fe): 스크롤 민감도 수정
KimKyuHoi Feb 27, 2025
2a99af3
#281 feat(fe): 스크롤 container label명 수정
KimKyuHoi Feb 27, 2025
db8907c
Merge branch 'dev' into fe-feat/get-workspace
KimKyuHoi Feb 27, 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
31 changes: 31 additions & 0 deletions src/frontend/apps/web/app/api/auth/token/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { cookies, headers } from 'next/headers';
import { NextResponse } from 'next/server';

const allowedOrigins = [
'https://jootalkpia.netlify.app',
'http://localhost:3000',
];

export async function GET() {
const cookieStore = cookies();
const token = cookieStore.get('accessToken')?.value || '';

const origin = allowedOrigins.includes(headers().get('origin') || '')
? headers().get('origin')
: allowedOrigins[0];

const response = NextResponse.json({ token });

response.headers.set('Access-Control-Allow-Origin', origin || '');
response.headers.set('Access-Control-Allow-Credentials', 'true');
response.headers.set(
'Access-Control-Allow-Methods',
'GET, POST, PATCH, DELETE',
);
response.headers.set(
'Access-Control-Allow-Headers',
'Content-Type, Authorization',
);

return response;
}
12 changes: 8 additions & 4 deletions src/frontend/apps/web/app/stock/[stockSlug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import Link from 'next/link';

import { ChevronLeft } from 'lucide-react';

import { ChatContainer } from '@/src/features/chat';
import { StockDetailLayout } from '@/src/features/stock';
import { ChevronLeft } from 'lucide-react';
import Link from 'next/link';
import { ChatIdProvider } from '@/src/shared';

export async function generateMetadata({ params }) {
const { stockSlug } = params;
Expand All @@ -15,7 +18,6 @@ export async function generateMetadata({ params }) {

export default function StockDetailsPage({ params }) {
const { stockSlug } = params;
// console.log(1, stockSlug);

return (
<div className="flex py-6 px-[30px] h-full min-w-0 min-h-0">
Expand All @@ -30,7 +32,9 @@ export default function StockDetailsPage({ params }) {
</div>

<div className="pl-2 basis-[55%] flex-shrink-0 min-w-0">
<ChatContainer stockSlug={stockSlug} />
<ChatIdProvider stockSlug={stockSlug}>
<ChatContainer stockSlug={stockSlug} />
</ChatIdProvider>
</div>
</div>
);
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"lint": "next lint"
},
"dependencies": {
"@hookform/resolvers": "^4.1.2",
"@stomp/stompjs": "^7.0.0",
"@tanstack/react-query": "^5.64.2",
"@tanstack/react-table": "^8.20.6",
Expand All @@ -18,9 +19,11 @@
"lucide-react": "0.473.0",
"next": "^14.2.23",
"next-themes": "^0.4.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
"sockjs-client": "^1.6.1",
"zod": "^3.24.1",
"zustand": "^5.0.3"
},
"devDependencies": {
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const useLogout = () => {

return () => {
localStorage.removeItem('user');
localStorage.removeItem('chat');
setUser(null);
document.cookie =
'accessToken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { clientFetchInstance } from '@/src/shared/services';
import { clientFetchInstance, TAG_KEYS } from '@/src/shared/services';

import type { HistoryResponse } from '../model';

Expand All @@ -20,6 +20,9 @@ export const getHistoryChat = async (
{
params,
includeAuthToken: true,
cache: 'force-cache',
revalidate: 300,
tags: [`${TAG_KEYS.CHAT_HISTORY(channelId)}`],
},
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ResponseChunkFileData } from '../model';
import { uploadFiles } from './upload-file.api';

import { ResponseChunkFileData } from '../model';

export const uploadChunkWithRetry = async (
chunk: Blob,
channelId: number,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ResponseChunkFileData } from '../model';
import { clientFetchInstance } from '@/src/shared/services/apis';

import { ResponseChunkFileData } from '../model';

/**
* 각 청크 데이터를 개별적으로 업로드하는 함수.
* 이 함수는 하나의 청크(ChunkInfo 객체)를 API로 전송합니다.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { serverFetchInstance } from '@/src/shared/services/apis';

import { type ResponseChunkFileData } from '../model';
import { clientFetchInstance } from '@/src/shared/services/apis';

/**
* 각 청크 데이터를 개별적으로 업로드하는 함수.
Expand All @@ -22,11 +23,13 @@ export async function uploadSmallFiles({
// }

try {
const response = await clientFetchInstance<ResponseChunkFileData, FormData>(
console.log('start upload');
const response = await serverFetchInstance<ResponseChunkFileData, FormData>(
'/api/v1/files/small',
'POST',
{
body: formData,
includeAuthToken: true,
},
);
console.log('[uploadFiles] Response =>', response);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { serverFetchInstance } from '@/src/shared/services/apis';

import type { FileResponse } from '../model';
import { postRequest } from '@/src/shared/services/apis';

type ThumbnailData = {
fileId: number;
Expand All @@ -25,9 +26,13 @@ export async function uploadThumbnail({
// }

try {
const response = await postRequest<FileResponse, FormData>(
const response = await serverFetchInstance<FileResponse, FormData>(
'/api/v1/files/thumbnail',
formData,
'POST',
{
body: formData,
includeAuthToken: true,
},
);
console.log('[uploadFiles] Response =>', response);
return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const MAX_IMAGE_SIZE = 20 * 1024 * 1024;
export const MAX_VIDEO_SIZE = 200 * 1024 * 1024;

export const validateFileSize = (file: File) => {
console.log('File type:', file.type);
// console.log('File type:', file.type);

if (!file.type.startsWith('image/') && !file.type.startsWith('video/')) {
alert('Only image and video files are supported');
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/apps/web/src/features/chat/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export type {
SendMessagePayload,
WebSocketResponsePayload,
} from './websocket.type';
export { useMessages } from './use-messages';
export { useWebSocketClient } from './use-websocket-client';
export { useChatMessages } from './use-chat-messages';
export { useChatSubscribe } from './use-chat-subscribe';
export { useChatAutoScroll } from './use-chat-autoscroll';
export { useSendMessage } from './send-message';
export { useReverseInfiniteHistory } from './use-reverse-infinite-history';
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/apps/web/src/features/chat/model/send-message.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useWebSocketClient } from '@/src/features/chat/model';
import { useChatSubscribe } from '@/src/features/chat/model';

export const useSendMessage = (
channelId: number,
currentUser: { userId: number; nickname: string; profileImage: string },
) => {
const { publishMessage } = useWebSocketClient(channelId);
const { publishMessage } = useChatSubscribe(channelId);

return (content: string, attachmentList: number[]) => {
if (!content.trim() && attachmentList.length === 0) return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useQuery, useQueryClient } from '@tanstack/react-query';
import type { WebSocketResponsePayload } from '@/src/features/chat/model';

export const useMessages = (topic: string) => {
export const useChatMessages = (topic: string) => {
const queryClient = useQueryClient();
const queryKey = ['messages', topic];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
import { useStompWebSocket } from '@/src/shared/providers';
import { QUERY_KEYS } from '@/src/shared/services';

export const useWebSocketClient = (channelId: number) => {
export const useChatSubscribe = (channelId: number) => {
const queryClient = useQueryClient();
const { client } = useStompWebSocket();
const [isConnected, setIsConnected] = useState(false);
Expand All @@ -33,7 +33,7 @@ export const useWebSocketClient = (channelId: number) => {
return;
}

console.log(`📡 Subscribing to /subscribe/chat.${channelId}`);
// console.log(`📡 Subscribing to /subscribe/chat.${channelId}`);
const subscription = client.subscribe(
`/subscribe/chat.${channelId}`,
(message) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@ import { QUERY_KEYS } from '@/src/shared/services';
import { getHistoryChat } from '../api';
import type { HistoryResponse } from '../model';

export function useForwardInfiniteHistory(
channelId: number,
initialCursor?: number,
) {
export function useForwardInfiniteHistory(channelId: number) {
const queryKey = QUERY_KEYS.forwardHistory(channelId);

return useInfiniteQuery<HistoryResponse>({
queryKey: QUERY_KEYS.forwardHistory(channelId),
queryKey,
queryFn: async ({ pageParam }: { pageParam?: unknown }) => {
if (pageParam === undefined) {
return getHistoryChat(channelId, 'forward', undefined, 5);
}
return getHistoryChat(channelId, 'forward', pageParam as number, 5);
},
getNextPageParam: (lastPage: HistoryResponse): number | undefined =>
lastPage.hasNext && lastPage.lastCursorId !== null
? lastPage.lastCursorId
: undefined,
initialPageParam: initialCursor,
initialPageParam: undefined,
staleTime: Infinity,
gcTime: Infinity,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,31 @@ import { QUERY_KEYS } from '@/src/shared/services';
import { getHistoryChat } from '../api';
import type { HistoryResponse } from '../model';

export function useReverseInfiniteHistory(
channelId: number,
initialCursor?: number,
) {
export function useReverseInfiniteHistory(channelId: number) {
const queryKey = QUERY_KEYS.reverseHistory(channelId);

return useInfiniteQuery<HistoryResponse>({
queryKey: QUERY_KEYS.reverseHistory(channelId),
queryFn: async ({ pageParam }: { pageParam: unknown }) => {
return getHistoryChat(channelId, 'backward', pageParam as number, 30);
queryKey,
queryFn: async ({ pageParam }: { pageParam?: unknown }) => {
if (pageParam === undefined) {
const data = getHistoryChat(channelId, 'backward', undefined, 30);
return data;
}
const data = getHistoryChat(
channelId,
'backward',
pageParam as number,
30,
);
return data;
},
getNextPageParam: (lastPage: HistoryResponse): number | undefined =>
lastPage.hasNext && lastPage.lastCursorId !== null
? lastPage.lastCursorId
: undefined,
initialPageParam: initialCursor,
initialPageParam: undefined,

staleTime: Infinity,
gcTime: Infinity,
});
}
9 changes: 5 additions & 4 deletions src/frontend/apps/web/src/features/chat/ui/chat-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import { SidebarContainer } from '@/src/features/workspace';

import ChatHeader from './chat-header';
import ChatSection from './chat-section';
import { useWebSocketClient } from '../model';
import { useChatSubscribe } from '../model';
import { useEffect } from 'react';
import { useChatId } from '@/src/shared';

type ChatContainerProps = {
stockSlug: string;
};

const ChatContainer = ({ stockSlug }: ChatContainerProps) => {
const channelId = 1;
const { subscribe, isConnected } = useWebSocketClient(channelId);
const { channelId } = useChatId();
const { subscribe, isConnected } = useChatSubscribe(channelId);

useEffect(() => {
if (!isConnected) return;
Expand All @@ -30,7 +31,7 @@ const ChatContainer = ({ stockSlug }: ChatContainerProps) => {
<SidebarContainer stockSlug={stockSlug} />
<SidebarInset className="flex flex-col min-w-0 min-h-0 w-full h-full">
<ChatHeader stockSlug={stockSlug} />
<ChatSection />
<ChatSection stockSlug={stockSlug} />
</SidebarInset>
</SidebarProvider>
);
Expand Down
Loading