Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE][Feat] #14 : bottomsheet 관련 로그인, 회원가입 #273

Merged
merged 12 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
2 changes: 2 additions & 0 deletions frontend/src/api/dto/channel.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export class channelListEntity {
name: string | undefined;

generated_at: string | undefined;

guest_count: number | undefined;
}

export class getUserChannelsResEntity {
Expand Down
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>
);
};
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
Loading
Loading