Skip to content
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
3 changes: 2 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Route, Routes } from 'react-router';

import useViewport from './hooks/useViewport';
import Header from './layouts/Header';
import Home from './pages/Home';
import LetterBoardPage from './pages/LetterBoard';
import LetterBoardDetailPage from './pages/LetterBoardDetail';
Expand Down Expand Up @@ -34,7 +35,7 @@ const App = () => {
<Route path="letter/:id" element={<LetterBoardDetailPage />} />
<Route path="rolling/:id" element={<RollingPaperPage />} />
</Route>
<Route path="mypage">
<Route path="mypage" element={<Header />}>
<Route index element={<MyPage />} />
<Route path="notifications" element={<NotificationsPage />} />
</Route>
Expand Down
8 changes: 8 additions & 0 deletions src/assets/icons/alarm.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icons/arrow-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import AlarmIcon from './alarm.svg?react';
import ArrowLeftIcon from './arrow-left.svg?react';
import PersonIcon from './person.svg?react';

export { AlarmIcon, PersonIcon, ArrowLeftIcon };
8 changes: 8 additions & 0 deletions src/assets/icons/person.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/background-overlay.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/yellow-modal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file removed src/components/.gitkeep
Empty file.
58 changes: 58 additions & 0 deletions src/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import ModalBg from '@/assets/images/yellow-modal.png';

import ModalOverlay from './ModalOverlay';

interface ConfirmModalProps {
title: string;
description: string;
cancelText: string;
confirmText: string;
children?: React.ReactNode;
onCancel: () => void;
onConfirm: () => void;
}

const ConfirmModal = ({
title,
description,
cancelText,
confirmText,
children,
onCancel,
onConfirm,
}: ConfirmModalProps) => {
// TODO: 배경 이미지 삽입
// TODO: 전역 상태로 관리해야하는지 고민
return (
<ModalOverlay>
<div className="w-73">
<section className="relative mb-12 overflow-hidden rounded-lg p-5">
<img src={ModalBg} className="absolute inset-0 z-[-10] h-full w-full" />
<div className="flex flex-col gap-1">
<p className="body-m text-gray-80">{title}</p>
<p className="caption-r text-black">{description}</p>
</div>
{children}
</section>
<section className="flex items-center gap-6">
<button
type="button"
className="body-m secondary-btn h-10 flex-1 basis-1/2"
onClick={onCancel}
>
{cancelText}
</button>
<button
type="button"
className="primary-btn body-m h-10 flex-1 basis-1/2"
onClick={onConfirm}
>
{confirmText}
</button>
</section>
</div>
</ModalOverlay>
);
};

export default ConfirmModal;
13 changes: 13 additions & 0 deletions src/components/ModalOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
interface ModalOverlayProps {
children: React.ReactElement;
}

const ModalOverlay = ({ children }: ModalOverlayProps) => {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
{children}
</div>
);
};

export default ModalOverlay;
4 changes: 2 additions & 2 deletions src/hooks/useViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { useEffect } from 'react';
function useViewport() {
useEffect(() => {
const setViewport = () => {
const vh = window.innerHeight * 0.00999;
const vw = document.documentElement.clientWidth * 0.00999;
const vh = window.innerHeight * 0.01;
const vw = document.documentElement.clientWidth * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
document.documentElement.style.setProperty('--vw', `${vw}px`);
};
Expand Down
22 changes: 22 additions & 0 deletions src/layouts/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Outlet } from 'react-router';

import { AlarmIcon, ArrowLeftIcon, PersonIcon } from '@/assets/icons';

const Header = () => {
// TODO: 뒤로 가기 버튼이 보이는 조건 추가
// TODO: 스크롤 발생 시, 어떻게 보여져야 하는지
return (
<>
<header className="sticky top-0 flex items-center justify-between p-5">
<ArrowLeftIcon className="h-6 w-6 text-white" />
<div className="flex items-center gap-3">
<AlarmIcon className="h-6 w-6 text-white" />
<PersonIcon className="h-6 w-6 text-white" />
</div>
</header>
<Outlet />
</>
);
};

export default Header;
9 changes: 9 additions & 0 deletions src/pages/MyPage/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const TEMPERATURE_RANGE = [
{ min: 0, max: 10, description: '따뜻함이 필요한 따숨님' },
{ min: 10, max: 25, description: '살짝 포근한 따숨님' },
{ min: 25, max: 40, description: '따스한 온기를 가진 따숨님' },
{ min: 40, max: 55, description: '마음이 따뜻한 따숨님' },
{ min: 55, max: 70, description: '훈훈한 따숨님' },
{ min: 70, max: 80, description: '정말 따뜻한 따숨님' },
{ min: 85, max: 100, description: '사랑이 넘치는 따숨님' },
];
85 changes: 84 additions & 1 deletion src/pages/MyPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,88 @@
import { useState } from 'react';

import ConfirmModal from '@/components/ConfirmModal';

import { TEMPERATURE_RANGE } from './constants';

const DUMMY_TEMP = 48.5;
const DUMMY_ZIP_CODE = '235EA';

const MyPage = () => {
return <div>MyPage</div>;
const [isOpenModal, setIsOpenModal] = useState(false);

const getDescriptionByTemperature = (temp: number) => {
const range = TEMPERATURE_RANGE.find((range) => temp >= range.min && temp < range.max);
return range?.description;
};

const description = getDescriptionByTemperature(DUMMY_TEMP);

return (
<>
{isOpenModal && (
<ConfirmModal
title="정말 탈퇴하시겠어요?"
description="탈퇴하시면, 지금까지 나눈 따뜻한 마음들이 사라져요"
cancelText="되돌아가기"
confirmText="탈퇴하기"
onCancel={() => setIsOpenModal(false)}
onConfirm={() => setIsOpenModal(false)}
/>
)}
<main className="flex grow flex-col gap-12 px-5 pt-9 pb-6">
<h1 className="h2-b mx-auto flex gap-1.5">
{DUMMY_ZIP_CODE.split('').map((code, index) => (
<div
key={index}
className="flex h-13.5 w-10 items-center justify-center rounded-sm bg-white inset-shadow-[0_4px_4px_0] inset-shadow-black/10"
>
{code}
</div>
))}
</h1>
<section>
<h2 className="mb-2 flex justify-between">
<p className="body-sb text-gray-60">{description}</p>
<p className="body-sb text-accent-1">{DUMMY_TEMP}도</p>
</h2>
<div className="h-4 w-full rounded-full bg-white">
<div
className="h-full w-[calc(${degree}%)] rounded-full bg-[#FFB5AC]"
style={{ width: `calc(${DUMMY_TEMP}%)` }}
/>
</div>
</section>
<section className="flex flex-col gap-8">
<div className="flex flex-col gap-2">
<h3 className="text-gray-40 body-sb">활동</h3>
<p className="body-sb text-gray-100">내가 올린 게시물</p>
</div>
<div className="flex flex-col gap-2">
<h3 className="text-gray-40 body-sb">고객 센터</h3>
<p className="body-sb text-gray-100">운영자에게 문의하기</p>
</div>
<div className="flex flex-col gap-2">
<h3 className="text-gray-40 body-sb">계정</h3>
<div className="flex justify-between">
<p className="body-sb text-gray-100">로그인 정보</p>
<p className="body-r text-gray-60">
<span className="mr-2">카카오</span>
<span>[email protected]</span>
</p>
</div>
<p className="body-sb text-gray-100">로그아웃</p>
</div>
</section>
<button
type="button"
className="text-gray-60 body-m mt-auto self-start underline"
onClick={() => setIsOpenModal(true)}
>
탈퇴하기
</button>
</main>
</>
);
};

export default MyPage;
7 changes: 7 additions & 0 deletions src/styles/components.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
/* 재사용 가능한 UI 컴포넌트의 스타일 정의 */

@layer components {
.primary-btn {
@apply bg-primary-3 rounded-lg text-black;
}

.secondary-btn {
@apply bg-primary-4 rounded-lg text-gray-50;
}
}
19 changes: 19 additions & 0 deletions src/styles/preflight.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,26 @@
--vw: 1vw;
}

* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

body {
font-family: 'Pretendard Variable', Pretendard, sans-serif;
overflow-y: scroll;
overflow-x: hidden;
/* 그라데이션 배경 들어가는 곳 */
background:
url('/src/assets/images/background-overlay.png') repeat,
linear-gradient(
180deg,
rgba(234, 191, 23, 0.5) 2.83%,
rgba(255, 245, 221, 0.5) 35.47%,
rgba(255, 251, 248, 0.5) 55.48%
),
#f2f2f2;
background-blend-mode: overlay, normal, normal;
}

#root {
Expand All @@ -23,4 +38,8 @@
margin: 0 auto;
position: relative;
}

button {
cursor: pointer;
}
}
1 change: 1 addition & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" />