diff --git a/frontend/src/App.css b/frontend/src/App.css index e6a56888..8aab6cad 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -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); + } +} diff --git a/frontend/src/api/channel.api.ts b/frontend/src/api/channel.api.ts index de9d6fe4..1907acdc 100644 --- a/frontend/src/api/channel.api.ts +++ b/frontend/src/api/channel.api.ts @@ -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'; @@ -56,9 +57,9 @@ export const getUserChannels = (userId: string): Promise> => { +export const getChannelInfo = (userId: string): Promise> => { const promiseFn = ( - fnResolve: (value: ResponseDto) => void, + fnResolve: (value: ResponseDto) => void, fnReject: (reason?: any) => void, ) => { const apiClient = getApiClient(); @@ -69,7 +70,7 @@ export const getChannelInfo = (userId: string): Promise(res.data)); + fnResolve(new ResponseDto(res.data)); } }) .catch(err => { diff --git a/frontend/src/api/dto/auth.dto.ts b/frontend/src/api/dto/auth.dto.ts index 528a074a..9d63e921 100644 --- a/frontend/src/api/dto/auth.dto.ts +++ b/frontend/src/api/dto/auth.dto.ts @@ -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 { diff --git a/frontend/src/api/dto/channel.dto.ts b/frontend/src/api/dto/channel.dto.ts index 58150eeb..dcc67f64 100644 --- a/frontend/src/api/dto/channel.dto.ts +++ b/frontend/src/api/dto/channel.dto.ts @@ -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; @@ -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; } @@ -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; +} diff --git a/frontend/src/component/authmodal/AuthModal.tsx b/frontend/src/component/authmodal/AuthModal.tsx index e99edf4b..dd565f01 100644 --- a/frontend/src/component/authmodal/AuthModal.tsx +++ b/frontend/src/component/authmodal/AuthModal.tsx @@ -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) => { const { name, value } = e.target; @@ -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자리 이상이어야 합니다.`, ); }); }; @@ -105,6 +110,7 @@ export const AuthModal = (props: IAuthModalProps) => { value={loginData.pw} onChange={handleChange} /> + {error ?

{error}

: ''} { value={registerData.confirmPw} onChange={handleChange} /> + {error ?

{error}

: ''} + - *
- *

예시 콘텐츠

- *

BottomSheet의 children

- *
- * - * ``` - */ - -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 (
- {props.children} +
+ +
+ +
{children}
); }; diff --git a/frontend/src/component/common/loadingSpinner/LoadingSpinner.tsx b/frontend/src/component/common/loadingSpinner/LoadingSpinner.tsx new file mode 100644 index 00000000..a39e2c16 --- /dev/null +++ b/frontend/src/component/common/loadingSpinner/LoadingSpinner.tsx @@ -0,0 +1,8 @@ +export const LoadingSpinner = () => { + return ( +
+
+ Loading map data... +
+ ); +}; diff --git a/frontend/src/component/content/Content.tsx b/frontend/src/component/content/Content.tsx index 3ac6cf9a..5f241726 100644 --- a/frontend/src/component/content/Content.tsx +++ b/frontend/src/component/content/Content.tsx @@ -3,9 +3,9 @@ import { useNavigate } from 'react-router-dom'; interface IContentProps { title: string; - time: string; person: number; link: string; + time: string; } /** @@ -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 (
{
{props.title}
- - {props.time} + {props.person > 0 && ( <> diff --git a/frontend/src/component/routebutton/RouteResultButton.tsx b/frontend/src/component/routebutton/RouteResultButton.tsx index 59cf11e7..f064f030 100644 --- a/frontend/src/component/routebutton/RouteResultButton.tsx +++ b/frontend/src/component/routebutton/RouteResultButton.tsx @@ -29,7 +29,7 @@ export const RouteResultButton = (props: IRouteResultButtonProps) => { )} >
- {props.user.end_location.title} + {props.user.start_location.title}
diff --git a/frontend/src/component/searchbox/SearchBox.tsx b/frontend/src/component/searchbox/SearchBox.tsx index 95fd8a4b..8d5d85a8 100644 --- a/frontend/src/component/searchbox/SearchBox.tsx +++ b/frontend/src/component/searchbox/SearchBox.tsx @@ -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 : '알 수 없는 오류'); @@ -177,7 +176,7 @@ export const SearchBox = (props: ISearchBoxProps) => { {searchResults.map(result => ( + {isUserLoggedIn && ( + + )}
@@ -103,25 +126,47 @@ export const Main = () => { otherLocations={otherLocations} /> ) : ( -
- Loading map data... -
+ ) ) : ( -
- {error ? `Error: ${error}` : 'Loading'} +
+ + {error ? `Error: ${error}` : 'Loading map data...'}
)}
- - {contentData.map(item => ( - - -
-
- ))} -
+ {isUserLoggedIn ? ( + + {channels.map(item => ( + + +
+
+ ))} +
+ ) : ( + +
+
+

로그인을 진행하여

+

더 많은 기능을

+

사용해보세요

+
+
+
+ )} + + {/* 로그인 모달 */} +
); }; diff --git a/frontend/src/types/channel.types.ts b/frontend/src/types/channel.types.ts index c96961e0..98ecafb8 100644 --- a/frontend/src/types/channel.types.ts +++ b/frontend/src/types/channel.types.ts @@ -1,22 +1,24 @@ -export interface ILocation { - lat: number | undefined; - lng: number | undefined; +export interface IPoint { + lat: number; + lng: number; } export interface IGuestMarkerStyle { - color: string | undefined; + color: string; } export interface IGuest { - name: string | undefined; - start_location: ILocation | undefined; - end_location: ILocation | undefined; - path: ILocation[] | undefined; - marker_style: IGuestMarkerStyle | undefined; + id: string; + name: string; + startPoint: IPoint; + endPoint: IPoint; + paths: IPoint[]; + markerStyle: IGuestMarkerStyle; } -export interface IUserChannelInfo { - host_id: string | undefined; - name: string | undefined; - guests: IGuest[] | undefined; +export interface IChannelInfo { + channelId: string; + hostId: string; + name: string; + guests: IGuest[]; } diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 325b5e83..74c5f62d 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -14,9 +14,12 @@ module.exports = { 75: '#E2E8F0', 100: '#EDF2F7', 150: '#AAB6C7', + 175: '#7A7A7A', + 180: '#7676801F', 200: '#6D6D6D', 400: '#555555', 800: '#3E3E3E', + 850: '#3C3C4399', 900: '#1C1C1C', }, blueGray: {