Skip to content

Commit

Permalink
Merge branch 'develop' into feat/login
Browse files Browse the repository at this point in the history
  • Loading branch information
khinwaiyan committed Feb 6, 2025
2 parents 1fcf15c + 047a2ee commit b2cdcba
Show file tree
Hide file tree
Showing 17 changed files with 318 additions and 20 deletions.
11 changes: 11 additions & 0 deletions src/Layout/AuthLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Outlet } from 'react-router-dom';

const AuthLayout = () => {
return (
<div className="relative mx-auto flex h-screen w-full max-w-[560px] flex-col items-center overflow-hidden">
<Outlet />
</div>
);
};

export default AuthLayout;
9 changes: 7 additions & 2 deletions src/Layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Outlet } from 'react-router-dom';

import SideBar from '@/Layout/SideBar.tsx';

const Layout = () => {
return (
<div className="relative mx-auto flex h-screen w-full max-w-[560px] flex-col items-center overflow-hidden">
<Outlet />
<div className="flex">
<SideBar />
<div className="ml-[70px] flex w-full justify-center px-[56px] py-[48px]">
<Outlet />
</div>
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/Layout/SideBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const SideBar = () => {
return (
<div className="fixed left-0 top-0 h-screen w-[70px] bg-[#ECECEC]"></div>
<div className="fixed left-0 top-0 h-full w-[70px] bg-[#ECECEC]"></div>
);
};

Expand Down
23 changes: 23 additions & 0 deletions src/components/InfoCard/InfoCardModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Modal from '@/components/common/Modal.tsx';
import { ModalStateType } from '@/types/type.ts';

interface InfoCardModalProps {
state: ModalStateType;
onClose: () => void;
}

const InfoCardModal = ({ state, onClose }: InfoCardModalProps) => {
return (
<Modal
state={state}
onBackgroundClick={onClose}
>
<div className="w-96 p-4">
<h1 className="text-lg font-bold">Info Card</h1>
<p className="mt-2 text-sm">This is an info card.</p>
</div>
</Modal>
);
};

export default InfoCardModal;
13 changes: 11 additions & 2 deletions src/components/common/BackButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { useNavigate } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';

import { Icons } from '@/constants/icons.tsx';

const BackButton = ({ linkTo = '-1' }: { linkTo?: string }) => {
interface BackButtonProps {
linkTo?: string;
className?: string;
}

const BackButton = ({ linkTo = '-1', className = '' }: BackButtonProps) => {
const navigate = useNavigate();

const handleClick = () => {
Expand All @@ -15,7 +21,10 @@ const BackButton = ({ linkTo = '-1' }: { linkTo?: string }) => {

return (
<div
className="flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-full border border-borderGray bg-white transition-colors hover:bg-backgroundGray"
className={twMerge(
'flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-full border border-borderGray bg-white transition-colors hover:bg-backgroundGray',
className
)}
onClick={handleClick}
>
{Icons.Backward}
Expand Down
3 changes: 2 additions & 1 deletion src/components/common/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const Input = ({
<input
{...props}
className={twMerge(
'box-border w-full rounded-[4px] border border-borderGray bg-white px-2 text-sm font-normal text-black placeholder-textGray1 outline-none focus:border-[1.5px] focus:border-pointColor',
'box-border w-full rounded-[4px] border border-borderGray bg-white px-[10px] py-[10px] text-sm font-normal text-black placeholder-textGray1 outline-none',
'focus:border-[1.5px] focus:border-pointColor focus:px-[9.5px] focus:py-[9.5px]',
className
)}
/>
Expand Down
58 changes: 58 additions & 0 deletions src/components/common/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useEffect, useState } from 'react';

import { ModalStateType } from '@/types/type.ts';

interface ModalProps {
children?: React.ReactNode;
state: ModalStateType;
onBackgroundClick: () => void;
}

export default function Modal({
children,
onBackgroundClick,
state,
}: ModalProps) {
const [isVisible, setIsVisible] = useState(state !== 'closed');

useEffect(() => {
if (state === 'open') {
setIsVisible(true);
} else if (state === 'closing') {
const timeout = setTimeout(() => {
setIsVisible(false);
}, 200);
return () => clearTimeout(timeout);
}
}, [state]);

useEffect(() => {
const handleEsc = (event: KeyboardEvent) => {
if (event.key === 'Escape' && state === 'open') {
onBackgroundClick();
}
};
window.addEventListener('keydown', handleEsc);
return () => {
window.removeEventListener('keydown', handleEsc);
};
}, [onBackgroundClick, state]);

if (!isVisible) return null;

return (
<div
className={`fixed inset-0 z-50 flex items-center justify-center transition-opacity duration-200 ${
state === 'open' ? 'opacity-100' : 'pointer-events-none opacity-0'
}`}
>
<div
className="absolute inset-0 bg-black bg-opacity-50"
onClick={onBackgroundClick}
/>
<div className="relative flex flex-col items-center rounded-md bg-white p-6">
{children}
</div>
</div>
);
}
54 changes: 54 additions & 0 deletions src/components/common/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { useEffect, useRef } from 'react';
import { twMerge } from 'tailwind-merge';

interface TextFieldProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
isEditing?: boolean;
}

const Textarea = ({
className = '',
isEditing = false,
rows = 4,
...props
}: TextFieldProps) => {
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const prevIsEditing = useRef(isEditing);

useEffect(() => {
const textarea = textAreaRef.current;
if (!textarea) return;

if (isEditing) {
// 포커스 전환 시: 텍스트의 맨 끝으로 커서를 이동하고 스크롤을 맞춤
textarea.focus();
const len = textarea.value.length;
textarea.setSelectionRange(len, len);
textarea.scrollTop = textarea.scrollHeight;
} else if (prevIsEditing.current && !isEditing) {
// 포커스 해제 시: 텍스트의 맨 처음으로 커서를 이동하고 스크롤을 맞춤
textarea.focus();
textarea.setSelectionRange(0, 0);
textarea.scrollTop = 0;
}
prevIsEditing.current = isEditing;
}, [isEditing]);

return (
<textarea
{...props}
rows={rows}
readOnly={!isEditing}
ref={textAreaRef}
className={twMerge(
'textarea-scrollbar box-border w-full resize-none rounded-[4px] bg-white px-[10px] py-[10px] text-sm font-normal placeholder-textGray1',
isEditing
? 'border-[1px] border-borderGray text-black focus:border-[1.5px] focus:border-pointColor focus:px-[9.5px] focus:py-[9.5px] focus:outline-none'
: 'cursor-default border-[1px] border-borderGray text-textGray2 focus:outline-none',
className
)}
/>
);
};

export default Textarea;
29 changes: 29 additions & 0 deletions src/hooks/useModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState, useRef } from 'react';
import { ModalStateHookType, ModalStateType } from '@/types/type.ts';

const MODAL_ANIMATION_DURATION = 300;

function useModalState(): ModalStateHookType {
const [modalState, setModalState] = useState<ModalStateType>('closed');
const closeTimeoutRef = useRef<number | null>(null);

const openModal = () => {
if (closeTimeoutRef.current !== null) {
clearTimeout(closeTimeoutRef.current);
closeTimeoutRef.current = null;
}
setModalState('open');
};

const closeModal = () => {
setModalState('closing');
closeTimeoutRef.current = window.setTimeout(() => {
setModalState('closed');
closeTimeoutRef.current = null;
}, MODAL_ANIMATION_DURATION);
};

return [modalState, openModal, closeModal];
}

export default useModalState;
5 changes: 5 additions & 0 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Home = () => {
return <div>Home입니다.</div>;
};

export default Home;
4 changes: 2 additions & 2 deletions src/pages/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Button from '@/components/common/Button.tsx';
import { BLACK_LOGO } from '@/constants/images.ts';

const Landing = () => {
const handleLogin = async () => {
const handleLogin = () => {
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=${import.meta.env.VITE_DOMAIN}/auth/callback&prompt=consent&response_type=code&client_id=${import.meta.env.VITE_GOOGLE_OAUTH_CLIENT_ID}&scope=openid%20email%20profile&access_type=offline`;
};

Expand Down Expand Up @@ -33,7 +33,7 @@ const Landing = () => {
<div className="text-[15px]">
<span className="text-textGray2">처음이시라면?</span>
<Link
to="/signup"
to="/auth/signup"
className="ml-2.5 font-semibold text-textGreen underline"
>
회원가입하기
Expand Down
2 changes: 1 addition & 1 deletion src/pages/OAuthCallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const OAuthCallback = () => {
setAccessToken(data.access_token);
localStorage.setItem('accessToken', data.access_token);
localStorage.setItem('refreshToken', data.refresh_token);
navigate('/signup');
navigate('/auth/signup');
}

return null;
Expand Down
39 changes: 39 additions & 0 deletions src/pages/Test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useState } from 'react';

import Input from '@/components/common/Input.tsx';
import Textarea from '@/components/common/Textarea.tsx';
import GroupTransferInfo from '@/components/InfoCard/GroupTransferInfo.tsx';
import InfoCardModal from '@/components/InfoCard/InfoCardModal.tsx';
import useModalState from '@/hooks/useModal.ts';

const Test = () => {
const [infoCardModalState, openInfoCardModal, closeInfoCardModal] =
useModalState();

const [isEditing, setIsEditing] = useState(false);

return (
<div>
<button onClick={openInfoCardModal}>모달 열기</button>
{infoCardModalState !== 'closed' && (
<InfoCardModal
state={infoCardModalState}
onClose={closeInfoCardModal}
/>
)}
<button onClick={() => setIsEditing((prev) => !prev)}>edit</button>
<Textarea isEditing={isEditing} />
<Input />

<GroupTransferInfo
teamName="AI기술팀"
startDate="2024.07.02"
endDate="2024.12.23"
leaderName="김철수"
responsibilities="밥먹기"
/>
</div>
);
};

export default Test;
31 changes: 20 additions & 11 deletions src/route/router.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
import { createBrowserRouter } from 'react-router-dom';

import AuthLayout from '@/Layout/AuthLayout.tsx';
import Layout from '@/Layout/Layout.tsx';
// import InfoCard from '@/pages/InfoCard/InfoCard.tsx';
import Home from '@/pages/Home.tsx';
import Landing from '@/pages/Landing.tsx';
import OAuthCallback from '@/pages/OAuthCallback.tsx';
import SignUp from '@/pages/SignUp.tsx';
import ProtectedRoute from '@/route/ProtectedRoute.tsx';

export const router = createBrowserRouter([
// Layout을 사용하는 기본 구조
{
path: '/',
element: <Layout />,
children: [
{ path: 'auth/login', element: <Landing /> },
{ path: 'auth/callback', element: <OAuthCallback /> },
{
element: <ProtectedRoute redirectTo="/auth/login" />,
children: [
{ path: 'signup', element: <SignUp /> },
// { path: 'infocard', element: <InfoCard /> },
],
children: [{ index: true, element: <Home /> }],
},
{ path: '*', element: <Landing /> },
],
},

// AuthLayout을 사용하는 인증 관련 경로
{
path: '/',
element: <ProtectedRoute redirectTo="/auth/login" />,
children: [{ index: true, element: <Landing /> }],
path: '/auth',
element: <AuthLayout />,
children: [
{ path: 'login', element: <Landing /> },
{ path: 'callback', element: <OAuthCallback /> },
{ path: 'signup', element: <SignUp /> },
],
},

// 404 페이지 처리 (잘못된 경로 접근 시 `Landing`으로 이동)
{
path: '*',
element: <Landing />,
},
]);

Expand All @@ -40,6 +48,7 @@ export const router = createBrowserRouter([
// { path: 'auth/login', element: <Landing /> },
// { path: 'signup', element: <SignUp /> },
// { path: 'infocard', element: <InfoCard /> },
// { path: 'test', element: <Test /> },
// ],
// },
// ]);
Loading

0 comments on commit b2cdcba

Please sign in to comment.