Skip to content

Commit

Permalink
Merge pull request #60 from softeerbootcamp4th/TASK-195
Browse files Browse the repository at this point in the history
[Feature][Task-195] 서버 api, socket 연동 테스트 완료
  • Loading branch information
nim-od authored Aug 15, 2024
2 parents ec3c281 + 4abffb8 commit ee6e4fe
Show file tree
Hide file tree
Showing 46 changed files with 244 additions and 273 deletions.
4 changes: 2 additions & 2 deletions packages/admin/src/hooks/useAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const useAuth = () => {
onSuccess: (data) => {
openAlert(InfoMessage.WELCOME, 'alert');
navigate(RoutePaths.EVENT_PAGE);
Cookie.setCookie(ACCESS_TOKEN_KEY, data.accessToken, 7);
Cookie.setCookie<string>(ACCESS_TOKEN_KEY, data.accessToken, 7);
},
onError: () => {
openAlert(ErrorMessage.INVALID_INPUT, 'alert');
Expand All @@ -38,7 +38,7 @@ const useAuth = () => {
};

const logout = () => {
Cookie.setCookie(ACCESS_TOKEN_KEY, '', 0);
Cookie.clearCookie(ACCESS_TOKEN_KEY);
navigate(RoutePaths.ROOT);
};

Expand Down
2 changes: 1 addition & 1 deletion packages/admin/src/layouts/appLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function AppLayout() {
const { headerTitle } = useHeader();
const location = useLocation();
const navigate = useNavigate();
const accessToken = Cookie.getCookie(ACCESS_TOKEN_KEY);
const accessToken = Cookie.getCookie<string>(ACCESS_TOKEN_KEY);
useEffect(() => {
if (location.pathname !== RoutePaths.ROOT) {
if (!accessToken) {
Expand Down
14 changes: 7 additions & 7 deletions packages/common/src/components/chat/Message.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { PropsWithChildren } from 'react';
import { MessageChatProps } from 'src/components/chat/index.ts';
import { Category } from 'src/types/category.ts';
import { SocketCategory } from 'src/types/category.ts';
import { cn } from 'src/utils/index.ts';

interface MessageProps extends Pick<MessageChatProps, 'sender' | 'team'> {
isMyMessage?: boolean;
}

const TYPES: Record<Category, { casper: string; textColor: string }> = {
pet: { casper: '/casper/yellow.svg', textColor: 'text-cream-600' },
leisure: { casper: '/casper/khaki.svg', textColor: 'text-khaki-400' },
travel: { casper: '/casper/orange.svg', textColor: 'text-orange-500' },
place: { casper: '/casper/white.svg', textColor: 'text-gray-300' },
const TYPES: Record<SocketCategory, { casper: string; textColor: string }> = {
p: { casper: '/casper/yellow.svg', textColor: 'text-cream-600' },
l: { casper: '/casper/khaki.svg', textColor: 'text-khaki-400' },
t: { casper: '/casper/orange.svg', textColor: 'text-orange-500' },
s: { casper: '/casper/white.svg', textColor: 'text-gray-300' },
};

export default function Message({
Expand All @@ -20,7 +20,7 @@ export default function Message({
isMyMessage = false,
children,
}: PropsWithChildren<MessageProps>) {
const { casper, textColor } = TYPES[team];
const { casper, textColor } = TYPES[team.toLowerCase() as SocketCategory];

return (
<div className="flex w-full items-center gap-12">
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/components/chat/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Category } from 'src/types/category.ts';
import { SocketCategory } from 'src/types/category.ts';

type NoticeChatProps = {
id: string;
Expand All @@ -13,7 +13,7 @@ export type MessageChatProps = {
type: 'm';
sender: number;
content: string;
team: Category;
team: SocketCategory;
};

type BlockedChatProps = {
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from './api.ts';
export { default as CATEGORIES } from './categories.ts';
export { CHAT_SOCKET_ENDPOINTS, RACING_SOCKET_ENDPOINTS } from './socket.ts';
export * from './socket.ts';
19 changes: 19 additions & 0 deletions packages/common/src/constants/socket.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Category, SocketCategory } from 'src/types/category.ts';

export const CHAT_SOCKET_ENDPOINTS = {
SUBSCRIBE: '/topic/chat',
PUBLISH: '/app/chat.sendMessage',
Expand All @@ -7,3 +9,20 @@ export const RACING_SOCKET_ENDPOINTS = {
SUBSCRIBE: '/topic/game',
PUBLISH: '/app/game.sendGameData',
} as const;

/**
* Mapping between Category and SocketCategory
*/
export const categoryToSocketCategory: Record<Category, SocketCategory> = {
pet: 'p',
travel: 't',
place: 's',
leisure: 'l',
};

export const socketCategoryToCategory: Record<SocketCategory, Category> = {
p: 'pet',
t: 'travel',
s: 'place',
l: 'leisure',
};
1 change: 1 addition & 0 deletions packages/common/src/types/category.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CATEGORIES from 'src/constants/categories.ts';

export type Category = (typeof CATEGORIES)[number];
export type SocketCategory = 'p' | 't' | 's' | 'l';
7 changes: 3 additions & 4 deletions packages/common/src/utils/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ACCESS_TOKEN_KEY } from 'src/constants/api.ts';
import getCookie from '../storage/cookie/getCookie.ts';
// eslint-disable-next-line import/no-cycle
import fetchWithInterceptors from './fetchInterceptors.ts';
import fetchWithInterceptors from 'src/utils/api/fetchInterceptors.ts';
import getCookie from 'src/utils/storage/cookie/getCookie.ts';

interface Interceptors {
request?: (url: string, options: RequestInit) => Promise<RequestInit> | RequestInit;
Expand All @@ -12,7 +11,7 @@ export function generateDefaultHeaders(): HeadersInit {
const headers: HeadersInit = {
'Content-Type': 'application/json',
};
const accessToken = getCookie(ACCESS_TOKEN_KEY);
const accessToken = getCookie<string>(ACCESS_TOKEN_KEY);
if (accessToken) {
headers.Authorization = accessToken;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/common/src/utils/storage/cookie/clearCookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function clearCookie(name: string): void {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
}

export default clearCookie;
7 changes: 4 additions & 3 deletions packages/common/src/utils/storage/cookie/getCookie.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
function getCookie(name: string) {
function getCookie<T>(name: string): T | null {
const cookies = document.cookie.split('; ');
if (cookies.length === 1 && cookies[0] === '') {
return null;
}
let returnValue = null;
let returnValue: T | null = null;
cookies.some((cookie) => {
const [key, value] = cookie.split('=');
if (key === name) {
returnValue = value;
returnValue = JSON.parse(value);
return true;
}
return false;
});
return returnValue;
}

export default getCookie;
3 changes: 2 additions & 1 deletion packages/common/src/utils/storage/cookie/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import clearCookie from './clearCookie.ts';
import getCookie from './getCookie.ts';
import setCookie from './setCookie.ts';

export default { getCookie, setCookie };
export default { getCookie, setCookie, clearCookie };
15 changes: 7 additions & 8 deletions packages/common/src/utils/storage/cookie/setCookie.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
function setCookie(name: string, value: string, days: number) {
let expires = '';
if (days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
expires = `; expires=${date.toUTCString()}`;
}
document.cookie = `${name}=${value || ''}${expires}; path=/`;
const DAY = 24 * 60 * 60 * 1000;

function setCookie<T>(name: string, value: T, expirationDays: number = 7): void {
const date = new Date();
date.setTime(date.getTime() + expirationDays * DAY);
const expires = `; expires=${date.toUTCString()}`;
document.cookie = `${name}=${JSON.stringify(value) || ''}${expires}; path=/`;
}

export default setCookie;
4 changes: 2 additions & 2 deletions packages/common/src/utils/storage/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import cookie from './cookie/index.ts';
import Cookie from './cookie/index.ts';

export default cookie;
export default Cookie;
4 changes: 2 additions & 2 deletions packages/user/src/components/common/toast/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ToastViewport = React.forwardRef<
<ToastPrimitives.Viewport
ref={ref}
className={cn(
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-10 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[500px]',
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-10 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[500px] gap-4',
className,
)}
{...props}
Expand Down Expand Up @@ -86,5 +86,5 @@ export {
ToastProvider,
ToastViewport,
type ToastActionElement,
type ToastProps,
type ToastProps
};
9 changes: 4 additions & 5 deletions packages/user/src/components/event/chatting/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { ChatList } from '@softeer/common/components';
import { memo } from 'react';
import ChatInput from 'src/components/event/chatting/inputArea/input/index.tsx';
import { UseChatSocketReturnType } from 'src/hooks/socket/useChatSocket.ts';
import Chat from './Chat.tsx';
import ChatInputArea from './inputArea/index.tsx';

/** 실시간 기대평 섹션 */

const RealTimeChatting = memo(({ onSendMessage, messages }: UseChatSocketReturnType) => (
<section className="container flex max-w-[1200px] snap-start flex-col items-center pb-[115px] pt-[50px]">
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>
<ChatInputArea>
<ChatInput onSend={onSendMessage} />
Expand All @@ -20,7 +19,7 @@ const RealTimeChatting = memo(({ onSendMessage, messages }: UseChatSocketReturnT
))}
</ChatList>
</div>
</section>
));
</section>;
}

export default RealTimeChatting;
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,24 @@ export default function ChatInput({ onSend }: ChatInputProps) {

const inputRef = useRef<HTMLInputElement>(null);

const handleSubmit = useCallback((event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

const disabledChatting = !isAuthenticated;

if (disabledChatting) {
toast({ description: DISABLED_CHATTING_TOAST_DESCRIPTION });
return;
}

if (inputRef.current?.value) {
onSend(inputRef.current.value);
inputRef.current.value = '';
}
}, [isAuthenticated]);
const handleSubmit = useCallback(
(event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

const disabledChatting = !isAuthenticated;

if (disabledChatting) {
toast({ description: DISABLED_CHATTING_TOAST_DESCRIPTION });
return;
}

if (inputRef.current?.value) {
onSend(inputRef.current.value);
inputRef.current.value = '';
}
},
[isAuthenticated],
);

return (
<form className="flex items-center gap-4" onSubmit={handleSubmit}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
import { Category } from '@softeer/common/types';
import numeral from 'numeral';
import { memo, useMemo } from 'react';
import { useMemo } from 'react';
import { TEAM_DESCRIPTIONS } from 'src/constants/teamDescriptions.ts';
import type { ChargeButtonData } from './ControlButton.tsx';

interface ChargeButtonContentProps extends Omit<ChargeButtonData, 'percentage'> {
interface ChargeButtonContentProps extends ChargeButtonData {
type: Category;
}

const ChargeButtonContent = memo(({ rank, vote, type }: ChargeButtonContentProps) => {
export default function ChargeButtonContent({
rank, vote, type, percentage }: ChargeButtonContentProps) {
const { shortTitle, title } = TEAM_DESCRIPTIONS[type];
const displayTitle = shortTitle ?? title;
const formattedVote = useMemo(() => formatVoteCount(vote), [vote]);
const displayVoteStats = useMemo(() => `${percentage.toFixed(1)}% (${formatVoteCount(vote)})`, [percentage, vote]);

return (
<>
<h2 className="pt-2">{rank}</h2>
<div className="flex flex-col items-center">
<p className={`text-body-3 font-medium ${voteStyles[type]}`}>{formattedVote}%</p>
<p className={`text-body-3 font-medium ${voteStyles[type]}`}>{displayVoteStats}</p>
<h6>{displayTitle}</h6>
</div>
</>
);
});

export default ChargeButtonContent;
}

/** Utility Functions */
function formatVoteCount(count: number): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Category } from '@softeer/common/types';
import { memo, useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import useAuth from 'src/hooks/useAuth.tsx';
import { useToast } from 'src/hooks/useToast.ts';
import type { Rank } from 'src/types/racing.d.ts';
import ChargeButtonContent from './ChargeButtonContent.tsx';
Expand All @@ -11,7 +12,7 @@ const MAX_CLICK = 10;
const MIN_PERCENT = 2;
const RESET_SECOND = 10000;
const MAX_CLICK_TOAST_DESCRIPTION = '배터리가 떨어질 때까지 기다려주세요!';

const DISABLED_RACING_TOAST_DESCRIPTION = '로그인 후 레이싱에 참여할 수 있습니다!';
interface ControlButtonProps {
type: Category;
data: ChargeButtonData;
Expand All @@ -25,7 +26,8 @@ export interface ChargeButtonData {
percentage: number;
}

const ControlButton = memo(({ onCharge, onFullyCharged, type, data }: ControlButtonProps) => {
export default function ControlButton({
onCharge, onFullyCharged, type, data }: ControlButtonProps) {
const { rank, percentage } = data;
const { progress, clickCount, handleClick } = useGaugeProgress({
percentage,
Expand All @@ -41,9 +43,7 @@ const ControlButton = memo(({ onCharge, onFullyCharged, type, data }: ControlBut
</ChargeButtonWrapper>
</ControllButtonWrapper>
);
});

export default ControlButton;
}

/** Custom Hook */
function useGaugeProgress({
Expand All @@ -56,31 +56,38 @@ function useGaugeProgress({
onFullyCharged: () => void;
}) {
const { toast } = useToast();
const { isAuthenticated } = useAuth();
const [progress, setProgress] = useState(percentage);
const [clickCount, setClickCount] = useState(0);

const updateProgress = useCallback((count: number) => {
const newProgress = calculateProgress(count);
setProgress(newProgress);
}, []);
}, [progress]);

const resetProgress = useCallback(() => {
setClickCount(0);
setProgress(percentage);
}, [percentage]);

useEffect(() => setProgress(percentage), [percentage]);

useEffect(() => {
if (clickCount > 0 && clickCount <= MAX_CLICK) {
updateProgress(clickCount);
}

if (clickCount === MAX_CLICK) {
onFullyCharged();
if (!isAuthenticated) {
toast({ description: DISABLED_RACING_TOAST_DESCRIPTION });
} else {
onFullyCharged();
}
toast({ description: MAX_CLICK_TOAST_DESCRIPTION });
const resetTimer = setTimeout(resetProgress, RESET_SECOND);
return () => clearTimeout(resetTimer);
}
}, [clickCount]);
}, [clickCount, isAuthenticated]);

const handleClick = useCallback(() => {
if (clickCount < MAX_CLICK) {
Expand Down
Loading

0 comments on commit ee6e4fe

Please sign in to comment.