Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 결과 수정 - 상세 페이지 api 연동 #112

Merged
merged 13 commits into from
Feb 8, 2025
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
3 changes: 3 additions & 0 deletions apps/web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const nextConfig = {
});
return config;
},
images: {
domains: ['instead-dev.s3.ap-northeast-2.amazonaws.com'],
},
};

export default withVanillaExtract(nextConfig);
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@repo/theme": "workspace:^",
"@repo/ui": "workspace:^",
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-devtools": "^5.66.0",
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

devDependencies로 이동을 제안합니다.

@tanstack/react-query-devtools는 개발 도구이므로 dependencies가 아닌 devDependencies에 위치하는 것이 더 적절합니다. 이렇게 하면 프로덕션 빌드 크기를 줄일 수 있습니다.

다음과 같이 수정하는 것을 제안합니다:

-    "@tanstack/react-query-devtools": "^5.66.0",

devDependencies 섹션에 추가:

+    "@tanstack/react-query-devtools": "^5.66.0",

"@vanilla-extract/css": "^1.17.0",
"cookies-next": "^5.1.0",
"emoji-picker-react": "^4.12.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 0,
},
{
id: 2,
Expand All @@ -28,6 +29,7 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 1,
},
{
id: 3,
Expand All @@ -38,6 +40,7 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 2,
},
{
id: 4,
Expand All @@ -48,6 +51,7 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 3,
},
{
id: 5,
Expand All @@ -58,5 +62,6 @@ export const INITIAL_CONTENT_ITEMS: Post[] = [
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
displayOrder: 4,
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { style } from '@vanilla-extract/css';

export const editDetailPage = style({
display: 'flex',
height: '100vh',
flexShrink: 0,
});

export const flexColumn = style({
display: 'flex',
width: '100%',
justifyContent: 'center',
});
Comment on lines +3 to +13
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스타일 중복을 제거해주세요.

page.css.ts와 EditDetail.css.ts에서 동일한 스타일이 중복 정의되어 있습니다. 유지보수성 향상을 위해 다음과 같이 개선하는 것을 추천드립니다:

  • 공통 스타일을 별도의 파일로 분리
  • 스타일을 재사용 가능한 형태로 구성

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client';

import { createContext, Dispatch, SetStateAction } from 'react';
import { EditPost } from './_components/EditPost/EditPost';
import { EditSidebar } from './_components/EditSidebar/EditSidebar';
import { editDetailPage, flexColumn } from './EditDetail.css';
import { useState } from 'react';
import { Post } from '@web/types';

// TODO 추후 Jotai, 또는 react-query 사용으로 수정할 예정
interface DetailPageContextType {
loadingPosts: Post['id'][];
setLoadingPosts: Dispatch<SetStateAction<Post['id'][]>>;
}

const defaultContextValue: DetailPageContextType = {
loadingPosts: [],
setLoadingPosts: () => {},
};
Comment on lines +16 to +19
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Context의 기본값 처리를 개선해 주세요.

현재 기본값의 setLoadingPosts가 빈 함수로 설정되어 있어, 실수로 Provider 없이 사용할 경우 감지하기 어려운 오류가 발생할 수 있습니다.

다음과 같이 개선하는 것을 제안합니다:

const defaultContextValue: DetailPageContextType = {
  loadingPosts: [],
-  setLoadingPosts: () => {},
+  setLoadingPosts: () => {
+    throw new Error('DetailPageContext가 Provider 외부에서 사용되었습니다.');
+  },
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const defaultContextValue: DetailPageContextType = {
loadingPosts: [],
setLoadingPosts: () => {},
};
const defaultContextValue: DetailPageContextType = {
loadingPosts: [],
setLoadingPosts: () => {
throw new Error('DetailPageContext가 Provider 외부에서 사용되었습니다.');
},
};


export const DetailPageContext =
createContext<DetailPageContextType>(defaultContextValue);

export function EditDetail() {
const [loadingPosts, setLoadingPosts] = useState<Post['id'][] | []>([]);

return (
<DetailPageContext.Provider value={{ loadingPosts, setLoadingPosts }}>
<div className={editDetailPage}>
<EditSidebar />
<div className={flexColumn}>
<EditPost />
</div>
</div>
</DetailPageContext.Provider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const contentItemStyle = style({
height: '6.4rem',
width: '100%',
padding: '1.6rem 0.8rem 1.2rem 1.2rem',
cursor: 'pointer',
});

export const titleStyle = style({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
contentItemStyle,
cursorGrabStyle,
cursorPointerStyle,
iconHoverStyle,
noShrinkStyle,
timeStyle,
Expand All @@ -10,15 +9,18 @@ import {
import { Icon } from '@repo/ui/Icon';
import { IconButton } from '@repo/ui/IconButton';
import { Text } from '@repo/ui/Text';
import { PostImage } from '@web/types';
import { getTimeAgo } from '@web/utils';

export type ContentItemProps = {
image?: string;
image?: PostImage[];
title?: string;
updatedAt: string;
onClick: () => void;
onRemove: () => void;
onModify: () => void;
onDrag: () => void;
isSelected?: boolean;
};

/**
Expand All @@ -31,10 +33,16 @@ export type ContentItemProps = {
* @property {() => void} onModify - 수정 아이콘 클릭 시 호출되는 콜백 함수.
* @property {() => void} onDrag - 드래그 아이콘 마우스 다운 시 호출되는 콜백 함수.
*/
export function ContentItem({ image, title, updatedAt }: ContentItemProps) {
export function ContentItem({
image,
title,
updatedAt,
onClick,
isSelected = false,
}: ContentItemProps) {
return (
<div className={contentItemStyle}>
{image ? (
<div className={contentItemStyle} onClick={onClick}>
{image?.[0] ? (
<div>x</div>
) : (
<Icon
Expand All @@ -49,7 +57,7 @@ export function ContentItem({ image, title, updatedAt }: ContentItemProps) {
className={titleStyle}
fontSize={18}
fontWeight="semibold"
color="grey600"
color={isSelected ? 'purple700' : 'grey600'}
>
{title}
</Text>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { style } from '@vanilla-extract/css';
import { vars } from '@repo/theme';

export const container = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '100%',
height: 'fit-content',
borderRadius: vars.borderRadius[24],
border: `1px solid ${vars.colors.grey50}`,
background: vars.colors.grey,
padding: vars.space[40],
});

export const description = style({
textAlign: 'center',
whiteSpace: 'pre-wrap',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as styles from './DragGuide.css';
import { Text } from '@repo/ui';

type DragGuideProps = {
description: string;
};

export function DragGuide({ description }: DragGuideProps) {
return (
<div className={styles.container}>
<Text.H2
className={styles.description}
fontSize={20}
fontWeight="semibold"
color="grey500"
>
{description}
</Text.H2>
<Text.H2
className={styles.description}
fontSize={20}
fontWeight="semibold"
color="grey500"
>
끌어서 여기에 놓아주세요
</Text.H2>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,51 @@ import { Badge } from '@repo/ui/Badge';
import { PostEditor } from '../PostEditor/PostEditor';
import { EditPromptField } from '../EditPromptField/EditPromptField';
import { FormProvider, useForm } from 'react-hook-form';
import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { useGroupPostsQuery } from '@web/store/query/useGroupPostsQuery';
import { useAdjacentPosts } from '../../_hooks/useAdjacentPosts';

export function EditPost() {
const router = useRouter();
const methods = useForm();
const { agentId, postGroupId } = useParams();
const searchParams = useSearchParams();
const postId = searchParams.get('post');
const { data } = useGroupPostsQuery(Number(agentId), Number(postGroupId));
const post = data?.data?.posts.find((post) => post.id === Number(postId));
Comment on lines +20 to +21
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

데이터 유효성 검사 필요

postId가 null일 경우에 대한 처리가 누락되어 있습니다. 이는 잠재적인 런타임 오류를 발생시킬 수 있습니다.

다음과 같이 early return을 추가하는 것을 제안합니다:

  const postId = searchParams.get('post');
+ if (!postId) {
+   return <div>잘못된 접근입니다.</div>;
+ }
  const { data } = useGroupPostsQuery(Number(agentId), Number(postGroupId));

const { routePreviousPost, routeNextPost, canMoveUp, canMoveDown } =
useAdjacentPosts(data?.data?.posts, post);

return (
<div className={wrapper}>
<div className={controlBar}>
<div>
<IconButton icon="arrowLineTop" />
<IconButton icon="arrowLineBottom" />
<IconButton
icon="arrowLineTop"
disabled={!canMoveUp}
onClick={routePreviousPost}
/>
<IconButton
icon="arrowLineBottom"
disabled={!canMoveDown}
onClick={routeNextPost}
/>
</div>

<div>
<IconButton icon="dots" />
<IconButton icon="x" iconType="stroke" />
<IconButton
icon="x"
iconType="stroke"
onClick={() => router.push(`/edit/${agentId}/${postGroupId}`)}
/>
</div>
</div>

<div className={postWrapper}>
<div className={titleWrapper}>
<Text color="grey1000" fontSize={28} fontWeight="bold">
네이버 웨일, 본사로 이전하는 이유
{post?.summary}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

데이터 로딩 상태 처리 필요

post가 undefined일 때의 로딩 상태 처리가 누락되어 있습니다.

다음과 같이 로딩 상태를 표시하는 것을 제안합니다:

- {post?.summary}
+ {post?.summary ?? <Skeleton width="80%" height="28px" />}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{post?.summary}
{post?.summary ?? <Skeleton width="80%" height="28px" />}

</Text>
<Badge size="large" variant="neutral" shape="square">
요약
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,94 @@

import { Checkbox } from '@repo/ui/Checkbox';
import { TextField } from '@repo/ui/TextField';
import { useFormContext } from 'react-hook-form';
import { Controller, useForm, useFormContext } from 'react-hook-form';
import { wrapper } from './EditPromptField.css';
import { Spacing } from '@repo/ui/Spacing';
import { isEmptyStringOrNil } from '@web/utils';
import { usePatchPromptMutation } from '@web/store/mutation/usePatchPromptMutation';
import { useParams, useSearchParams } from 'next/navigation';
import { useContext, useEffect } from 'react';
import { DetailPageContext } from '../../EditDetail';
import { useGroupPostsQuery } from '@web/store/query/useGroupPostsQuery';

export function EditPromptField() {
const { register } = useFormContext();
const { register, watch, control, handleSubmit } = useForm<{
isEntire: boolean;
prompt: string;
}>({
defaultValues: {
isEntire: false,
prompt: '',
},
});
const { loadingPosts, setLoadingPosts } = useContext(DetailPageContext);
const isEntire = watch('isEntire');
const prompt = watch('prompt');
const isSubmitDisabled = isEmptyStringOrNil(prompt);
const { agentId, postGroupId } = useParams();
const searchParams = useSearchParams();
const postId = searchParams.get('post');
const { data } = useGroupPostsQuery(Number(agentId), Number(postGroupId));
const posts = data?.data.posts ?? [];
const editingPosts = posts
.filter((post) => post.status === 'EDITING')
.map((post) => post.id);

const { mutate: patchPrompt, isPending } = usePatchPromptMutation({
agentId: Number(agentId),
postGroupId: Number(postGroupId),
postId: Number(postId),
});

// TODO 제거 예정
useEffect(() => {
if (isPending) {
// 요청이 진행 중이면
if (isEntire) {
setLoadingPosts(editingPosts);
} else {
setLoadingPosts([Number(postId)]);
}
} else {
if (isEntire) {
setLoadingPosts((prev) =>
prev.filter((id) => !editingPosts.includes(id))
);
} else {
setLoadingPosts((prev) => prev.filter((id) => id !== Number(postId)));
}
}
}, [isPending]);

const onSubmit = async (data: { isEntire: boolean; prompt: string }) => {
patchPrompt({ ...data });
};

return (
<div className={wrapper}>
<Spacing size={8} />
<Checkbox
label="수정 중인 글을 모두 업그레이드하기"
defaultChecked
onChange={(checked) => console.log(checked)}
<Controller
name="isEntire"
control={control}
render={({ field }) => (
<Checkbox
label="수정 중인 글을 모두 업그레이드하기"
defaultChecked
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

체크박스 defaultChecked 속성 제거 필요

defaultChecked 속성과 checked 속성이 동시에 사용되고 있어 React 컴포넌트의 제어 상태가 모호해질 수 있습니다.

다음과 같이 defaultChecked 속성을 제거하는 것을 제안합니다:

  <Checkbox
    label="수정 중인 글을 모두 업그레이드하기"
-   defaultChecked
    checked={field.value}
    onChange={field.onChange}
  />

checked={field.value}
onChange={field.onChange}
/>
)}
/>
<Spacing size={16} />
<TextField id="ai-field" variant="button">
<TextField.Input
sumbitButton={<TextField.Submit type="submit" />}
sumbitButton={
<TextField.Submit
type="submit"
onClick={handleSubmit(onSubmit)}
disabled={isSubmitDisabled}
/>
}
placeholder="AI에게 요청하여 글 업그레이드하기"
{...register('prompt', {
required: '메시지를 입력해주세요',
Expand Down
Loading
Loading