Skip to content

Commit

Permalink
[편지] 편지 꾸미기 (#34)
Browse files Browse the repository at this point in the history
* Feat: 꾸미기 페이지 이동

* Feat: letter index 전역 상태로 저장

* Feat: react-to-image 라이브러리 설치

* Chore: 더미데이터 추가

* Feat: 기본 편지지 컴포넌트 추가

* Feat: 하단 버튼 추가

* Feat: 폰트 두께 편집 기능

* Feat: 폰트 크기 조정

* Feat: 날짜 표시하기

* Chore: 배경 이미지 데이터 추가

* Feat: 배경 이미지 설정
  • Loading branch information
yoouyeon authored Jul 26, 2024
1 parent d82536e commit b737f1d
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 7 deletions.
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@hugocxl/react-to-image": "^0.0.9",
"@tanstack/react-query": "^5.51.11",
"axios": "^1.7.2",
"framer-motion": "^11.3.8",
Expand Down
Binary file added public/bg1.jpg
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 public/bg2.jpg
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 public/bg3.jpg
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 public/bg4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 19 additions & 5 deletions src/app/letter/@create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ import { useSourceStore } from '../letter-source-store';
import { useLetterStore } from '../letter-store';
import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
import { useState, useRef } from 'react';
import { useLetterFlowStore } from '../letter-flow-store';

const Page = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef(null);
const { source } = useSourceStore();
const { letter } = useLetterStore();
const [currentLetterIdx, setCurrentLetterIdx] = useState(0);
const {
letter,
currentLetterIndex,
increaseCurrentLetterIndex,
decreaseCurrentLetterIndex,
} = useLetterStore();
const { setState } = useLetterFlowStore();
// TODO : 저장 개수 + 현재 불러올 수 있는 편지 개수 받아와야 함.
const DUMMYSAVECOUNT = 2;
const DUMMYLOADCOUNT = 3;
Expand All @@ -50,7 +56,11 @@ const Page = () => {
<Button>{`저장 ${DUMMYSAVECOUNT}/6`}</Button>
</HStack>
<HStack w={'100%'} justifyContent={'space-between'}>
<IconButton aria-label="이전 편지" icon={<ChevronLeftIcon />} />
<IconButton
onClick={decreaseCurrentLetterIndex}
aria-label="이전 편지"
icon={<ChevronLeftIcon />}
/>
<Editable
defaultValue={
'사전 질문의 답변으로 사용자 문체를 파악하고 아키네이터가 질문지 답변을 바탕으로 내용을 구성하여 편지 1을 보여줍니다. 그리고 사용자가 후속으로 직접 수정할 수 있으며 완성된 편지는 사용자 데이터로 남아 기존의 사용자 데이터 + 아카이빙 데이터 형식으로 누적됩니다.'
Expand All @@ -61,7 +71,11 @@ const Page = () => {
<EditablePreview />
<EditableTextarea h={'100%'} />
</Editable>
<IconButton aria-label="다음 편지" icon={<ChevronRightIcon />} />
<IconButton
onClick={increaseCurrentLetterIndex}
aria-label="다음 편지"
icon={<ChevronRightIcon />}
/>
</HStack>
<HStack>
<Text>From.</Text>
Expand All @@ -71,7 +85,7 @@ const Page = () => {
<Button
onClick={onOpen}
>{`새로운 편지 가져오기 (${DUMMYLOADCOUNT}/3)`}</Button>
<Button>편지 꾸미러 가기</Button>
<Button onClick={() => setState('decorate')}>편지 꾸미러 가기</Button>
</HStack>
</VStack>
<AlertDialog
Expand Down
8 changes: 8 additions & 0 deletions src/app/letter/@decorate/bgImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const bgImageData = {
bg1: '/bg1.jpg',
bg2: '/bg2.jpg',
bg3: '/bg3.jpg',
bg4: '/bg4.jpg',
};

export type bgImageType = keyof typeof bgImageData;
220 changes: 220 additions & 0 deletions src/app/letter/@decorate/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
'use client';

import {
Text,
Heading,
VStack,
Box,
Button,
HStack,
Drawer,
DrawerBody,
DrawerOverlay,
DrawerContent,
useDisclosure,
IconButton,
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
Select,
SimpleGrid,
Checkbox,
Input,
} from '@chakra-ui/react';
import { useLetterStore } from '../letter-store';
import { useRef, useState } from 'react';
import { useToPng } from '@hugocxl/react-to-image';
import { useLetterFlowStore } from '../letter-flow-store';
import { ArrowLeftIcon } from '@chakra-ui/icons';
import { bgImageData, bgImageType } from './bgImage';

const Page = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const openButtonRef = useRef(null);
const { setState } = useLetterFlowStore();
const { letter, currentLetterIndex } = useLetterStore();
const [imageSrc, setImageSrc] = useState<string | undefined>();
const [{ status, isLoading }, convertToImage, ref] = useToPng<HTMLDivElement>(
{
onStart: () => {
console.log('onStart');
},
onSuccess: (data) => {
setImageSrc(data);
},
onError: (error) => {
console.error(error);
},
}
);

const [fontWeight, setFontWeight] = useState('normal');
const [fontSize, setFontSize] = useState(16);
const [date, setDate] = useState(
new Date().toISOString().split('T')[0].split('-').join('-')
);
const [showDate, setShowDate] = useState(false);
const [bgImage, setBgImage] = useState<bgImageType>('bg1');

return (
<>
<VStack>
{/* 편지 이미지 컴포넌트 */}
<VStack
p={6}
spacing={4}
alignItems={'flex-start'}
w="1024px"
backgroundImage={bgImageData[bgImage]}
ref={ref}
>
<Heading mx={'auto'}>{letter[currentLetterIndex].title}</Heading>
<Text
fontWeight={'bold'}
>{`To. ${letter[currentLetterIndex].to}`}</Text>
<Box>
{letter[currentLetterIndex].body.split('\n').map((line, index) => (
<Text
key={index}
fontWeight={fontWeight}
fontSize={`${fontSize}px`}
>
{line}
</Text>
))}
</Box>
<HStack w={'100%'} justifyContent={'space-between'}>
<Text
fontWeight={'bold'}
>{`From. ${letter[currentLetterIndex].from}`}</Text>
{showDate && <Text fontWeight={'bold'}>{`Date. ${date}`}</Text>}
</HStack>
</VStack>
<HStack>
<Button onClick={() => setState('create')}>
<Text>편지 수정</Text>
</Button>
<Button>
<Text>편지 완성하기</Text>
</Button>
</HStack>
</VStack>
<IconButton
ref={openButtonRef}
onClick={onOpen}
aria-label={'open-drawer'}
icon={<ArrowLeftIcon />}
/>
<Drawer
isOpen={isOpen}
placement="right"
onClose={onClose}
finalFocusRef={openButtonRef}
>
<DrawerOverlay />
<DrawerContent>
<DrawerBody>
<VStack>
<VStack>
<Text>텍스트</Text>
<Select placeholder="폰트">
<option value="option1">option1</option>
<option value="option2">option2</option>
<option value="option3">option3</option>
</Select>
<NumberInput
defaultValue={16}
min={10}
max={20}
onChange={(_valueAsString, valueAsNumber) => {
setFontSize(valueAsNumber);
}}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<Select
placeholder="폰트 두께"
defaultValue={'normal'}
onChange={(select) => {
setFontWeight(select.target.value);
}}
>
<option value="hairline">hairline</option>
<option value="thin">thin</option>
<option value="light">light</option>
<option value="normal">normal</option>
<option value="medium">medium</option>
<option value="semibold">semibold</option>
<option value="bold">bold</option>
<option value="extrabold">extrabold</option>
<option value="black">black</option>
</Select>
</VStack>
<VStack>
<Text>텍스트</Text>
<Checkbox
isChecked={showDate}
onChange={() => {
setShowDate(!showDate);
}}
>
날짜 표시하기
</Checkbox>
<Input
placeholder="날짜"
type="date"
value={date}
onChange={(event) => {
setDate(event.target.value);
}}
/>
</VStack>
<VStack>
<Text>편지지</Text>
<SimpleGrid columns={3} spacing={5}>
<Button
onClick={() => setBgImage('bg1')}
colorScheme={bgImage === 'bg1' ? 'orange' : 'gray'}
>
편지지1
</Button>
<Button
onClick={() => setBgImage('bg2')}
colorScheme={bgImage === 'bg2' ? 'orange' : 'gray'}
>
편지지2
</Button>
<Button
onClick={() => setBgImage('bg3')}
colorScheme={bgImage === 'bg3' ? 'orange' : 'gray'}
>
편지지3
</Button>
<Button
onClick={() => setBgImage('bg4')}
colorScheme={bgImage === 'bg4' ? 'orange' : 'gray'}
>
편지지4
</Button>
<Button isDisabled>편지지5</Button>
<Button isDisabled>편지지6</Button>
<Button isDisabled>편지지7</Button>
<Button isDisabled>편지지8</Button>
<Button isDisabled>편지지9</Button>
</SimpleGrid>
</VStack>
</VStack>
</DrawerBody>
</DrawerContent>
</Drawer>
</>
);
};

export default Page;
7 changes: 6 additions & 1 deletion src/app/letter/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ type LayoutProps = {
children: ReactNode; // 기본 페이지
initial: ReactNode; // 초기 페이지
create: ReactNode; // 편지 쓰기 페이지
decorate: ReactNode; // 편지 꾸미기 페이지
};

const Layout = ({ children, initial, create }: LayoutProps) => {
const Layout = ({ children, initial, create, decorate }: LayoutProps) => {
const { state } = useLetterFlowStore();
if (state === 'initial') {
return <>{initial}</>;
}
if (state === 'create') {
return <>{create}</>;
}
if (state === 'decorate') {
return <>{decorate}</>;
}

return <>{children}</>;
};

Expand Down
26 changes: 25 additions & 1 deletion src/app/letter/letter-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,37 @@ export type LetterData = {

type LetterStore = {
letter: LetterData[];
currentLetterIndex: number;
addNewLetter: (letter: LetterData) => void;
decreaseCurrentLetterIndex: () => void;
increaseCurrentLetterIndex: () => void;
};

const defaultState: LetterData[] = [];
const defaultState: LetterData[] = [
{
title: '임시 저장',
to: '개발팀',
from: '기획팀',
body: '사전 질문의 답변으로 사용자 문체를 파악하고 아키네이터가 질문지 답변을 바탕으로 내용을 구성하여 편지 1을 보여줍니다. 그리고 사용자가 후속으로 직접 수정할 수 있으며 완성된 편지는 사용자 데이터로 남아 기존의 사용자 데이터 + 아카이빙 데이터 형식으로 누적됩니다.',
},
];
const defaultIndex = 0;

export const useLetterStore = create<LetterStore>((set) => ({
letter: defaultState,
currentLetterIndex: defaultIndex,
addNewLetter: (letter) =>
set((state) => ({ letter: [...state.letter, letter] })),
decreaseCurrentLetterIndex: () =>
set((state) => ({
currentLetterIndex:
state.currentLetterIndex - 1 > 0 ? state.currentLetterIndex - 1 : 0,
})),
increaseCurrentLetterIndex: () =>
set((state) => ({
currentLetterIndex:
state.currentLetterIndex + 1 < state.letter.length
? state.currentLetterIndex + 1
: state.letter.length - 1,
})),
}));

0 comments on commit b737f1d

Please sign in to comment.