Skip to content

Commit

Permalink
Merge pull request #58 from softeerbootcamp4th/TASK-207
Browse files Browse the repository at this point in the history
[Feature][Task-207] 캐스퍼 레이싱 섹션 소켓 연동 및 테스트 완료 (socket custom hook)
  • Loading branch information
nim-od authored Aug 15, 2024
2 parents 9843dc6 + 6721b09 commit 0a35369
Show file tree
Hide file tree
Showing 38 changed files with 637 additions and 360 deletions.
4 changes: 2 additions & 2 deletions packages/admin/src/constants/message.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const enum ErrorMessage {
INVALID_DURATION = '올바르지 않은 기간입니다.',
INVALID_INPUT = '올바른 정보를 입력해 주세요.',
INVALID_INPUT = '올바른 정보를 입력해 주세요.',
NEED_LOGIN = '로그인이 필요한 기능입니다.',
}

Expand All @@ -9,5 +9,5 @@ export const enum ConfirmMessage {
}

export const enum InfoMessage {
WELCOME = '환영합니다.\n관리자님',
WELCOME = '환영합니다.\n관리자님',
}
4 changes: 1 addition & 3 deletions packages/admin/src/layouts/appLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ export default function AppLayout() {

return (
<div className="mx-auto flex h-screen w-screen min-w-[1200px]">
{
accessToken && <SideBarContainer />
}
{accessToken && <SideBarContainer />}
<div className="mx-auto flex h-screen w-full max-w-[1200px] gap-10">
<SystemContainer />
<div className="flex w-full flex-col p-4 pb-8 pt-8">
Expand Down
4 changes: 1 addition & 3 deletions packages/admin/src/pages/events/QuizEventTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ function QuizEventTab() {
return (
<div className="mt-4 flex flex-col gap-2">
<Accordion type="single" collapsible>
{quizEvent?.map((quiz, index) => (
<QuizEventBox quiz={quiz} index={index} />
))}
{quizEvent?.map((quiz, index) => <QuizEventBox quiz={quiz} index={index} />)}
</Accordion>
</div>
);
Expand Down
4 changes: 1 addition & 3 deletions packages/admin/src/pages/review/Review.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
function Review() {
return <div>
리뷰 입니다.
</div>;
return <div>리뷰 입니다.</div>;
}
export default Review;
104 changes: 52 additions & 52 deletions packages/admin/src/services/api/types/apiType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,62 +55,62 @@ export interface LoginProps {
}

export interface Payload {
[API.COMMON_EVENT]: {
[METHOD.GET]: Record<string, never>;
[METHOD.POST]: CommonEvent;
};
[API.QUIZ_LIST]: {
[METHOD.GET]: Record<string, never>;
};
[API.QUIZ]: {
[METHOD.POST]: Quiz;
};
[API.RACING_WINNERS]: {
[METHOD.GET]: Record<string, never>;
[METHOD.POST]: WinnerSetting[];
};
[API.PERSONALITY_TEST_LIST]: {
[METHOD.GET]: Record<string, never>;
};
[API.PERSONALITY_TEST]: {
[METHOD.POST]: PersonalityTest;
};
[API.LOGIN]: {
[METHOD.POST]: LoginProps;
};
[API.COMMON_EVENT]: {
[METHOD.GET]: Record<string, never>;
[METHOD.POST]: CommonEvent;
};
[API.QUIZ_LIST]: {
[METHOD.GET]: Record<string, never>;
};
[API.QUIZ]: {
[METHOD.POST]: Quiz;
};
[API.RACING_WINNERS]: {
[METHOD.GET]: Record<string, never>;
[METHOD.POST]: WinnerSetting[];
};
[API.PERSONALITY_TEST_LIST]: {
[METHOD.GET]: Record<string, never>;
};
[API.PERSONALITY_TEST]: {
[METHOD.POST]: PersonalityTest;
};
[API.LOGIN]: {
[METHOD.POST]: LoginProps;
};
}

export interface Response {
[API.COMMON_EVENT]: {
[METHOD.GET]: CommonEvent;
[METHOD.POST]: Record<string, never>;
};
[API.QUIZ_LIST]: {
[METHOD.GET]: Quiz[];
};
[API.QUIZ]: {
[METHOD.POST]: Quiz;
};
[API.RACING_WINNERS]: {
[METHOD.GET]: RacingWinner[];
[METHOD.POST]: string;
};
[API.PERSONALITY_TEST_LIST]: {
[METHOD.GET]: PersonalityTest[];
};
[API.PERSONALITY_TEST]: {
[METHOD.POST]: PersonalityTest;
};
[API.LOGIN]: {
[METHOD.POST]: {
accessToken: string;
};
};
[API.COMMON_EVENT]: {
[METHOD.GET]: CommonEvent;
[METHOD.POST]: Record<string, never>;
};
[API.QUIZ_LIST]: {
[METHOD.GET]: Quiz[];
};
[API.QUIZ]: {
[METHOD.POST]: Quiz;
};
[API.RACING_WINNERS]: {
[METHOD.GET]: RacingWinner[];
[METHOD.POST]: string;
};
[API.PERSONALITY_TEST_LIST]: {
[METHOD.GET]: PersonalityTest[];
};
[API.PERSONALITY_TEST]: {
[METHOD.POST]: PersonalityTest;
};
[API.LOGIN]: {
[METHOD.POST]: {
accessToken: string;
};
};
}

export type FetchDataRequestOptions<K extends keyof Payload, T extends keyof Payload[K]> = {
path: K;
payload?: Payload[K][T];
method: T; // T가 'GET', 'POST', 'PUT', 'DELETE' 중 하나로 제한됩니다.
headers?: HeadersInit;
path: K;
payload?: Payload[K][T];
method: T; // T가 'GET', 'POST', 'PUT', 'DELETE' 중 하나로 제한됩니다.
headers?: HeadersInit;
};
30 changes: 13 additions & 17 deletions packages/admin/src/utils/fetchData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,22 @@ import { BASE_URL, METHOD } from 'src/constants/api.ts';
import { FetchDataRequestOptions, Payload, Response } from 'src/services/api/types/apiType.ts';

// FetchDataRequestOptions의 제네릭 타입을 수정합니다.
// fetchData 함수의 제네릭 타입을 수정합니다.
// fetchData 함수의 제네릭 타입을 수정합니다.
const fetchData = async <
K extends keyof Payload,
T extends keyof Payload[K] & keyof Response[K] & METHOD,
>(
{
path,
payload,
method,
}: FetchDataRequestOptions<K, T>): Promise<Response[K][T]> => {
const http = new FetchWrapper(BASE_URL);
K extends keyof Payload,
T extends keyof Payload[K] & keyof Response[K] & METHOD,
>({
path,
payload,
method,
}: FetchDataRequestOptions<K, T>): Promise<Response[K][T]> => {
const http = new FetchWrapper(BASE_URL);

if (method === METHOD.GET) {
return http.get<Response[K][T]>(path);
}
if (method === METHOD.GET) {
return http.get<Response[K][T]>(path);
}

return http.post<Response[K][T], Payload[K][T]>(
path,
payload as Payload[K][T],
);
return http.post<Response[K][T], Payload[K][T]>(path, payload as Payload[K][T]);
};

export default fetchData;
5 changes: 4 additions & 1 deletion packages/common/src/constants/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ export const CHAT_SOCKET_ENDPOINTS = {
PUBLISH: '/app/chat.sendMessage',
} as const;

export const RACING_SOCKET_ENDPOINTS = {} as const;
export const RACING_SOCKET_ENDPOINTS = {
SUBSCRIBE: '/topic/game',
PUBLISH: '/app/game.sendGameData',
} as const;
1 change: 1 addition & 0 deletions packages/common/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { default as FetchWrapper } from './api/index.ts';
export { default as Cookie } from './storage/index.ts';

export { default as Socket } from './socket.ts';
export type { SocketSubscribeCallbackType } from './socket.ts';
12 changes: 7 additions & 5 deletions packages/common/src/utils/socket.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Client, IFrame, IMessage, StompSubscription } from '@stomp/stompjs';
import SockJS from 'sockjs-client';

export type SocketSubscribeCallbackType = (data: unknown, messageId: string) => void;
export default class Socket {
private client: Client;

Expand All @@ -25,7 +26,8 @@ export default class Socket {
this.client.onConnect = () => callback?.(true);

this.client.onStompError = (error) => {
console.error('STOMP Error', error);
alert(`실시간 데이터 연동에 실패했습니다. (${error})`);
console.error(error);
callback?.(false);
};

Expand Down Expand Up @@ -56,12 +58,12 @@ export default class Socket {
callback,
}: {
destination: string;
callback: (messageId: string, message: IMessage) => void;
callback: SocketSubscribeCallbackType;
}) {
const subscription = this.client.subscribe(destination, (message: IMessage) => {
const messageId = message.headers['message-id'];

callback(messageId, message);
const data = JSON.parse(message.body);
callback(data, messageId);
});
this.subscriptions.set(destination, subscription);
}
Expand All @@ -71,7 +73,7 @@ export default class Socket {
callback,
}: {
destination: string;
callback: (messageId: string, message: IMessage) => void;
callback: SocketSubscribeCallbackType;
}) {
if (this.client.connected) {
this.createSubscription({ destination, callback });
Expand Down
4 changes: 3 additions & 1 deletion packages/user/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"jsdom": "^24.1.1",
"lucide-react": "^0.417.0"
"lucide-react": "^0.417.0",
"numeral": "^2.0.6"
},
"devDependencies": {
"@types/numeral": "^2",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.39",
Expand Down
2 changes: 1 addition & 1 deletion packages/user/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ToasterStack from 'src/components/common/toast/ToasterStack.tsx';
import AppProviders from 'src/libs/index.tsx';
import router from 'src/routes/router.tsx';
import CasperCursor from './components/cursor/CasperCursor.tsx';
import useCursor from './hooks/useCorusr.ts';
import useCursor from './hooks/useCursor.ts';

export default function App() {
useCursor();
Expand Down
23 changes: 15 additions & 8 deletions packages/user/src/components/cursor/CasperCursor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
function CasperCursor() {
return (
<div
className="custom-cursor-obj"
style={{ zIndex: 999, position: 'absolute', backgroundColor: 'transparent', top: -35, left: -8, pointerEvents: 'none' }}
>
<img src="/cursor/cursor-100.png" alt="커서" style={{ pointerEvents: 'none' }} />
</div>
);
return (
<div
className="custom-cursor-obj"
style={{
zIndex: 999,
position: 'absolute',
backgroundColor: 'transparent',
top: -35,
left: -8,
pointerEvents: 'none',
}}
>
<img src="/cursor/cursor-100.png" alt="커서" style={{ pointerEvents: 'none' }} />
</div>
);
}
export default CasperCursor;
46 changes: 3 additions & 43 deletions packages/user/src/components/event/chatting/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { ChatList, ChatProps } from '@softeer/common/components';
import { CHAT_SOCKET_ENDPOINTS } from '@softeer/common/constants';
import { IMessage } from '@stomp/stompjs';
import { useEffect, useState } from 'react';
import socketClient from 'src/services/socket.ts';
import { ChatList } from '@softeer/common/components';
import { UseChatSocketReturnType } from 'src/hooks/socket/useChatSocket.ts';
import Chat from './Chat.tsx';
import ChatInputArea from './inputArea/index.tsx';

/** 실시간 기대평 섹션 */
export default function RealTimeChatting() {
const { onSendMessage, messages } = useChatSocket();

export default function RealTimeChatting({ onSendMessage, messages }: UseChatSocketReturnType) {
return (
<section className="container flex max-w-[1200px] snap-start flex-col items-center pb-[115px] pt-[50px]">
<h6 className="text-heading-10 mb-[25px] font-medium">기대평을 남겨보세요!</h6>
Expand All @@ -24,39 +20,3 @@ export default function RealTimeChatting() {
</section>
);
}

function useChatSocket() {
const [messages, setMessages] = useState<ChatProps[]>([]);

const handleIncomingMessage = (messageId: string, message: IMessage) => {
const parsedMessage: ChatProps = { id: messageId, ...JSON.parse(message.body) };
setMessages((prevMessages) => [...prevMessages, parsedMessage]);
};

useEffect(() => {
socketClient.connect((isConnected) => {
if (isConnected) {
socketClient.subscribe({
destination: CHAT_SOCKET_ENDPOINTS.SUBSCRIBE,
callback: handleIncomingMessage,
});
}
});
return () => socketClient.disconnect();
}, [socketClient, handleIncomingMessage]);

const handleSendMessage = (text: string) => {
const chatMessage = {
sender: 1,
team: 'pet',
content: text,
};

socketClient.sendMessages({
destination: CHAT_SOCKET_ENDPOINTS.PUBLISH,
body: chatMessage,
});
};

return { onSendMessage: handleSendMessage, messages };
}
Loading

0 comments on commit 0a35369

Please sign in to comment.