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
8 changes: 6 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Home from './pages/Home';
import LetterBoardPage from './pages/LetterBoard';
import LetterBoardDetailPage from './pages/LetterBoardDetail';
import LetterBoxPage from './pages/LetterBox';
import LetterBoxDetailPage from './pages/LetterBoxDetail';
import LetterDetailPage from './pages/LetterDetail';
import LoginPage from './pages/Login';
import MyPage from './pages/MyPage';
Expand All @@ -25,8 +26,11 @@ const App = () => {
<Route path="login" element={<LoginPage />} />
<Route path="onboarding" element={<OnboardingPage />} />
<Route path="letter">
<Route path="random" element={<RandomLettersPage />} />
<Route path="box" element={<LetterBoxPage />} />
<Route element={<Layout />}>
<Route path="random" element={<RandomLettersPage />} />
<Route path="box" element={<LetterBoxPage />} />
<Route path="box/:id" element={<LetterBoxDetailPage />} />
</Route>
<Route path="write" element={<WritePage />} />
<Route path=":id" element={<LetterDetailPage />} />
</Route>
Expand Down
2 changes: 2 additions & 0 deletions src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ArrowLeftIcon from './arrow-left.svg?react';
import BoardIcon from './board.svg?react';
import DeleteIcon from './delete.svg?react';
import EnvelopeIcon from './envelope.svg?react';
import InformationIcon from './infromation.svg?react';
import LikeFilledIcon from './like-filled.svg?react';
import LikeOutlinedIcon from './like-outlined.svg?react';
import NoticeIcon from './notice.svg?react';
Expand All @@ -14,6 +15,7 @@ export {
AlarmIcon,
PersonIcon,
ArrowLeftIcon,
InformationIcon,
SirenFilledIcon,
SirenOutlinedIcon,
EnvelopeIcon,
Expand Down
8 changes: 8 additions & 0 deletions src/assets/icons/infromation.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/door.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 6 additions & 3 deletions src/components/BackgroundBottom.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import BgItem from '@/assets/images/field-4.png';

import BackgroundImageWrapper from './BackgroundImageWrapper';

const BackgroundBottom = () => {
return (
<div
className="background-image-filled fixed bottom-[-40px] left-1/2 z-[-10] h-42 w-full -translate-x-1/2 opacity-70"
style={{ '--bg-image': `url(${BgItem})` } as React.CSSProperties}
<BackgroundImageWrapper
as="div"
className="fixed bottom-[-40px] left-1/2 z-[-10] h-42 w-full -translate-x-1/2 opacity-70"
imageUrl={BgItem}
/>
);
};
Expand Down
29 changes: 29 additions & 0 deletions src/components/BackgroundImageWrapper.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오옹 컴포넌트 타입을 따로 받을 수도 있군요! 그런데 왜 따로 받은 건가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

시맨틱 태그에 신경쓰신다면 적절한 태그가 상황마다 다를 것 같아서 props로 받을 수 있게 했습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디테일 귀신 ㄷㄷㄷㄷ

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { twMerge } from 'tailwind-merge';

interface BackgroundImageWrapperProps {
as?: React.ElementType;
imageUrl: string;
className?: string;
children?: React.ReactNode;
onClick?: () => void;
}

const BackgroundImageWrapper = ({
as: Component = 'div',
imageUrl,
className,
children,
onClick,
}: BackgroundImageWrapperProps) => {
return (
<Component
className={twMerge('background-image-filled', className)}
style={{ '--bg-image': `url(${imageUrl})` } as React.CSSProperties}
onClick={onClick}
>
{children}
</Component>
);
};

export default BackgroundImageWrapper;
8 changes: 3 additions & 5 deletions src/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ModalBg from '@/assets/images/modal-yellow.png';

import BackgroundImageWrapper from './BackgroundImageWrapper';
import ModalOverlay from './ModalOverlay';

interface ConfirmModalProps {
Expand Down Expand Up @@ -27,16 +28,13 @@ const ConfirmModal = ({
return (
<ModalOverlay>
<div className="w-73">
<section
className="background-image-filled mb-12 rounded-lg p-5"
style={{ '--bg-image': `url(${ModalBg})` } as React.CSSProperties}
>
<BackgroundImageWrapper as="section" className="mb-12 rounded-lg p-5" imageUrl={ModalBg}>
<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>
</BackgroundImageWrapper>
<section className="flex items-center gap-6">
<button
type="button"
Expand Down
31 changes: 31 additions & 0 deletions src/components/ListItemWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { twMerge } from 'tailwind-merge';

interface ListItemWrapperProps {
isSender?: boolean;
className?: string;
children: React.ReactNode;
onClick?: (e: React.MouseEvent<HTMLElement>) => void;
}

const ListItemWrapper = ({
isSender = false,
className,
children,
onClick,
}: ListItemWrapperProps) => {
return (
<article
className={twMerge(
'relative flex overflow-hidden rounded-sm p-4',
isSender ? 'list-sender-bg' : 'list-receiver-bg',
className,
)}
onClick={onClick}
>
<div className="z-10 w-full">{children}</div>
<div className="absolute inset-0 z-0 bg-white/50 blur-xl" />
</article>
);
};

export default ListItemWrapper;
10 changes: 4 additions & 6 deletions src/components/MessageModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ChangeEvent } from 'react';

import ModalBg from '@/assets/images/modal-pink.png';

import BackgroundImageWrapper from './BackgroundImageWrapper';
import ModalOverlay from './ModalOverlay';
import TextareaField from './TextareaField';

Expand Down Expand Up @@ -30,19 +31,16 @@ const MessageModal = ({
}: MessageModalProps) => {
return (
<ModalOverlay>
<p className="body-sb mb-4 text-white">{description}</p>
<section
className="background-image-filled mb-12 w-78 rounded-lg p-4"
style={{ '--bg-image': `url(${ModalBg})` } as React.CSSProperties}
>
<p className="body-sb mb-4 text-center text-white">{description}</p>
<BackgroundImageWrapper as="section" className="mb-12 w-78 rounded-lg p-4" imageUrl={ModalBg}>
<TextareaField
rows={5}
value={inputValue}
placeholder={placeholder}
onChange={onInputChange}
/>
{children}
</section>
</BackgroundImageWrapper>
<section className="flex items-center gap-6">
<button
type="button"
Expand Down
2 changes: 1 addition & 1 deletion src/components/PageTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface PageTitleProps {

const PageTitle = ({ className, children }: PageTitleProps) => {
return (
<h1 className={twMerge('text-gray-60 body-b rounded-full bg-white px-6 py-4', className)}>
<h1 className={twMerge('text-gray-60 body-b w-fit rounded-full bg-white px-6 py-4', className)}>
{children}
</h1>
);
Expand Down
10 changes: 6 additions & 4 deletions src/components/TextareaField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { ComponentPropsWithoutRef } from 'react';

const TextareaField = ({ ...props }: ComponentPropsWithoutRef<'textarea'>) => {
return (
<textarea
className="body-m placeholder:text-gray-30 text-gray-80 w-full resize-none rounded-sm bg-white px-3 py-1.5"
{...props}
/>
<div className="w-full rounded-sm bg-white px-3 py-1.5">
<textarea
className="body-m placeholder:text-gray-30 text-gray-80 w-full resize-none bg-transparent"
{...props}
/>
</div>
);
};

Expand Down
10 changes: 6 additions & 4 deletions src/pages/LetterBoard/components/LetterPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link } from 'react-router';

import PinkLetterBg from '@/assets/images/letter-pink.png';
import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';

interface LetterPreviewProps {
id: string;
Expand All @@ -12,14 +13,15 @@ interface LetterPreviewProps {
const LetterPreview = ({ id, to, from, content }: LetterPreviewProps) => {
return (
<Link to={id}>
<article
className="caption-r background-image-filled flex flex-col gap-2 rounded-sm px-3 py-2"
style={{ '--bg-image': `url(${PinkLetterBg})` } as React.CSSProperties}
<BackgroundImageWrapper
as="article"
className="caption-r flex flex-col gap-2 rounded-sm px-3 py-2"
imageUrl={PinkLetterBg}
>
<p>From.{from}</p>
<p className="line-clamp-2 font-light">{content}</p>
<p className="place-self-end">To.{to}</p>
</article>
</BackgroundImageWrapper>
</Link>
);
};
Expand Down
12 changes: 6 additions & 6 deletions src/pages/LetterBoardDetail/components/Letter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PinkMemoBg from '@/assets/images/memo-pink.png';
import YellowMemoBg from '@/assets/images/memo-yellow.png';
import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';

interface LetterProps {
letter: {
Expand All @@ -12,16 +13,15 @@ interface LetterProps {

const Letter = ({ letter, isSender = false }: LetterProps) => {
return (
<article
className="background-image-filled flex flex-col gap-2 p-4 text-black"
style={
{ '--bg-image': `url(${isSender ? PinkMemoBg : YellowMemoBg})` } as React.CSSProperties
}
<BackgroundImageWrapper
as="article"
className="flex flex-col gap-2 p-4 text-black"
imageUrl={isSender ? PinkMemoBg : YellowMemoBg}
>
<p className="body-sb">To. {letter.receiver}</p>
<p className="body-r leading-[26px] whitespace-pre-wrap">{letter.content}</p>
<p className="body-m place-self-end">From. {letter.sender}</p>
</article>
</BackgroundImageWrapper>
);
};

Expand Down
55 changes: 55 additions & 0 deletions src/pages/LetterBox/components/LetterBoxItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Link } from 'react-router';
import { twMerge } from 'tailwind-merge';

interface LetterBoxItemProps {
zipCode: string;
letterCount: number;
isChecked?: boolean;
isClosed?: boolean;
}

const LetterBoxItem = ({
zipCode,
letterCount,
isChecked = false,
isClosed = false,
}: LetterBoxItemProps) => {
return (
<Link to="id">
<article className="flex h-fit w-fit flex-col items-center">
<div className="text-gray-70 flex h-25 w-20 flex-col gap-1.5 bg-linear-to-b from-[#D5B695] to-[#B3895D] p-1.5">
<p
className={twMerge(
'body-m from-white px-1',
isClosed
? 'bg-[repeating-linear-gradient(#D9D9D9,#D9D9D9_17px,#C2C2C2_17px,#C2C2C2_23px)]'
: 'bg-linear-to-b',
isChecked ? 'to-[#FFF5ED]' : 'to-[#FFF4F2]',
)}
>
{zipCode}
</p>
{isClosed ? (
<div className="flex grow flex-col bg-[repeating-linear-gradient(#D9D9D9,#D9D9D9_15px,#C2C2C2_15px,#C2C2C2_20px)]" />
) : (
<div
className={twMerge(
'flex grow flex-col bg-linear-to-b',
isChecked ? 'from-[#FFF7E3] to-[#FFE197]' : 'from-[#FFF4F2] to-[#FFE6E3]',
)}
>
<p className="body-r mt-auto mr-[1px] text-right">{letterCount}통</p>
</div>
)}
</div>
<div className="flex flex-col items-center">
<div className="h-[7px] w-23 bg-[#D7A877]" />
<div className="h-[3px] w-20 bg-[#A88156]" />
<div className="h-1 w-18 bg-[#917B63]" />
</div>
</article>
</Link>
);
};

export default LetterBoxItem;
42 changes: 41 additions & 1 deletion src/pages/LetterBox/index.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

창문의 갯수가 3의 배수가 아니거나, 창문갯수가 늘어나면 어떻게 처리 될까요?

Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
import DoorImg from '@/assets/images/door.png';
import PageTitle from '@/components/PageTitle';
import { chunkBox } from '@/utils/chunkBox';

import LetterBoxItem from './components/LetterBoxItem';

const DUMMY_COUNT = 200;

const LetterBoxPage = () => {
return <div>LetterBoxPage</div>;
return (
<main className="flex grow flex-col items-center px-5 pt-20">
<PageTitle>내 편지함</PageTitle>
<div className="w-full max-w-94">
<p className="body-sb mt-16 mb-[7px] place-self-start text-gray-50">
나와 연락한 사람들 {DUMMY_COUNT}
</p>
<section className="letter-box-bg flex grow flex-col items-center px-4 pt-5">
<div className="flex w-full flex-col gap-5">
{chunkBox(
Array.from({ length: 12 }).map((_, index) => (
<LetterBoxItem
key={index}
zipCode="12E12"
letterCount={90}
isChecked={index % 2 === 0}
/>
)),
).map((row, index) => (
<div key={index} className="flex justify-between">
{row}
</div>
))}
<div className="flex justify-between">
<LetterBoxItem zipCode="12E12" letterCount={90} isClosed />
<img src={DoorImg} alt="출입문 이미지" />
<LetterBoxItem zipCode="12E12" letterCount={90} isClosed />
</div>
</div>
</section>
</div>
</main>
);
};

export default LetterBoxPage;
Loading