diff --git a/frontend/src/apis/category.ts b/frontend/src/apis/category.ts
index bd290644c..4026ce329 100644
--- a/frontend/src/apis/category.ts
+++ b/frontend/src/apis/category.ts
@@ -4,7 +4,12 @@ import { AddCategoriesRequest, PatchCategoryArgs } from 'types/apis/category';
// POST: 카테고리 추가
export const addCategory = (body: AddCategoriesRequest) =>
- http.post(categoryURL, { body: JSON.stringify(body) });
+ http.post(categoryURL, {
+ body: JSON.stringify(body),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
// GET: 카테고리 목록 조회
export const getCategories = () => http.get(categoryURL);
@@ -17,6 +22,9 @@ export const getWritingsInCategory = (categoryId: number | null) =>
export const patchCategory = ({ categoryId, body }: PatchCategoryArgs) =>
http.patch(`${categoryURL}/${categoryId}`, {
body: JSON.stringify(body),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
});
// DELETE: 카테고리 삭제
diff --git a/frontend/src/apis/writings.ts b/frontend/src/apis/writings.ts
index 7bf8f8ee7..621428301 100644
--- a/frontend/src/apis/writings.ts
+++ b/frontend/src/apis/writings.ts
@@ -1,6 +1,10 @@
import { writingURL } from 'constants/apis/url';
import { http } from './fetch';
-import type { AddWritingRequest, PublishWritingArgs } from 'types/apis/writings';
+import type {
+ AddNotionWritingRequest,
+ AddWritingRequest,
+ PublishWritingArgs,
+} from 'types/apis/writings';
// 글 생성(글 업로드): POST
export const addWriting = (body: AddWritingRequest) =>
@@ -11,6 +15,15 @@ export const addWriting = (body: AddWritingRequest) =>
// },
});
+// 노션 - 글 생성(글 업로드): POST
+export const addNotionWriting = (body: AddNotionWritingRequest) =>
+ http.post(`${writingURL}/notion`, {
+ body: JSON.stringify(body),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
// 글 조회: GET
export const getWriting = (writingId: number) => http.get(`${writingURL}/${writingId}`);
diff --git a/frontend/src/assets/icons/import.svg b/frontend/src/assets/icons/import.svg
new file mode 100644
index 000000000..340720b4c
--- /dev/null
+++ b/frontend/src/assets/icons/import.svg
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/assets/icons/index.ts b/frontend/src/assets/icons/index.ts
index 01151a7f9..26cb1994f 100644
--- a/frontend/src/assets/icons/index.ts
+++ b/frontend/src/assets/icons/index.ts
@@ -8,7 +8,7 @@ export { ReactComponent as TagIcon } from './tag.svg';
export { ReactComponent as LeftArrowHeadIcon } from './left-arrow-head.svg';
export { ReactComponent as TistoryLogoIcon } from './tistory-logo.svg';
export { ReactComponent as MediumLogoIcon } from './medium-logo.svg';
-export { ReactComponent as CloseRounded } from './close-rounded.svg';
+export { ReactComponent as CloseIcon } from './close-rounded.svg';
export { ReactComponent as SidebarLeftIcon } from './sidebar-left.svg';
export { ReactComponent as SidebarRightIcon } from './sidebar-right.svg';
export { ReactComponent as SettingIcon } from './setting.svg';
@@ -18,3 +18,4 @@ export { ReactComponent as ArrowRightIcon } from './arrow-right.svg';
export { ReactComponent as DeleteIcon } from './delete.svg';
export { ReactComponent as PencilIcon } from './pencil.svg';
export { ReactComponent as WritingIcon } from './writing.svg';
+export { ReactComponent as ImportIcon } from './import.svg';
diff --git a/frontend/src/components/@common/FileUploader/FileUploader.stories.tsx b/frontend/src/components/@common/FileUploader/FileUploader.stories.tsx
new file mode 100644
index 000000000..6fb428fcb
--- /dev/null
+++ b/frontend/src/components/@common/FileUploader/FileUploader.stories.tsx
@@ -0,0 +1,20 @@
+import type { Meta, StoryObj } from '@storybook/react';
+import FileUploader from './FileUploader';
+
+const meta: Meta = {
+ title: 'common/FileUploader',
+ component: FileUploader,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Playground: Story = {
+ render: () => {
+ const onFileSelect = (file: FormData | null) => {
+ if (file) alert('파일이 선택되었습니다!');
+ };
+ return ;
+ },
+};
diff --git a/frontend/src/components/@common/FileUploader/FileUploader.tsx b/frontend/src/components/@common/FileUploader/FileUploader.tsx
new file mode 100644
index 000000000..3aa5f593b
--- /dev/null
+++ b/frontend/src/components/@common/FileUploader/FileUploader.tsx
@@ -0,0 +1,77 @@
+import { InputHTMLAttributes, useEffect } from 'react';
+import { css, styled } from 'styled-components';
+import { useFileUpload } from 'hooks/useFileUpload';
+import { useFileDragAndDrop } from 'hooks/@common/useFileDragAndDrop';
+import { ImportIcon } from 'assets/icons';
+
+type Props = {
+ accept?: InputHTMLAttributes['accept'];
+ width?: string;
+ height?: string;
+ onFileSelect: (file: FormData | null) => void;
+};
+
+const FileUploader = ({ accept = '*', width = '30rem', height = '10rem', onFileSelect }: Props) => {
+ const { onFileChange, openFinder, selectedFile } = useFileUpload(accept);
+ const { dragRef, isDragging } = useFileDragAndDrop({ onFileChange });
+
+ useEffect(() => {
+ onFileSelect(selectedFile);
+ }, [selectedFile]);
+
+ return (
+
+ );
+};
+
+export default FileUploader;
+
+const S = {
+ Description: styled.div<{ $isDragging: boolean; $width: string; $height: string }>`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 1rem;
+ ${({ $width, $height }) => {
+ return css`
+ width: ${$width};
+ height: ${$height};
+ `;
+ }};
+ border: 2px dashed ${({ theme }) => theme.color.gray6};
+ background-color: ${({ theme }) => theme.color.gray4};
+ font-size: 1.3rem;
+ color: ${({ theme }) => theme.color.gray7};
+ transition: all 0.2s ease-in-out;
+
+ ${({ $isDragging, theme }) => {
+ return (
+ $isDragging &&
+ css`
+ border: 2px dashed ${theme.color.primary};
+ background-color: ${theme.color.gray5};
+ `
+ );
+ }}
+ &:hover {
+ background-color: ${({ theme }) => theme.color.gray5};
+ }
+ `,
+ SpinnerWrapper: styled.div<{ $width: string; $height: string }>`
+ ${({ $width, $height }) => {
+ return css`
+ width: ${$width};
+ height: ${$height};
+ `;
+ }};
+
+ display: flex;
+ justify-content: center;
+ `,
+};
diff --git a/frontend/src/components/@common/Input/Input.tsx b/frontend/src/components/@common/Input/Input.tsx
index 65d0547eb..6a320dbf3 100644
--- a/frontend/src/components/@common/Input/Input.tsx
+++ b/frontend/src/components/@common/Input/Input.tsx
@@ -146,6 +146,10 @@ const S = {
${({ $size }) => genSizeStyle($size)};
${({ $variant, $isError }) => genVariantStyle($variant, $isError)};
+
+ &::placeholder {
+ color: ${({ theme }) => theme.color.gray6};
+ }
`,
SupportingText: styled.p<{ $isError: boolean | undefined }>`
color: ${({ $isError, theme }) => ($isError ? theme.color.red6 : theme.color.gray7)};
diff --git a/frontend/src/components/@common/Modal/Modal.stories.tsx b/frontend/src/components/@common/Modal/Modal.stories.tsx
new file mode 100644
index 000000000..bc8fc772b
--- /dev/null
+++ b/frontend/src/components/@common/Modal/Modal.stories.tsx
@@ -0,0 +1,56 @@
+import { Meta, StoryObj } from '@storybook/react';
+import Modal from './Modal';
+import Button from 'components/@common/Button/Button';
+import { useModal } from 'hooks/@common/useModal';
+import { styled } from 'styled-components';
+
+const meta = {
+ title: 'common/Modal',
+ component: Modal,
+ argTypes: {
+ children: {
+ control: false,
+ },
+ isOpen: {
+ control: false,
+ },
+ },
+ args: {
+ isOpen: false,
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Playground: Story = {
+ render: ({ ...rest }) => {
+ const { isOpen, openModal, closeModal } = useModal();
+ return (
+ <>
+
+
+
+ 모달
+ 내용을 마음껏 써주세요.
+
+
+ >
+ );
+ },
+};
+
+const ModalContent = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ height: 30rem;
+ p {
+ margin-top: 13rem;
+ font-size: 1.3rem;
+ }
+`;
diff --git a/frontend/src/components/@common/Modal/Modal.tsx b/frontend/src/components/@common/Modal/Modal.tsx
new file mode 100644
index 000000000..e2a8fb3e0
--- /dev/null
+++ b/frontend/src/components/@common/Modal/Modal.tsx
@@ -0,0 +1,81 @@
+import { useCallback, useEffect } from 'react';
+import { ComponentPropsWithoutRef } from 'react';
+import { createPortal } from 'react-dom';
+import { styled } from 'styled-components';
+import { CloseIcon } from 'assets/icons';
+
+type Props = {
+ isOpen: boolean;
+ closeModal: () => void;
+} & ComponentPropsWithoutRef<'dialog'>;
+
+const Modal = ({ isOpen = true, closeModal, children, ...rest }: Props) => {
+ const onKeyDownEscape = useCallback(
+ (event: KeyboardEvent) => {
+ if (event.key !== 'Escape') return;
+ closeModal();
+ },
+ [closeModal],
+ );
+
+ useEffect(() => {
+ if (isOpen) {
+ window.addEventListener('keydown', onKeyDownEscape);
+ document.body.style.overflow = 'hidden';
+ }
+
+ return () => {
+ window.removeEventListener('keydown', onKeyDownEscape);
+ document.body.style.overflow = 'auto';
+ };
+ }, [isOpen, onKeyDownEscape]);
+
+ return createPortal(
+
+ {isOpen && (
+ <>
+
+
+
+
+
+ {children}
+
+ >
+ )}
+ ,
+ document.body,
+ );
+};
+
+export default Modal;
+
+const S = {
+ ModalWrapper: styled.div`
+ position: relative;
+ z-index: 9999;
+ `,
+ Backdrop: styled.div`
+ position: fixed;
+ inset: 0;
+ background: ${({ theme }) => theme.color.modalBackdrop};
+ `,
+ Content: styled.dialog`
+ position: fixed;
+ inset: 50% auto auto 50%;
+ display: flex;
+ justify-content: center;
+ min-width: 20vw;
+ max-height: 80vh;
+ overflow: auto;
+ padding: 2.5rem;
+ border: none;
+ border-radius: 8px;
+ background-color: ${({ theme }) => theme.color.gray1};
+ transform: translate(-50%, -50%);
+ `,
+ CloseButton: styled.button`
+ position: absolute;
+ inset: 2.5rem 2.5rem auto auto;
+ `,
+};
diff --git a/frontend/src/components/@common/Tag/Tag.tsx b/frontend/src/components/@common/Tag/Tag.tsx
index b9983b843..6e1ca8e36 100644
--- a/frontend/src/components/@common/Tag/Tag.tsx
+++ b/frontend/src/components/@common/Tag/Tag.tsx
@@ -1,6 +1,6 @@
-import { CloseRounded } from 'assets/icons';
-import { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
+import { ComponentPropsWithoutRef } from 'react';
import { styled } from 'styled-components';
+import { CloseIcon } from 'assets/icons';
type Props = {
removable?: boolean;
@@ -10,7 +10,7 @@ const Tag = ({ removable = true, children, ...rest }: Props) => {
return (
#{children}
- {removable && }
+ {removable && }
);
};
diff --git a/frontend/src/components/FileUploadModal/FileUploadModal.tsx b/frontend/src/components/FileUploadModal/FileUploadModal.tsx
new file mode 100644
index 000000000..e69e21ff9
--- /dev/null
+++ b/frontend/src/components/FileUploadModal/FileUploadModal.tsx
@@ -0,0 +1,88 @@
+import Button from 'components/@common/Button/Button';
+import FileUploader from 'components/@common/FileUploader/FileUploader';
+import Modal from 'components/@common/Modal/Modal';
+import Spinner from 'components/@common/Spinner/Spinner';
+import { styled } from 'styled-components';
+import { useFileUploadModal } from './useFileUploadModal';
+import Input from 'components/@common/Input/Input';
+
+type Props = {
+ isOpen: boolean;
+ closeModal: () => void;
+};
+
+const FileUploadModal = ({ isOpen, closeModal }: Props) => {
+ const { isLoading, inputValue, uploadOnServer, setNotionPageLink, uploadNotionWriting } =
+ useFileUploadModal({ closeModal });
+
+ return (
+
+
+ 글 가져오기
+ {isLoading ? (
+ <>
+ 글 가져오는 중...
+
+ >
+ ) : (
+
+
+ 내 컴퓨터에서 가져오기
+
+
+
+ 노션에서 가져오기
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default FileUploadModal;
+
+const S = {
+ Container: styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 3rem;
+ width: 50vw;
+ max-width: 40rem;
+ `,
+ Title: styled.h1`
+ font-size: 2rem;
+ font-weight: 700;
+ `,
+ Content: styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 7rem;
+ height: 100%;
+ margin: 2rem 0;
+ font-size: 1.3rem;
+ `,
+ Item: styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ width: 100%;
+ `,
+ ItemTitle: styled.h2`
+ font-size: 1.4rem;
+ font-weight: 500;
+ `,
+};
diff --git a/frontend/src/components/FileUploadModal/useFileUploadModal.ts b/frontend/src/components/FileUploadModal/useFileUploadModal.ts
new file mode 100644
index 000000000..571a813b3
--- /dev/null
+++ b/frontend/src/components/FileUploadModal/useFileUploadModal.ts
@@ -0,0 +1,61 @@
+import { addNotionWriting, addWriting } from 'apis/writings';
+import useMutation from 'hooks/@common/useMutation';
+import { usePageNavigate } from 'hooks/usePageNavigate';
+import { ChangeEventHandler, useState } from 'react';
+import { AddWritingRequest } from 'types/apis/writings';
+
+type Args = {
+ closeModal: () => void;
+};
+
+export const useFileUploadModal = ({ closeModal }: Args) => {
+ const [inputValue, setInputValue] = useState('');
+ const { goWritingPage } = usePageNavigate();
+
+ const { mutateQuery: uploadNotion, isLoading: isNotionUploadLoading } = useMutation({
+ fetcher: addNotionWriting,
+ onSuccess: (data) => onFileUploadSuccess(data.headers),
+ });
+ const { mutateQuery: uploadFile, isLoading: isFileUploadLoading } = useMutation<
+ AddWritingRequest,
+ null
+ >({
+ fetcher: addWriting,
+ onSuccess: (data) => onFileUploadSuccess(data.headers),
+ });
+
+ const onFileUploadSuccess = (headers: Headers) => {
+ const writingId = headers.get('Location')?.split('/').pop();
+ goWritingPage(Number(writingId));
+ closeModal();
+ };
+
+ const uploadOnServer = async (selectedFile: FormData | null) => {
+ if (!selectedFile) return;
+
+ selectedFile.append('categoryId', JSON.stringify(1));
+
+ await uploadFile(selectedFile);
+ };
+
+ const setNotionPageLink: ChangeEventHandler = (e) => {
+ setInputValue(e.target.value);
+ };
+
+ const uploadNotionWriting = async () => {
+ const blockId = inputValue.split('/')?.pop()?.split('?')?.shift()?.split('-').pop();
+
+ if (!blockId) return;
+
+ setInputValue('');
+
+ await uploadNotion({
+ blockId: blockId,
+ categoryId: 1,
+ });
+ };
+
+ const isLoading = isNotionUploadLoading || isFileUploadLoading;
+
+ return { isLoading, inputValue, uploadOnServer, setNotionPageLink, uploadNotionWriting };
+};
diff --git a/frontend/src/hooks/@common/useFileDragAndDrop.ts b/frontend/src/hooks/@common/useFileDragAndDrop.ts
new file mode 100644
index 000000000..f5b4106f6
--- /dev/null
+++ b/frontend/src/hooks/@common/useFileDragAndDrop.ts
@@ -0,0 +1,63 @@
+import { useState, useEffect, useCallback, useRef } from 'react';
+
+type Args = {
+ onFileChange: (e: React.DragEvent | Event) => void;
+};
+
+export const useFileDragAndDrop = ({ onFileChange }: Args) => {
+ const [isDragging, setIsDragging] = useState(false);
+ const dragRef = useRef(null);
+
+ const onDragEnter = (e: DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsDragging(true);
+ };
+
+ const onDragLeave = (e: DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsDragging(false);
+ };
+
+ const onDragOver = (e: DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (e.dataTransfer?.files) {
+ setIsDragging(true);
+ }
+ };
+
+ const onDrop = (e: DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ onFileChange(e);
+ setIsDragging(false);
+ };
+
+ const initDragEvents = useCallback((): void => {
+ if (dragRef.current) {
+ dragRef.current.addEventListener('dragenter', onDragEnter);
+ dragRef.current.addEventListener('dragleave', onDragLeave);
+ dragRef.current.addEventListener('dragover', onDragOver);
+ dragRef.current.addEventListener('drop', onDrop);
+ }
+ }, [onDragEnter, onDragLeave, onDragOver, onDrop]);
+
+ const resetDragEvents = useCallback((): void => {
+ if (dragRef.current) {
+ dragRef.current.removeEventListener('dragenter', onDragEnter);
+ dragRef.current.removeEventListener('dragleave', onDragLeave);
+ dragRef.current.removeEventListener('dragover', onDragOver);
+ dragRef.current.removeEventListener('drop', onDrop);
+ }
+ }, [onDragEnter, onDragLeave, onDragOver, onDrop]);
+
+ useEffect(() => {
+ initDragEvents();
+
+ return () => resetDragEvents();
+ }, [initDragEvents, resetDragEvents]);
+
+ return { dragRef, isDragging };
+};
diff --git a/frontend/src/hooks/@common/useModal.ts b/frontend/src/hooks/@common/useModal.ts
new file mode 100644
index 000000000..fb3a5688b
--- /dev/null
+++ b/frontend/src/hooks/@common/useModal.ts
@@ -0,0 +1,19 @@
+import { useState } from 'react';
+
+export const useModal = () => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const openModal = () => {
+ setIsOpen(true);
+ };
+
+ const closeModal = () => {
+ setIsOpen(false);
+ };
+
+ return {
+ isOpen,
+ openModal,
+ closeModal,
+ };
+};
diff --git a/frontend/src/hooks/useFileUpload.ts b/frontend/src/hooks/useFileUpload.ts
index ae2b3deb0..8e263dfc7 100644
--- a/frontend/src/hooks/useFileUpload.ts
+++ b/frontend/src/hooks/useFileUpload.ts
@@ -1,23 +1,11 @@
-import { InputHTMLAttributes, useEffect, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
-import useMutation from './@common/useMutation';
-import { addWriting } from 'apis/writings';
-import { AddWritingRequest } from 'types/apis/writings';
+import { InputHTMLAttributes, useState } from 'react';
export const useFileUpload = (accept: InputHTMLAttributes['accept'] = '*') => {
const [selectedFile, setSelectedFile] = useState(null);
- const { mutateQuery } = useMutation({
- fetcher: (body) => addWriting(body),
- onSuccess: (data) => {
- const writingId = data.headers.get('Location')?.split('/').pop();
- navigate(`/writing/${writingId}`);
- },
- });
-
- const navigate = useNavigate();
-
- const onFileChange = (e: Event) => {
- const target = e.target as HTMLInputElement;
+
+ const onFileChange = (e: React.DragEvent | Event) => {
+ const target = 'dataTransfer' in e ? e.dataTransfer : (e.target as HTMLInputElement);
+
if (!target.files) return;
const newFile = target.files[0];
@@ -28,12 +16,6 @@ export const useFileUpload = (accept: InputHTMLAttributes['acc
setSelectedFile(formData);
};
- const uploadOnServer = async (selectedFile: FormData | null) => {
- if (!selectedFile) return;
-
- await mutateQuery(selectedFile);
- };
-
const openFinder = () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
@@ -42,9 +24,5 @@ export const useFileUpload = (accept: InputHTMLAttributes['acc
fileInput.click();
};
- useEffect(() => {
- uploadOnServer(selectedFile);
- }, [selectedFile]);
-
- return { openFinder };
+ return { selectedFile, openFinder, onFileChange };
};
diff --git a/frontend/src/mocks/handlers/writing.ts b/frontend/src/mocks/handlers/writing.ts
index 041dd5706..dedd95800 100644
--- a/frontend/src/mocks/handlers/writing.ts
+++ b/frontend/src/mocks/handlers/writing.ts
@@ -57,7 +57,12 @@ export const writingHandlers = [
// 글 생성(글 업로드): POST
rest.post(`${writingURL}/file`, async (_, res, ctx) => {
- return res(ctx.status(201), ctx.set('Location', `/writings/200`));
+ return res(ctx.delay(3000), ctx.status(201), ctx.set('Location', `/writings/200`));
+ }),
+
+ // 글 생성(글 업로드): POST
+ rest.post(`${writingURL}/notion`, async (_, res, ctx) => {
+ return res(ctx.delay(3000), ctx.status(201), ctx.set('Location', `/writings/200`));
}),
// 글 블로그로 발행: POST
diff --git a/frontend/src/pages/Layout/Layout.tsx b/frontend/src/pages/Layout/Layout.tsx
index acb2fb335..2f06d5a5f 100644
--- a/frontend/src/pages/Layout/Layout.tsx
+++ b/frontend/src/pages/Layout/Layout.tsx
@@ -3,13 +3,13 @@ import { Outlet, useOutletContext } from 'react-router-dom';
import { styled } from 'styled-components';
import { PlusCircleIcon } from 'assets/icons';
import Button from 'components/@common/Button/Button';
-import { useFileUpload } from 'hooks/useFileUpload';
-
import { HEADER_STYLE, LAYOUT_STYLE, sidebarStyle } from 'styles/layoutStyle';
import Header from 'components/Header/Header';
import { usePageNavigate } from 'hooks/usePageNavigate';
import WritingSideBar from 'components/WritingSideBar/WritingSideBar';
import CategorySection from 'components/Category/CategorySection/CategorySection';
+import { useModal } from 'hooks/@common/useModal';
+import FileUploadModal from 'components/FileUploadModal/FileUploadModal';
export type PageContext = {
isLeftSidebarOpen?: boolean;
@@ -21,7 +21,7 @@ const Layout = () => {
const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true);
const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(true);
const [activeWritingId, setActiveWritingId] = useState(null);
- const { openFinder } = useFileUpload('.md');
+ const { isOpen, openModal, closeModal } = useModal();
const isWritingViewerActive = activeWritingId !== null;
const toggleLeftSidebar = () => {
@@ -47,11 +47,11 @@ const Layout = () => {
icon={}
block={true}
align='left'
- onClick={openFinder}
+ onClick={openModal}
>
Add Post
-
+
diff --git a/frontend/src/styles/layoutStyle.ts b/frontend/src/styles/layoutStyle.ts
index 8125b6426..6e88080fc 100644
--- a/frontend/src/styles/layoutStyle.ts
+++ b/frontend/src/styles/layoutStyle.ts
@@ -2,11 +2,11 @@ import { RuleSet, css } from 'styled-components';
import { theme } from './theme';
export const HEADER_STYLE = {
- height: '3rem',
+ height: '4rem',
} as const;
export const LAYOUT_STYLE = {
- padding: '1rem',
+ padding: '0 1rem 1rem 1rem',
border: `2px solid ${theme.color.gray13}`,
gap: '0.4rem',
} as const;
diff --git a/frontend/src/styles/theme.ts b/frontend/src/styles/theme.ts
index b9ffa1396..0f1c04c95 100644
--- a/frontend/src/styles/theme.ts
+++ b/frontend/src/styles/theme.ts
@@ -5,6 +5,8 @@ const color = {
tistory: '#FF5A4A',
medium: '#000000',
+ modalBackdrop: '#00000059',
+
red1: '#fff1f0',
red2: '#ffccc7',
red3: '#ffa39e',
diff --git a/frontend/src/types/apis/writings.ts b/frontend/src/types/apis/writings.ts
index 96186358a..08c330600 100644
--- a/frontend/src/types/apis/writings.ts
+++ b/frontend/src/types/apis/writings.ts
@@ -2,6 +2,11 @@ import { Blog, PublishingPropertyData } from 'types/domain';
export type AddWritingRequest = FormData;
+export type AddNotionWritingRequest = {
+ blockId: string;
+ categoryId: number;
+};
+
export type GetWritingResponse = {
id: number;
title: string;