Skip to content

Commit

Permalink
Merge branch 'frontend' into feature/fe/#10-canvas-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
juwon5272 authored Nov 27, 2024
2 parents 2f7741f + 6fda46b commit b8ba18f
Show file tree
Hide file tree
Showing 18 changed files with 429 additions and 165 deletions.
19 changes: 19 additions & 0 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,22 @@
-ms-user-select:none;
user-select:none
}

.spinner {
width: 3rem;
height: 3rem;
border: 0.5rem solid rgba(0, 0, 0, 0.1);
border-top: 0.5rem solid #1c58b3;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 10px;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
7 changes: 4 additions & 3 deletions frontend/src/api/channel.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ResponseDto } from '@/api/dto/response.dto.ts';
import {
createChannelReqEntity,
createChannelResEntity,
getChannelResEntity,
getUserChannelsResEntity,
} from '@/api/dto/channel.dto.ts';
import { getApiClient } from '@/api/client.api.ts';
Expand Down Expand Up @@ -56,9 +57,9 @@ export const getUserChannels = (userId: string): Promise<ResponseDto<getUserChan
return new Promise(promiseFn);
};

export const getChannelInfo = (userId: string): Promise<ResponseDto<createChannelReqEntity>> => {
export const getChannelInfo = (userId: string): Promise<ResponseDto<getChannelResEntity>> => {
const promiseFn = (
fnResolve: (value: ResponseDto<createChannelReqEntity>) => void,
fnResolve: (value: ResponseDto<getChannelResEntity>) => void,
fnReject: (reason?: any) => void,
) => {
const apiClient = getApiClient();
Expand All @@ -69,7 +70,7 @@ export const getChannelInfo = (userId: string): Promise<ResponseDto<createChanne
console.error(res);
fnReject(`msg.${res}`);
} else {
fnResolve(new ResponseDto<createChannelReqEntity>(res.data));
fnResolve(new ResponseDto<getChannelResEntity>(res.data));
}
})
.catch(err => {
Expand Down
10 changes: 7 additions & 3 deletions frontend/src/api/dto/auth.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
export class LoginResEntity {
token: string | undefined;

userId: string | undefined;
data: {
token: string | undefined;
userId: string | undefined;
} = {
token: undefined,
userId: undefined,
};
}

export class RegisterResEntity {
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/api/dto/channel.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
export class locationEntity {
title: string | undefined;

lat: number | undefined;

lng: number | undefined;
}

export class pathLocationEntity {
lat: number | undefined;

lng: number | undefined;
Expand All @@ -9,13 +17,15 @@ export class guestMarkerStyleEntity {
}

export class guestEntity {
id: string | undefined;

name: string | undefined;

start_location: locationEntity | undefined;

end_location: locationEntity | undefined;

path: locationEntity[] | undefined;
path: pathLocationEntity[] | undefined;

marker_style: guestMarkerStyleEntity | undefined;
}
Expand Down Expand Up @@ -52,8 +62,20 @@ export class channelListEntity {
name: string | undefined;

generated_at: string | undefined;

guest_count: number | undefined;
}

export class getUserChannelsResEntity {
channels: channelListEntity[] | undefined;
}

export class getChannelResEntity {
id: string | undefined;

name: string | undefined;

host_id: string | undefined;

guests: guestEntity[] | undefined;
}
24 changes: 16 additions & 8 deletions frontend/src/component/authmodal/AuthModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const AuthModal = (props: IAuthModalProps) => {
});

const [modalType, setModalType] = useState<'login' | 'register'>(props.type);
const [error, setError] = useState('');

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
Expand All @@ -46,42 +47,46 @@ export const AuthModal = (props: IAuthModalProps) => {
};

const switchToRegister = () => {
setError('');
setModalType('register');
};

const switchToLogin = () => {
setError('');
setModalType('login');
};

const handleLoginClick = () => {
doLogin(loginData.id, loginData.pw)
.then(el => {
if (el.data?.token && el.data?.userId) {
saveLocalData(AppConfig.KEYS.LOGIN_TOKEN, el.data.token);
saveLocalData(AppConfig.KEYS.LOGIN_USER, el.data.userId);
if (el.data?.data.token && el.data?.data.userId) {
saveLocalData(AppConfig.KEYS.LOGIN_TOKEN, el.data?.data.token);
saveLocalData(AppConfig.KEYS.LOGIN_USER, el.data?.data.userId);
}
setError('');
props.onClose();

window.location.reload();
})
.catch(() => {
alert('์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
setError('์•„์ด๋”” ํ˜น์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.');
});
};

const handleSignUpClick = () => {
if (registerData.pw !== registerData.confirmPw) {
alert('๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
setError('๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
return;
}
doRegister(registerData.id, registerData.name, registerData.pw, registerData.email)
.then(el => {
if (el.data) {
alert('ํšŒ์›๊ฐ€์ž…์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”.');
switchToLogin();
}
})
.catch(() => {
alert(
'ํšŒ์›๊ฐ€์ž…์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.\nid๋Š” 4์ž ์ด์ƒ, ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 6์ž๋ฆฌ ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
setError(
`ํšŒ์›๊ฐ€์ž…์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.\nid๋Š” 4์ž ์ด์ƒ, ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 6์ž๋ฆฌ ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.`,
);
});
};
Expand All @@ -105,6 +110,7 @@ export const AuthModal = (props: IAuthModalProps) => {
value={loginData.pw}
onChange={handleChange}
/>
{error ? <p className="pt-2 text-sm font-normal text-red-500">{error}</p> : ''}
<Modal.Footer
text="๋กœ๊ทธ์ธ"
onClick={handleLoginClick}
Expand Down Expand Up @@ -150,6 +156,8 @@ export const AuthModal = (props: IAuthModalProps) => {
value={registerData.confirmPw}
onChange={handleChange}
/>
{error ? <p className="pt-2 text-sm font-normal text-red-500">{error}</p> : ''}

<Modal.Footer
text="ํšŒ์›๊ฐ€์ž…"
onClick={handleSignUpClick}
Expand Down
101 changes: 67 additions & 34 deletions frontend/src/component/bottomsheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,86 @@
import React from 'react';
import { useBottomSheet } from '@/hooks/useBottomSheet';
import React, { useState, useRef } from 'react';

interface IBottomSheetProps {
minHeight: number;
maxHeight: number;
backgroundColor: string;
children: React.ReactNode;
}

/**
* `BottomSheet` ์ปดํฌ๋„ŒํŠธ๋Š” ํ•˜๋‹จ์—์„œ ์˜ฌ๋ผ์˜ค๋Š” ์‹œํŠธ ํ˜•ํƒœ์˜ UI๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
*
* @param {IBottomSheetProps} props - `children`์„ ํฌํ•จํ•œ ์ปดํฌ๋„ŒํŠธ ์†์„ฑ
* @param {number} props.minHeight - Bottom Sheet์˜ ์ตœ์†Œ ๋†’์ด๋ฅผ ํ™”๋ฉด ๋น„์œจ๋กœ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค (0.0 - 1.0).
* @param {number} props.maxHeight - Bottom Sheet์˜ ์ตœ๋Œ€ ๋†’์ด๋ฅผ ํ™”๋ฉด ๋น„์œจ๋กœ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค (0.0 - 1.0).
* @param {ReactNode} props.children - Bottom Sheet ๋‚ด๋ถ€์— ๋ Œ๋”๋งํ•  ์ฝ˜ํ…์ธ ์ž…๋‹ˆ๋‹ค.
* @returns {ReactNode} - ํ•˜๋‹จ ์‹œํŠธ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
*
* @remarks
* - ๋“œ๋ž˜๊ทธ ๋™์ž‘์„ ํ†ตํ•ด ์‹œํŠธ๋ฅผ ์—ด๊ณ  ๋‹ซ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
* - `useBottomSheet` ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ์œ„์น˜ ๋ฐ ๋“œ๋ž˜๊ทธ ๋™์ž‘์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
* - `minHeight`๋Š” Bottom Sheet๊ฐ€ ๋‹ซํžŒ ์ƒํƒœ์˜ ๋†’์ด ๋น„์œจ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
* - `maxHeight`๋Š” Bottom Sheet๊ฐ€ ์—ด๋ฆฐ ์ƒํƒœ์˜ ์ตœ๋Œ€ ๋†’์ด ๋น„์œจ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
*
* @example
* ```tsx
* <BottomSheet minHeight={0.5} maxHeight={0.85}>
* <div className="p-4">
* <h2>์˜ˆ์‹œ ์ฝ˜ํ…์ธ </h2>
* <p>BottomSheet์˜ children</p>
* </div>
* </BottomSheet>
* ```
*/

export const BottomSheet = (props: IBottomSheetProps) => {
const { sheet } = useBottomSheet({ minHeight: props.minHeight, maxHeight: props.maxHeight });
export const BottomSheet = ({
minHeight,
maxHeight,
backgroundColor,
children,
}: IBottomSheetProps) => {
const [sheetHeight, setSheetHeight] = useState(minHeight);
const startY = useRef(0);
const startHeight = useRef(minHeight);

const handleStart = (y: number) => {
startY.current = y;
startHeight.current = sheetHeight;
};

const handleMove = (y: number) => {
const deltaY = startY.current - y;
const newHeight = startHeight.current + deltaY / window.innerHeight;
setSheetHeight(Math.max(minHeight, Math.min(maxHeight, newHeight)));
};

const handleTouchStart = (e: React.TouchEvent) => {
handleStart(e.touches[0].clientY);
};

const handleTouchMove = (e: React.TouchEvent) => {
handleMove(e.touches[0].clientY);
};

const handleMouseMove = (e: MouseEvent) => {
handleMove(e.clientY);
};

const handleMouseUp = () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};

const handleMouseDown = (e: React.MouseEvent) => {
handleStart(e.clientY);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
};

const handleClose = () => {
setSheetHeight(minHeight);
};

return (
<div
ref={sheet}
className="bg-grayscale-25 shadow-dark fixed left-0 right-0 z-[1000] flex h-full flex-col rounded-t-lg transition-transform duration-700"
className="fixed bottom-0 left-0 right-0 z-50 rounded-t-2xl bg-white shadow-lg transition-transform duration-700 ease-out"
style={{
top: `calc(100% - ${props.minHeight * 100}%)`,
backgroundColor: `${backgroundColor}`,
height: `${sheetHeight * 100}vh`,
transform: `translateY(${(1 - sheetHeight) * 100}%)`,
}}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onMouseDown={handleMouseDown}
>
<div className="flex items-center justify-center pb-1 pt-2">
<div className="h-1.5 w-12 rounded-full bg-gray-300" />
</div>
{props.children}
<div className="flex items-center justify-end pb-1 pr-5 pt-2">
<button
type="button"
className="bg-grayscale-180 h-[30px] w-[30px] rounded-full text-lg font-semibold text-gray-500"
onClick={handleClose}
>
<p className="text-grayscale-850">โœ•</p>
</button>
</div>

<div className="h-full overflow-auto">{children}</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const LoadingSpinner = () => {
return (
<section className="flex h-full flex-col items-center justify-center gap-2 text-xl text-gray-700">
<div className="spinner" />
Loading map data...
</section>
);
};
17 changes: 14 additions & 3 deletions frontend/src/component/content/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useNavigate } from 'react-router-dom';

interface IContentProps {
title: string;
time: string;
person: number;
link: string;
time: string;
}

/**
Expand All @@ -30,6 +30,16 @@ interface IContentProps {
*/

export const Content = (props: IContentProps) => {
const formattedDate = new Date(props.time).toLocaleDateString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});

const formattedTime = new Date(props.time).toLocaleTimeString('ko-KR', {
hour: '2-digit',
minute: '2-digit',
});
const navigate = useNavigate();
return (
<div
Expand All @@ -41,8 +51,9 @@ export const Content = (props: IContentProps) => {
<div>
<header className="border-gray-200 pb-1 text-lg">{props.title}</header>
<section className="flex items-center text-sm leading-5 text-gray-500">
<time className="mr-4">์‹œ๊ฐ„</time>
<span className="mr-6">{props.time}</span>
<time className="mr-4">
{formattedDate} {formattedTime}
</time>
{props.person > 0 && (
<>
<MdGroup className="mr-2 h-5 w-5" aria-label="์ธ์›์ˆ˜ ์•„์ด์ฝ˜" />
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/component/routebutton/RouteResultButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const RouteResultButton = (props: IRouteResultButtonProps) => {
)}
>
<div className="h-full w-24 overflow-hidden text-ellipsis whitespace-nowrap rounded border-2 px-2 py-[16px] text-start text-xs font-normal">
{props.user.end_location.title}
{props.user.start_location.title}
</div>
<GoArrowRight className="mx-2 h-8 w-8" />
<div className="h-full w-24 overflow-hidden text-ellipsis whitespace-nowrap rounded border-2 px-2 py-[16px] text-start text-xs font-normal">
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/component/searchbox/SearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ export const SearchBox = (props: ISearchBoxProps) => {
lat: parseFloat(item.mapy) / 1e7,
lng: parseFloat(item.mapx) / 1e7,
}));
console.log(data);
setSearchResults(formattedResults); // ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
} catch (err) {
setError(err instanceof Error ? err.message : '์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜');
Expand Down Expand Up @@ -177,7 +176,7 @@ export const SearchBox = (props: ISearchBoxProps) => {
{searchResults.map(result => (
<button
type="button"
key={result.roadAddress}
key={`${result.lat}-${result.lng}`}
onClick={() => handleSelectResult(result)}
className="flex flex-col items-start gap-2 p-2"
>
Expand Down
Loading

0 comments on commit b8ba18f

Please sign in to comment.