diff --git a/src/app/(main)/activity/[semesterId]/[activityId]/boards/[boardId]/create-post/_components/form/Form.tsx b/src/app/(main)/activity/[semesterId]/[activityId]/boards/[boardId]/create-post/_components/form/Form.tsx index f85196be..c6a23f05 100644 --- a/src/app/(main)/activity/[semesterId]/[activityId]/boards/[boardId]/create-post/_components/form/Form.tsx +++ b/src/app/(main)/activity/[semesterId]/[activityId]/boards/[boardId]/create-post/_components/form/Form.tsx @@ -1,12 +1,14 @@ 'use client' +import { useRef } from 'react' import { useForm } from 'react-hook-form' +import { Block } from '@blocknote/core' import { zodResolver } from '@hookform/resolvers/zod' import { useMutation } from '@tanstack/react-query' +import dynamic from 'next/dynamic' import { usePathname, useRouter } from 'next/navigation' -import { PostContentFieldEditor } from '@/components/feature/post/create-post-form/editor' import { Button, Form, @@ -17,6 +19,7 @@ import { Input, Label, Separator, + Skeleton, useToast, } from '@/components/ui' import { queryClient } from '@/lib/query-client' @@ -25,6 +28,14 @@ import { activityPostQuries, addActivityPostApi } from '@/service/api' import { ActivityDateFieldDialog } from './date-field-dialog' +const PostContentFieldEditor = dynamic( + () => import('@/components/feature/post/post-editor/EditorField'), + { + ssr: false, + loading: () => , + }, +) + interface CreateActivityPostFormProps { boardId: number } @@ -52,6 +63,31 @@ export const CreateActivityPostForm = ({ }, }) + const imageMapRef = useRef>(new Map()) + + const addImageId = (url: string, id: number) => { + imageMapRef.current.set(url, id) + } + + const onSubmit = (values: CreateActivityPost) => { + const imageIds: number[] = [] + + JSON.parse(values.postContent) + .filter((block: Block) => block.type === 'image') + .forEach((block: Block) => { + if (block.type === 'image') { + const url = block.props.url.split('/').pop() ?? '' + const imageId = imageMapRef.current.get(url) + + if (imageId) imageIds.push(imageId) + } + }) + + form.setValue('postImageIds', imageIds) + + addActivityPost({ boardId, data: values }) + } + const onSuccess = (message?: string) => { toast({ title: message, @@ -69,9 +105,7 @@ export const CreateActivityPostForm = ({ return (
- addActivityPost({ boardId, data: values }), - )} + onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4" >
게시글 내용 작성하기
- + addImageId(url, id)} + />
+
+ + ) } diff --git a/src/components/feature/post/create-post-form/CreatePostForm.tsx b/src/components/feature/post/create-post-form/CreatePostForm.tsx deleted file mode 100644 index beb756f7..00000000 --- a/src/components/feature/post/create-post-form/CreatePostForm.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { - ControllerRenderProps, - FieldValues, - UseFormReturn, -} from 'react-hook-form' - -import dynamic from 'next/dynamic' - -import { ImageInput, PostFormField } from '@/components/feature' -import { Button, Form, Input, Separator, Skeleton } from '@/components/ui' - -const PostContentFieldEditor = dynamic(() => import('./editor/EditorField'), { - ssr: false, - loading: () => , -}) - -type CreatePostFormProps = { - form: UseFormReturn - onSubmit: (values: T) => void - isExecuting: boolean - isImageRequired?: boolean -} - -export const CreatePostForm = ({ - form, - onSubmit, - isExecuting, - isImageRequired = true, -}: CreatePostFormProps) => { - return ( -
- - - {(field: ControllerRenderProps) => } - - -
게시글 내용 작성하기
- - {isImageRequired && ( -
- - - {(field) => } - -
- )} -
- -
- - - ) -} diff --git a/src/components/feature/post/create-post-form/index.ts b/src/components/feature/post/create-post-form/index.ts deleted file mode 100644 index 223cc79f..00000000 --- a/src/components/feature/post/create-post-form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { CreatePostForm } from './CreatePostForm' diff --git a/src/components/feature/post/form-field/index.ts b/src/components/feature/post/form-field/index.ts deleted file mode 100644 index 55da74e9..00000000 --- a/src/components/feature/post/form-field/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PostFormField } from './FormField' diff --git a/src/components/feature/post/index.ts b/src/components/feature/post/index.ts index 4ec20f9e..c57c9b72 100644 --- a/src/components/feature/post/index.ts +++ b/src/components/feature/post/index.ts @@ -1,5 +1,4 @@ -export * from './create-post-form' -export * from './form-field' +export * from './post-editor' export * from './table' export * from './content' export * from './image-input' diff --git a/src/components/feature/post/create-post-form/editor/EditorField.tsx b/src/components/feature/post/post-editor/EditorField.tsx similarity index 51% rename from src/components/feature/post/create-post-form/editor/EditorField.tsx rename to src/components/feature/post/post-editor/EditorField.tsx index 5b14d5ae..820217b2 100644 --- a/src/components/feature/post/create-post-form/editor/EditorField.tsx +++ b/src/components/feature/post/post-editor/EditorField.tsx @@ -5,14 +5,39 @@ import { useFormContext } from 'react-hook-form' import { BlockNoteView } from '@blocknote/mantine' import '@blocknote/mantine/style.css' import { useCreateBlockNote } from '@blocknote/react' +import { useMutation } from '@tanstack/react-query' import { FormField, FormItem, FormMessage } from '@/components/ui' import { CreateActivityPost } from '@/schema/post' +import { uploadPostImageApi } from '@/service/api/post/image-upload' +import { BASE_URL } from '@/service/config/instance' -const PostContentFieldEditor = () => { +interface PostContentFieldEditorProps { + addImageId: (url: string, id: number) => void +} + +const PostContentFieldEditor = ({ + addImageId, +}: PostContentFieldEditorProps) => { + const { mutateAsync: uploadPostImage } = useMutation({ + mutationFn: uploadPostImageApi, + }) const { control } = useFormContext() - const editor = useCreateBlockNote() + const uploadFile = async (file: File): Promise => { + const data = await uploadPostImage({ data: { file } }) + + const url = data.postImageUrl.split('/').pop() ?? '' + addImageId(url, data.postImageId) + + const imageUrl = data.postImageUrl.replace('/upload', `${BASE_URL}/upload`) + + return imageUrl + } + + const editor = useCreateBlockNote({ + uploadFile, + }) return ( { + const postImageClient = new PostImages(AUTHORIZATION_API) + const response = await postImageClient.registerPostImage(data) + + return response.data +} diff --git a/src/service/models/PostImages.ts b/src/service/models/PostImages.ts new file mode 100644 index 00000000..42936260 --- /dev/null +++ b/src/service/models/PostImages.ts @@ -0,0 +1,47 @@ +/* eslint-disable */ + +/* tslint:disable */ + +/* + * --------------------------------------------------------------- + * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## + * ## ## + * ## AUTHOR: acacode ## + * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## + * --------------------------------------------------------------- + */ +import { CustomHttpClient } from '../config' +import { + RegisterPostImageData, + RegisterPostImagePayload, +} from './data-contracts' +import { ContentType, RequestParams } from './http-client' + +export class PostImages< + SecurityDataType = unknown, +> extends CustomHttpClient { + /** + * No description + * + * @tags 게시글 API + * @name RegisterPostImage + * @summary 게시글 이미지 등록 + * @request POST:/post-images + * @secure + * @response `200` `RegisterPostImageData` OK + * @response `400` `void` + * @response `401` `void` + */ + registerPostImage = ( + data: RegisterPostImagePayload, + params: RequestParams = {}, + ) => + this.request({ + path: `/post-images`, + method: 'POST', + body: data, + secure: true, + type: ContentType.FormData, + ...params, + }) +} diff --git a/src/service/models/data-contracts.ts b/src/service/models/data-contracts.ts index 1e499f27..d80a9e33 100644 --- a/src/service/models/data-contracts.ts +++ b/src/service/models/data-contracts.ts @@ -711,6 +711,13 @@ export interface UpdateBoardImagePayload { file: File } +export interface RegisterPostImagePayload { + /** @format binary */ + file: File +} + +export type RegisterPostImageData = PostImageResponseDto + export type GetPostWithBoardData = PostWithBoardResponseDto export type GetNoticePostData = BasePostResponseDto @@ -842,6 +849,16 @@ export interface GetAdminUsersRequest { active: boolean } +export interface PostImageResponseDto { + /** + * 게시글 이미지 id + * @format int64 + */ + postImageId: number + /** 게시글 이미지 파일 Url */ + postImageUrl: string +} + export interface LoginRequest { data: LoginRequestDto } @@ -928,3 +945,7 @@ export interface GetUserRequest { export interface GetNoticePostDetailRequest { postId: number } + +export interface UploadPostImageRequest { + data: RegisterPostImagePayload +}