From 338a9d274e7addb0b9e3f3a437e46994cf9a08a5 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 04:13:27 +0900 Subject: [PATCH 01/30] =?UTF-8?q?fix(apps/web):=20useScroll=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[agentId]/[postGroupId]/pageStyle.css.ts | 26 +++++++++++++++---- apps/web/src/app/create/pageStyle.css.ts | 2 +- apps/web/src/hooks/useScroll.ts | 11 +++----- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/pageStyle.css.ts b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/pageStyle.css.ts index 527a8d6..4926004 100644 --- a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/pageStyle.css.ts +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/pageStyle.css.ts @@ -2,13 +2,14 @@ import { style } from '@vanilla-extract/css'; import { vars } from '@repo/theme'; export const mainStyle = style({ - width: '100%', - height: '100vh', - background: - 'linear-gradient(174deg, rgba(255, 255, 255, 0.55) -11.84%, rgba(243, 244, 249, 0.55) 29.91%, rgba(231, 232, 251, 0.55) 100%), #FFF', + maxWidth: '100%', + minHeight: '100vh', display: 'flex', justifyContent: 'center', - overflow: 'auto', + paddingTop: '8rem', + overflowY: 'auto', + background: + 'linear-gradient(174deg, rgba(255, 255, 255, 0.55) -11.84%, rgba(243, 244, 249, 0.55) 29.91%, rgba(231, 232, 251, 0.55) 100%), #FFF', }); export const contentStyle = style({ @@ -40,3 +41,18 @@ export const accordionItemStyle = style({ width: '51.2rem', flex: '0 0 auto', }); + +export const accordionContentStyle = style({ + display: 'flex', + flexDirection: 'column', + gap: vars.space[10], +}); + +export const contentInnerWrapper = style({ + height: '100%', +}); + +export const buttonWrapperStyle = style({ + display: 'flex', + justifyContent: 'flex-end', +}); diff --git a/apps/web/src/app/create/pageStyle.css.ts b/apps/web/src/app/create/pageStyle.css.ts index 0329406..2c6e38b 100644 --- a/apps/web/src/app/create/pageStyle.css.ts +++ b/apps/web/src/app/create/pageStyle.css.ts @@ -3,7 +3,7 @@ import { vars } from '@repo/theme'; export const mainStyle = style({ maxWidth: '100%', - height: '100vh', + minHeight: '100vh', margin: '0 auto', background: 'radial-gradient(100% 100% at 51.8% 0%, #D7DAFF 0%, #FFF 79.28%)', overflow: 'auto', diff --git a/apps/web/src/hooks/useScroll.ts b/apps/web/src/hooks/useScroll.ts index c0655f1..9fdb477 100644 --- a/apps/web/src/hooks/useScroll.ts +++ b/apps/web/src/hooks/useScroll.ts @@ -13,24 +13,19 @@ export function useScroll({ const elementRef = useRef(null); useEffect(() => { - const targetElement: HTMLElement | Window = elementRef.current ?? window; - const handleScroll = () => { requestAnimationFrame(() => { - const scrollTop = - targetElement instanceof HTMLElement - ? targetElement.scrollTop - : window.scrollY || document.documentElement.scrollTop; + const scrollTop = window.scrollY || document.documentElement.scrollTop; const scrolled = scrollTop > threshold; setIsScrolled((prev) => (prev !== scrolled ? scrolled : prev)); }); }; - targetElement.addEventListener('scroll', handleScroll, { passive: true }); + window.addEventListener('scroll', handleScroll, { passive: true }); handleScroll(); return () => { - targetElement.removeEventListener('scroll', handleScroll); + window.removeEventListener('scroll', handleScroll); }; }, [threshold]); From ae914715d2b4cc22c0e88c70b86175c0e6f58541 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:04:06 +0900 Subject: [PATCH 02/30] =?UTF-8?q?feat(packages/ui):=20TextField.Submit=20d?= =?UTF-8?q?isabled=20=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/src/components/TextField/TextField.css.ts | 5 +++++ .../ui/src/components/TextField/TextFieldSubmit.tsx | 11 ++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/TextField/TextField.css.ts b/packages/ui/src/components/TextField/TextField.css.ts index eb491c8..f267b9a 100644 --- a/packages/ui/src/components/TextField/TextField.css.ts +++ b/packages/ui/src/components/TextField/TextField.css.ts @@ -105,6 +105,11 @@ export const submitButtonStyle = recipe({ cursor: 'not-allowed', }, }, + isDisabled: { + true: { + cursor: 'not-allowed', + }, + }, }, }); diff --git a/packages/ui/src/components/TextField/TextFieldSubmit.tsx b/packages/ui/src/components/TextField/TextFieldSubmit.tsx index 0399104..c44731b 100644 --- a/packages/ui/src/components/TextField/TextFieldSubmit.tsx +++ b/packages/ui/src/components/TextField/TextFieldSubmit.tsx @@ -11,7 +11,7 @@ export type TextFieldSubmitProps = Omit< export const TextFieldSubmit = forwardRef< HTMLButtonElement, TextFieldSubmitProps ->(({ className = '', type = 'button', ...props }, ref) => { +>(({ className = '', type = 'button', disabled, ...props }, ref) => { const { variant, isError } = useContext(TextFieldContext); if (variant !== 'button') return null; @@ -19,11 +19,16 @@ export const TextFieldSubmit = forwardRef< return ( ); }); From 1ca9aa8384d7fefec70efa2ea9d779bc0c9c0460 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:32:55 +0900 Subject: [PATCH 03/30] =?UTF-8?q?feat(apps/web):=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EA=B7=B8=EB=A3=B9=EB=B3=84=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/store/query/useGetAllPostsQuery.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 apps/web/src/store/query/useGetAllPostsQuery.ts diff --git a/apps/web/src/store/query/useGetAllPostsQuery.ts b/apps/web/src/store/query/useGetAllPostsQuery.ts new file mode 100644 index 0000000..355d569 --- /dev/null +++ b/apps/web/src/store/query/useGetAllPostsQuery.ts @@ -0,0 +1,50 @@ +import { GET } from '@web/shared/server'; +import { + queryOptions, + useQuery, + useSuspenseQuery, +} from '@tanstack/react-query'; +import { Post } from '@web/types'; +import { EditPageParams } from '@web/app/(prompt)/edit/[agentId]/[postGroupId]/types'; +import { PostGroup } from '@web/types/post'; +import { Tokens } from '@web/shared/server/types'; + +const STALE_TIME = 1000 * 60 * 1; +const GC_TIME = 1000 * 60 * 1; + +export type GetAllPostsParams = EditPageParams & { + tokens?: Tokens; +}; + +export interface GetAllPostsResponse { + postGroup: PostGroup; + posts: Post[]; +} + +/** + * 게시물 그룹별 게시물 목록 조회 API + * + * 게시물 그룹에 해당되는 모든 게시물 목록을 조회합니다. + */ +export function getAllPostsQueryOptions({ + agentId, + postGroupId, + tokens, +}: GetAllPostsParams) { + return queryOptions({ + queryKey: ['posts', agentId, postGroupId], + queryFn: () => + GET( + `agents/${agentId}/post-groups/${postGroupId}/posts`, + undefined, + tokens + ), + // NOTE: 항상 fresh 상태로 유지 + staleTime: STALE_TIME, + gcTime: GC_TIME, + }); +} + +export function useGetAllPostsQuery(params: GetAllPostsParams) { + return useSuspenseQuery(getAllPostsQueryOptions(params)); +} From aa71d6fbf6197f15dde8c0e64f1037a65110eb80 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:33:11 +0900 Subject: [PATCH 04/30] =?UTF-8?q?feat(apps/web):=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=ED=94=84=EB=A1=AC=ED=94=84=ED=8A=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EC=9D=BC=EA=B4=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/mutation/useUpdatePromptMutation.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 apps/web/src/store/mutation/useUpdatePromptMutation.ts diff --git a/apps/web/src/store/mutation/useUpdatePromptMutation.ts b/apps/web/src/store/mutation/useUpdatePromptMutation.ts new file mode 100644 index 0000000..9d0b999 --- /dev/null +++ b/apps/web/src/store/mutation/useUpdatePromptMutation.ts @@ -0,0 +1,40 @@ +import { PATCH } from '@web/shared/server'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useToast } from '@repo/ui/hooks'; +import { EditPageParams } from '@web/app/(prompt)/edit/[agentId]/[postGroupId]/types'; +import { getAllPostsQueryOptions } from '../query/useGetAllPostsQuery'; +import { Post } from '@web/types'; + +export interface UpdatePromptRequest { + prompt: string; + postsId: Post['id'][]; +} + +/** + * 게시물 프롬프트 기반 일괄 수정 + * + * 일괄 게시물에 대해 입력된 프롬프트를 바탕으로 수정합니다. + */ +export function useUpdatePromptMutation({ + agentId, + postGroupId, +}: EditPageParams) { + const queryClient = useQueryClient(); + const toast = useToast(); + + return useMutation({ + mutationFn: (data: UpdatePromptRequest) => + PATCH(`agents/${agentId}/post-groups/${postGroupId}/posts/prompt`, data), + onSuccess: () => { + toast.success('프롬프트가 적용되었어요!'); + queryClient.invalidateQueries( + getAllPostsQueryOptions({ agentId, postGroupId }) + ); + }, + onError: (error) => { + if (error instanceof Error) { + toast.error(error.message); + } + }, + }); +} From 91aa11a6b57248de03f63c0cd1aea074b2f53466 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:34:14 +0900 Subject: [PATCH 05/30] =?UTF-8?q?feat(apps/web):=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EA=B8=B0=ED=83=80=20=EC=A0=95=EB=B3=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/mutation/useUpdatePostsMutation.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 apps/web/src/store/mutation/useUpdatePostsMutation.ts diff --git a/apps/web/src/store/mutation/useUpdatePostsMutation.ts b/apps/web/src/store/mutation/useUpdatePostsMutation.ts new file mode 100644 index 0000000..b99a715 --- /dev/null +++ b/apps/web/src/store/mutation/useUpdatePostsMutation.ts @@ -0,0 +1,46 @@ +import { PUT } from '@web/shared/server'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useToast } from '@repo/ui/hooks'; +import { EditPageParams } from '@web/app/(prompt)/edit/[agentId]/[postGroupId]/types'; +import { PostStatus } from '@web/types/post'; +import { getAllPostsQueryOptions } from '../query/useGetAllPostsQuery'; + +interface UpdatePostPayload { + postId?: number; + status?: PostStatus; + uploadTime?: string; + displayOrder?: number; +} + +interface UpdatePostsRequest { + posts: UpdatePostPayload[]; +} + +/** + * + * 게시물 기타 정보 수정 API + * + * 기존 여러 게시물들의 상태 / 업로드 예약 일시 / 순서를 수정합니다. + */ +export function useUpdatePostsMutation({ + agentId, + postGroupId, +}: EditPageParams) { + const queryClient = useQueryClient(); + const toast = useToast(); + + return useMutation({ + mutationFn: (data: UpdatePostsRequest) => + PUT(`agents/${agentId}/post-groups/${postGroupId}/posts`, data), + onSuccess: () => { + queryClient.invalidateQueries( + getAllPostsQueryOptions({ agentId, postGroupId }) + ); + }, + onError: (error) => { + if (error instanceof Error) { + toast.error(error.message); + } + }, + }); +} From f094f50a545ec25cc86283e16b048361ffcea1b1 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:35:06 +0900 Subject: [PATCH 06/30] =?UTF-8?q?feat(apps/web):=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EA=B0=9C=EB=B3=84=20=EC=82=AD=EC=A0=9C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/mutation/useDeletePostMutation.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 apps/web/src/store/mutation/useDeletePostMutation.ts diff --git a/apps/web/src/store/mutation/useDeletePostMutation.ts b/apps/web/src/store/mutation/useDeletePostMutation.ts new file mode 100644 index 0000000..89b09b2 --- /dev/null +++ b/apps/web/src/store/mutation/useDeletePostMutation.ts @@ -0,0 +1,37 @@ +import { DELETE } from '@web/shared/server'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useToast } from '@repo/ui/hooks'; +import { EditPageParams } from '@web/app/(prompt)/edit/[agentId]/[postGroupId]/types'; +import { getAllPostsQueryOptions } from '../query/useGetAllPostsQuery'; +import { Post } from '@web/types'; + +/** + * 게시물 개별 삭제 API + * + * 업로드가 확정되지 않은 단건의 게시물을 개별 삭제합니다. (생성됨, 수정 중, 수정 완료) + * + * 업로드가 확정된 상태의 게시물은 삭제할 수 없습니다. (예약 완료, 업로드 완료, 업로드 실패) + */ +export function useDeletePostMutation({ + agentId, + postGroupId, +}: EditPageParams) { + const queryClient = useQueryClient(); + const toast = useToast(); + + return useMutation({ + mutationFn: (postId: Post['id']) => + DELETE(`agents/${agentId}/post-groups/${postGroupId}/posts/${postId}`), + onSuccess: () => { + toast.success('게시글이 삭제되었어요.'); + queryClient.invalidateQueries( + getAllPostsQueryOptions({ agentId, postGroupId }) + ); + }, + onError: (error) => { + if (error instanceof Error) { + toast.error(error.message); + } + }, + }); +} From d0da5dae5b9f5a03bacbeeb43c40ae78f3e9273f Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:35:52 +0900 Subject: [PATCH 07/30] =?UTF-8?q?feat(apps/web):=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=B6=94=EA=B0=80=20=EC=83=9D=EC=84=B1=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mutation/useCreateMorePostsMutation.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 apps/web/src/store/mutation/useCreateMorePostsMutation.ts diff --git a/apps/web/src/store/mutation/useCreateMorePostsMutation.ts b/apps/web/src/store/mutation/useCreateMorePostsMutation.ts new file mode 100644 index 0000000..d84ed6a --- /dev/null +++ b/apps/web/src/store/mutation/useCreateMorePostsMutation.ts @@ -0,0 +1,41 @@ +import { POST } from '@web/shared/server'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useToast } from '@repo/ui/hooks'; +import { EditPageParams } from '@web/app/(prompt)/edit/[agentId]/[postGroupId]/types'; +import { Post } from '@web/types'; +import { PostGroup } from '@web/types/post'; +import { getAllPostsQueryOptions } from '../query/useGetAllPostsQuery'; + +export interface CreateMorePostsResponse { + postGroup: PostGroup; + posts: Post[]; +} + +/** + * 게시물 추가 생성 API + */ +export function useCreateMorePostsMutation({ + agentId, + postGroupId, +}: EditPageParams) { + const queryClient = useQueryClient(); + const toast = useToast(); + + return useMutation({ + mutationFn: () => + POST( + `agents/${agentId}/post-groups/${postGroupId}/posts` + ), + onSuccess: () => { + toast.success('게시글이 5개 추가됐어요!'); + queryClient.invalidateQueries( + getAllPostsQueryOptions({ agentId, postGroupId }) + ); + }, + onError: (error) => { + if (error instanceof Error) { + toast.error(error.message); + } + }, + }); +} From 65eb046be3c3e9b5f7e21e6774b9a04b6b273bc9 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:36:22 +0900 Subject: [PATCH 08/30] =?UTF-8?q?fix(apps/web):=20DND=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DNDController/context/DndContext.tsx | 3 +++ .../DNDController/hooks/useDragAndDrop.ts | 24 +++++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/common/DNDController/context/DndContext.tsx b/apps/web/src/components/common/DNDController/context/DndContext.tsx index d3d404f..0ca14d4 100644 --- a/apps/web/src/components/common/DNDController/context/DndContext.tsx +++ b/apps/web/src/components/common/DNDController/context/DndContext.tsx @@ -77,6 +77,9 @@ export function DndControllerProvider({ scaleY: 1, }), ]} + autoScroll={{ + enabled: false, + }} > {children} diff --git a/apps/web/src/components/common/DNDController/hooks/useDragAndDrop.ts b/apps/web/src/components/common/DNDController/hooks/useDragAndDrop.ts index f71573f..40e5692 100644 --- a/apps/web/src/components/common/DNDController/hooks/useDragAndDrop.ts +++ b/apps/web/src/components/common/DNDController/hooks/useDragAndDrop.ts @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { DragEndEvent, DragOverEvent } from '@dnd-kit/core'; import { arrayMove } from '@dnd-kit/sortable'; import { Post } from '@web/types'; @@ -6,7 +6,7 @@ import { Post } from '@web/types'; type UseDragAndDropProps = { initialItems: Post[]; onItemsChange?: (items: Post[]) => void; - onDragEnd?: () => void; + onDragEnd?: (items: Post[]) => void; }; export function useDragAndDrop({ @@ -17,6 +17,11 @@ export function useDragAndDrop({ const [items, setItems] = useState(initialItems); const [activeId, setActiveId] = useState(null); + // initialItems가 변경될 때마다 items 상태 업데이트 + useEffect(() => { + setItems(initialItems); + }, [initialItems]); + const getItemsByStatus = (status: Post['status']) => items.filter((item) => item.status === status); @@ -103,14 +108,13 @@ export function useDragAndDrop({ if (typeof over.id === 'string') { const targetStatus = over.id as Post['status']; - // 같은 상태면 무시 if (draggedItem.status === targetStatus) return; - setItems((prev) => - prev.map((item) => - item.id === draggedItemId ? { ...item, status: targetStatus } : item - ) + const newItems = items.map((item) => + item.id === draggedItemId ? { ...item, status: targetStatus } : item ); + setItems(newItems); + onDragEnd?.(newItems); return; } @@ -121,9 +125,9 @@ export function useDragAndDrop({ const oldIndex = items.findIndex((item) => item.id === draggedItemId); const newIndex = items.findIndex((item) => item.id === overId); - setItems(arrayMove(items, oldIndex, newIndex)); - - onDragEnd?.(); + const newItems = arrayMove(items, oldIndex, newIndex); + setItems(newItems); + onDragEnd?.(newItems); }; const handleRemove = (id: number) => { From 1912ebfee013ca6c5b3327f5cfcd97911b3a94ff Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:37:01 +0900 Subject: [PATCH 09/30] =?UTF-8?q?feat(apps/web):=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/types/post.ts | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/web/src/types/post.ts b/apps/web/src/types/post.ts index 250cf47..4eb7df7 100644 --- a/apps/web/src/types/post.ts +++ b/apps/web/src/types/post.ts @@ -13,7 +13,7 @@ export const POST_STATUS = { UPLOAD_FAILED: 'UPLOAD_FAILED', } as const; -type PostStatus = (typeof POST_STATUS)[keyof typeof POST_STATUS]; +export type PostStatus = (typeof POST_STATUS)[keyof typeof POST_STATUS]; export interface Post { id: number; @@ -25,3 +25,37 @@ export interface Post { status: PostStatus; uploadTime: string; } + +export type Purpose = 'INFORMATION' | 'OPINION' | 'HUMOR' | 'MARKETING'; + +export type Reference = 'NONE' | 'NEWS' | 'IMAGE'; + +export type NewsCategory = + | 'INVEST' + | 'STOCK' + | 'REALESTATE' + | 'FASHION' + | 'TRAVEL' + | 'BEAUTY' + | 'FITNESS' + | 'COOKING' + | 'HEALTHCARE' + | 'AI' + | 'GAME' + | 'APP' + | 'SPACE' + | 'ENVIRONMENT' + | 'ENGINEER'; + +export type PostGroupLength = 'SHORT' | 'MEDIUM' | 'LONG'; + +export interface PostGroup { + id: number; + topic: string; + purpose: Purpose; + reference: Reference; + newsCategory: NewsCategory | null; + postGroupImages: PostImage[]; + length: PostGroupLength; + content: string; +} From 520e7085870282e7e53eb9cf4d361317da432050 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:37:44 +0900 Subject: [PATCH 10/30] =?UTF-8?q?feat(apps/web):=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../edit/[agentId]/[postGroupId]/Edit.tsx | 207 ++++----------- .../EditContent/EditContent.css.ts | 47 ++++ .../_components/EditContent/EditContent.tsx | 237 ++++++++++++++++++ .../edit/[agentId]/[postGroupId]/page.tsx | 16 +- 4 files changed, 346 insertions(+), 161 deletions(-) create mode 100644 apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.css.ts create mode 100644 apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/Edit.tsx b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/Edit.tsx index 62fc1d7..7f53fd5 100644 --- a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/Edit.tsx +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/Edit.tsx @@ -1,30 +1,38 @@ 'use client'; -import React, { RefObject } from 'react'; import { useScroll } from '@web/hooks'; import * as style from './pageStyle.css'; import { NavBar, MainBreadcrumbItem } from '@web/components/common'; -import { Breadcrumb, Button, Chip, Icon, Accordion } from '@repo/ui'; +import { Breadcrumb, Button, Icon } from '@repo/ui'; import { POST_STATUS } from '@web/types/post'; -import { INITIAL_CONTENT_ITEMS } from './constants'; -import { DndController, useDndController } from '@web/components/common'; +import { DndController } from '@web/components/common'; import { EditPageParams } from './types'; -import { DragGuide } from './_components/DragGuide/DragGuide'; +import { useGetAllPostsQuery } from '@web/store/query/useGetAllPostsQuery'; +import { useUpdatePostsMutation } from '@web/store/mutation/useUpdatePostsMutation'; +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { EditContent } from './_components/EditContent/EditContent'; -type EditContentProps = { - scrollRef: RefObject; - isScrolled: boolean; - agentId: EditPageParams['agentId']; - postGroupId: EditPageParams['postGroupId']; -}; +export default function Edit({ agentId, postGroupId }: EditPageParams) { + const [scrollRef, isScrolled] = useScroll({ threshold: 100 }); + const { data: posts } = useGetAllPostsQuery({ + agentId, + postGroupId, + }); + const { mutate: updatePosts } = useUpdatePostsMutation({ + agentId, + postGroupId, + }); + const router = useRouter(); -function EditContent({ - scrollRef, - isScrolled, - agentId, - postGroupId, -}: EditContentProps) { - const { getItemsByStatus, handleRemove } = useDndController(); + const [items, setItems] = useState(posts?.data.posts ?? []); + + /** + * READY_TO_UPLOAD 상태인 게시물이 있는지 확인 + */ + const hasReadyToUploadPosts = items.some( + (post) => post.status === POST_STATUS.READY_TO_UPLOAD + ); return (
@@ -41,8 +49,10 @@ function EditContent({ size="large" variant="primary" leftAddon={} - onClick={() => {}} - disabled={false} + onClick={() => + router.push(`/edit/${agentId}/${postGroupId}/schedule`) + } + disabled={!hasReadyToUploadPosts} isLoading={false} className={style.submitButtonStyle} > @@ -51,148 +61,25 @@ function EditContent({ } isScrolled={isScrolled} /> -
- - {/* 생성된 글 영역 */} - - - 생성된 글 - - - - item.id - )} - > - {getItemsByStatus(POST_STATUS.GENERATED).map((item) => ( - handleRemove(item.id)} - onModify={() => {}} - /> - ))} - - - - + { + setItems(updatedItems); - {/* 수정 중인 글 영역 */} - - - 수정 중인 글 - - - - item.id - )} - > - {getItemsByStatus(POST_STATUS.EDITING).length > 0 ? ( - getItemsByStatus(POST_STATUS.EDITING).map((item) => ( - handleRemove(item.id)} - onModify={() => {}} - /> - )) - ) : ( - - )} - - - - + const updatePayload = { + posts: updatedItems.map((item, index) => ({ + postId: item.id, + status: item.status, + displayOrder: index + 1, + uploadTime: item.uploadTime, + })), + }; - {/* 업로드할 글 영역 */} - - - 업로드할 글 - - - - item.id - )} - > - {getItemsByStatus(POST_STATUS.READY_TO_UPLOAD).length > 0 ? ( - getItemsByStatus(POST_STATUS.READY_TO_UPLOAD).map( - (item) => ( - handleRemove(item.id)} - onModify={() => {}} - /> - ) - ) - ) : ( - - )} - - - - - -
+ updatePosts(updatePayload); + }} + > + +
); } - -export default function Edit({ agentId, postGroupId }: EditPageParams) { - const [scrollRef, isScrolled] = useScroll({ threshold: 100 }); - - return ( - { - console.log('=== Current Items Status ==='); - const itemsByStatus = { - GENERATED: items.filter((item) => item.status === 'GENERATED'), - EDITING: items.filter((item) => item.status === 'EDITING'), - READY_TO_UPLOAD: items.filter( - (item) => item.status === 'READY_TO_UPLOAD' - ), - }; - console.log('GENERATED:', itemsByStatus.GENERATED); - console.log('EDITING:', itemsByStatus.EDITING); - console.log('READY_TO_UPLOAD:', itemsByStatus.READY_TO_UPLOAD); - console.log('========================'); - }} - > - - - ); -} diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.css.ts b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.css.ts new file mode 100644 index 0000000..c56314b --- /dev/null +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.css.ts @@ -0,0 +1,47 @@ +import { style } from '@vanilla-extract/css'; +import { vars } from '@repo/theme'; + +export const contentStyle = style({ + position: 'relative', + width: '100%', + padding: `${vars.space[80]} ${vars.space[24]}`, + margin: '0 auto', + overflowX: 'auto', +}); + +export const submitButtonStyle = style({ + fontSize: vars.typography.fontSize[18], +}); + +export const accordionStyle = style({ + display: 'flex', + gap: vars.space[64], + height: 'fit-content', + minWidth: 'min-content', + padding: `0 ${vars.space[32]}`, +}); + +export const accordionTriggerStyle = style({ + height: '8rem', + padding: `${vars.space[12]} ${vars.space[16]}`, +}); + +export const accordionItemStyle = style({ + width: '51.2rem', + flex: '0 0 auto', +}); + +export const accordionContentStyle = style({ + display: 'flex', + flexDirection: 'column', + gap: vars.space[10], +}); + +export const contentInnerWrapper = style({ + height: '100%', +}); + +export const buttonWrapperStyle = style({ + display: 'flex', + justifyContent: 'flex-end', +}); diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx new file mode 100644 index 0000000..763681b --- /dev/null +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx @@ -0,0 +1,237 @@ +'use client'; + +import { Button, Chip, Icon, Accordion, Modal, TextField } from '@repo/ui'; +import { Post, POST_STATUS } from '@web/types/post'; +import { DndController, useDndController } from '@web/components/common'; +import { useCreateMorePostsMutation } from '@web/store/mutation/useCreateMorePostsMutation'; +import { useDeletePostMutation } from '@web/store/mutation/useDeletePostMutation'; +import { useRouter } from 'next/navigation'; +import { useModal } from '@repo/ui/hooks'; +import { FormProvider, useForm } from 'react-hook-form'; +import { EditPageParams } from '../../types'; +import * as style from './EditContent.css'; +import { DragGuide } from '../DragGuide/DragGuide'; +import { + UpdatePromptRequest, + useUpdatePromptMutation, +} from '@web/store/mutation/useUpdatePromptMutation'; + +type PromptForm = UpdatePromptRequest; + +export function EditContent({ agentId, postGroupId }: EditPageParams) { + const modal = useModal(); + const { getItemsByStatus } = useDndController(); + const methods = useForm({ + defaultValues: { + prompt: '', + }, + }); + const { register, handleSubmit, watch, setValue } = methods; + const promptValue = watch('prompt'); + + const { mutate: createMorePosts, isPending: isCreateMorePostsPending } = + useCreateMorePostsMutation({ + agentId, + postGroupId, + }); + + const { mutate: updatePrompt, isPending: isUpdatePromptPending } = + useUpdatePromptMutation({ + agentId, + postGroupId, + }); + + const { mutate: deletePost } = useDeletePostMutation({ + agentId, + postGroupId, + }); + + const router = useRouter(); + + const handleModify = (postId: Post['id']) => { + router.push(`/edit/${agentId}/${postGroupId}/detail?postId=${postId}`); + }; + + const handleDeletePost = (postId: Post['id']) => { + modal.confirm({ + title: '정말 삭제하시겠어요?', + description: '삭제된 글은 복구할 수 없어요', + icon: , + confirmButton: '삭제하기', + cancelButton: '취소', + confirmButtonProps: { + onClick: () => { + deletePost(postId); + }, + }, + }); + }; + + const onSubmit = (data: PromptForm) => { + const editingPostIds = getItemsByStatus(POST_STATUS.EDITING).map( + (item) => item.id + ); + + updatePrompt({ + prompt: data.prompt, + postsId: editingPostIds, + }); + setValue('prompt', ''); + }; + + return ( +
+ + {/* 생성된 글 영역 */} + + + 생성된 글 + + +
+ {isCreateMorePostsPending && + Array.from({ length: 5 }).map((_, index) => ( + + ))} + + item.id + )} + > + {getItemsByStatus(POST_STATUS.GENERATED).map((item) => ( + handleDeletePost(item.id)} + onModify={() => handleModify(item.id)} + /> + ))} + + +
+
+ +
+
+
+ + {/* 수정 중인 글 영역 */} + + + 수정 중인 글 + + + + item.id + )} + > + +
+ + setValue('prompt', e.target.value)} + placeholder="AI에게 요청하여 글 업그레이드하기" + sumbitButton={ + + } + maxLength={5000} + /> + +
+
+ {getItemsByStatus(POST_STATUS.EDITING).length > 0 ? ( + getItemsByStatus(POST_STATUS.EDITING).map((item) => ( + handleDeletePost(item.id)} + onModify={() => handleModify(item.id)} + isLoading={isUpdatePromptPending} + /> + )) + ) : ( + + )} +
+
+
+
+ + {/* 업로드할 글 영역 */} + + + 업로드할 글 + + + + item.id + )} + > + {getItemsByStatus(POST_STATUS.READY_TO_UPLOAD).length > 0 ? ( + getItemsByStatus(POST_STATUS.READY_TO_UPLOAD).map((item) => ( + handleDeletePost(item.id)} + onModify={() => handleModify(item.id)} + /> + )) + ) : ( + + )} + + + + +
+
+ ); +} diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/page.tsx b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/page.tsx index 73c53e9..c5c9ab2 100644 --- a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/page.tsx +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/page.tsx @@ -1,6 +1,20 @@ +import { ServerFetchBoundary } from '@web/store/query/ServerFetchBoundary'; import Edit from './Edit'; import type { EditPageProps } from './types'; +import { getAllPostsQueryOptions } from '@web/store/query/useGetAllPostsQuery'; +import { getServerSideTokens } from '@web/shared/server/serverSideTokens'; export default function EditPage({ params }: EditPageProps) { - return ; + const tokens = getServerSideTokens(); + const serverFetchOptions = getAllPostsQueryOptions({ + agentId: params.agentId, + postGroupId: params.postGroupId, + tokens, + }); + + return ( + + + + ); } From 87fdcc9e14f76f8d95c3f13f08630cfbd281b838 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:38:05 +0900 Subject: [PATCH 11/30] =?UTF-8?q?remove(apps/web):=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 1533859..68700c4 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -548,7 +548,7 @@ export default function Home() { - + {/* */} ); } From fe5c934970be423632a13c30fe9998ff22ac3548 Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:44:15 +0900 Subject: [PATCH 12/30] =?UTF-8?q?refactor(apps/web):=20SkeletonItems=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/EditContent/EditContent.tsx | 13 +++--------- .../SkeletonItems/SkeletonItems.tsx | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/SkeletonItems/SkeletonItems.tsx diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx index 763681b..cfc755d 100644 --- a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx @@ -15,6 +15,7 @@ import { UpdatePromptRequest, useUpdatePromptMutation, } from '@web/store/mutation/useUpdatePromptMutation'; +import { SkeletonItems } from '../SkeletonItems/SkeletonItems'; type PromptForm = UpdatePromptRequest; @@ -100,16 +101,7 @@ export function EditContent({ agentId, postGroupId }: EditPageParams) {
- {isCreateMorePostsPending && - Array.from({ length: 5 }).map((_, index) => ( - - ))} + {isCreateMorePostsPending && } handleDeletePost(item.id)} onModify={() => handleModify(item.id)} + isLoading={isCreateMorePostsPending} /> ))} diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/SkeletonItems/SkeletonItems.tsx b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/SkeletonItems/SkeletonItems.tsx new file mode 100644 index 0000000..70be47e --- /dev/null +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/SkeletonItems/SkeletonItems.tsx @@ -0,0 +1,21 @@ +import { DndController } from '@web/components/common'; + +type SkeletonItemsProps = { + length: number; +}; + +export function SkeletonItems({ length }: SkeletonItemsProps) { + return ( + <> + {Array.from({ length }).map((_, index) => ( + + ))} + + ); +} From a228b1bf7bf7ce3b5c06915d9f4ff144a6b1652b Mon Sep 17 00:00:00 2001 From: minseong Date: Fri, 7 Feb 2025 05:54:19 +0900 Subject: [PATCH 13/30] =?UTF-8?q?feat(packages/ui):=20TextField=20'white'?= =?UTF-8?q?=20variant=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/EditContent/EditContent.tsx | 2 +- .../src/components/TextField/TextField.css.ts | 20 ++++++++++--------- .../components/TextField/TextFieldInput.tsx | 1 - .../ui/src/components/TextField/context.ts | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx index cfc755d..86b9a6d 100644 --- a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx @@ -154,7 +154,7 @@ export function EditContent({ agentId, postGroupId }: EditPageParams) { >
- + Date: Fri, 7 Feb 2025 05:54:19 +0900 Subject: [PATCH 14/30] =?UTF-8?q?feat(packages/ui):=20TextField=20'white'?= =?UTF-8?q?=20variant=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/EditContent/EditContent.tsx | 2 +- .../src/components/TextField/TextField.css.ts | 20 ++++++++++--------- .../components/TextField/TextFieldInput.tsx | 1 - .../ui/src/components/TextField/context.ts | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx index cfc755d..86b9a6d 100644 --- a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx @@ -154,7 +154,7 @@ export function EditContent({ agentId, postGroupId }: EditPageParams) { > - + Date: Fri, 7 Feb 2025 06:13:13 +0900 Subject: [PATCH 15/30] =?UTF-8?q?fix(apps/web):=20=EC=9E=90=EC=9E=98?= =?UTF-8?q?=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/EditContent/EditContent.tsx | 44 ++++++++++--------- .../components/TextField/TextFieldSubmit.tsx | 2 +- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx index 86b9a6d..5e45f81 100644 --- a/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx +++ b/apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx @@ -22,6 +22,7 @@ type PromptForm = UpdatePromptRequest; export function EditContent({ agentId, postGroupId }: EditPageParams) { const modal = useModal(); const { getItemsByStatus } = useDndController(); + const isExistEditingPost = getItemsByStatus(POST_STATUS.EDITING).length > 0; const methods = useForm({ defaultValues: { prompt: '', @@ -116,7 +117,6 @@ export function EditContent({ agentId, postGroupId }: EditPageParams) { updatedAt={item.updatedAt} onRemove={() => handleDeletePost(item.id)} onModify={() => handleModify(item.id)} - isLoading={isCreateMorePostsPending} /> ))} @@ -152,26 +152,28 @@ export function EditContent({ agentId, postGroupId }: EditPageParams) { (item) => item.id )} > - - - - setValue('prompt', e.target.value)} - placeholder="AI에게 요청하여 글 업그레이드하기" - sumbitButton={ - - } - maxLength={5000} - /> - - - - {getItemsByStatus(POST_STATUS.EDITING).length > 0 ? ( + {isExistEditingPost && ( + +
+ + setValue('prompt', e.target.value)} + placeholder="AI에게 요청하여 글 업그레이드하기" + sumbitButton={ + + } + maxLength={5000} + /> + +
+
+ )} + {isExistEditingPost ? ( getItemsByStatus(POST_STATUS.EDITING).map((item) => ( (({ className = '', type = 'button', disabled, ...props }, ref) => { const { variant, isError } = useContext(TextFieldContext); - if (variant !== 'button') return null; + if (variant !== 'button' && variant !== 'white') return null; return (