From 46042ff0c81b34c6120154a52573c73a70f57985 Mon Sep 17 00:00:00 2001
From: wnhlee <2wheeh@gmail.com>
Date: Mon, 11 Mar 2024 19:13:46 +0900
Subject: [PATCH 1/2] feat: implement CreateReviewButton
---
.../review/client/create-review-button.tsx | 76 +++++++++++++++++++
ui/src/lib/utils/common/error.ts | 12 +++
2 files changed, 88 insertions(+)
create mode 100644 ui/src/components/review/client/create-review-button.tsx
create mode 100644 ui/src/lib/utils/common/error.ts
diff --git a/ui/src/components/review/client/create-review-button.tsx b/ui/src/components/review/client/create-review-button.tsx
new file mode 100644
index 00000000..337a6e1b
--- /dev/null
+++ b/ui/src/components/review/client/create-review-button.tsx
@@ -0,0 +1,76 @@
+'use client';
+
+import clsx from 'clsx';
+import { useRouter } from 'next/navigation';
+
+import Text from '@/components/common/server/text';
+
+import { useToast } from '@/context/common/toast-context';
+import { useReview } from '@/context/review/review-context';
+import { useEditorRef } from '@/context/editor/editor-ref-context';
+
+import { createReview } from '@/lib/apis/review/client';
+import { validateReviewFields } from '@/lib/utils/review/validate-review-fields';
+import { isErrorWithMessage } from '@/lib/utils/common/error';
+
+export function CreateReviewButton() {
+ const router = useRouter();
+ const { emitToast } = useToast();
+
+ const { title, movieName, movieNameRef, titleRef, disabled, setDisabled } = useReview();
+ const { editorRef } = useEditorRef() ?? {};
+
+ const handleClick = () => {
+ setDisabled(true);
+
+ const editorState = editorRef?.current?.getEditorState();
+
+ if (editorState) {
+ const validatedFields = validateReviewFields({ title, movieName, editorState });
+
+ if (!validatedFields.success) {
+ // Order to check should be aligned with the order of elements in the DOM
+ // title -> movieName -> editor
+ if (validatedFields.errors.title) {
+ titleRef.current?.focus();
+ emitToast(validatedFields.errors.title, 'error');
+ } else if (validatedFields.errors.movieName) {
+ movieNameRef.current?.focus();
+ emitToast(validatedFields.errors.movieName, 'error');
+ } else {
+ editorRef?.current?.focus();
+ emitToast(validatedFields.errors.content!, 'error');
+ }
+
+ setDisabled(false);
+ return;
+ }
+
+ void createReview(validatedFields.data)
+ .then(({ review }) => {
+ emitToast('리뷰 등록 완료', 'success');
+ router.push(`/review/${review.id}`);
+ })
+ .catch((error) => {
+ if (isErrorWithMessage(error)) {
+ emitToast(error.message, 'error');
+ } else {
+ // TODO: throw new Error and move to global error handler
+ console.error(error);
+ emitToast('알 수 없는 에러가 발생했습니다. 다시 시도해주세요.', 'error');
+ }
+ })
+ .finally(() => setDisabled(false));
+ }
+ };
+
+ const className = clsx(' rounded-full border bg-white px-2 py-1', {
+ 'pointer-events-none': disabled,
+ });
+
+ return (
+
+ );
+}
diff --git a/ui/src/lib/utils/common/error.ts b/ui/src/lib/utils/common/error.ts
new file mode 100644
index 00000000..0d5ead46
--- /dev/null
+++ b/ui/src/lib/utils/common/error.ts
@@ -0,0 +1,12 @@
+export interface ErrorWithMessage {
+ message: string;
+}
+
+export function isErrorWithMessage(error: unknown): error is ErrorWithMessage {
+ return (
+ typeof error === 'object' &&
+ error !== null &&
+ 'message' in error &&
+ typeof error.message === 'string'
+ );
+}
From 9e0166b9e5f90a8e7298ee87beb68c6aeedddc8e Mon Sep 17 00:00:00 2001
From: wnhlee <2wheeh@gmail.com>
Date: Mon, 11 Mar 2024 23:31:05 +0900
Subject: [PATCH 2/2] fixup: CreateReviewButton
---
.../review/client/create-review-button.tsx | 108 +++++++++++-------
1 file changed, 66 insertions(+), 42 deletions(-)
diff --git a/ui/src/components/review/client/create-review-button.tsx b/ui/src/components/review/client/create-review-button.tsx
index 337a6e1b..50e1418a 100644
--- a/ui/src/components/review/client/create-review-button.tsx
+++ b/ui/src/components/review/client/create-review-button.tsx
@@ -20,56 +20,80 @@ export function CreateReviewButton() {
const { title, movieName, movieNameRef, titleRef, disabled, setDisabled } = useReview();
const { editorRef } = useEditorRef() ?? {};
- const handleClick = () => {
- setDisabled(true);
+ const validateAndGetData = () => {
+ if (!editorRef?.current) {
+ return;
+ }
+
+ const editorState = editorRef.current.getEditorState();
+
+ const validatedFields = validateReviewFields({ title, movieName, editorState });
- const editorState = editorRef?.current?.getEditorState();
-
- if (editorState) {
- const validatedFields = validateReviewFields({ title, movieName, editorState });
-
- if (!validatedFields.success) {
- // Order to check should be aligned with the order of elements in the DOM
- // title -> movieName -> editor
- if (validatedFields.errors.title) {
- titleRef.current?.focus();
- emitToast(validatedFields.errors.title, 'error');
- } else if (validatedFields.errors.movieName) {
- movieNameRef.current?.focus();
- emitToast(validatedFields.errors.movieName, 'error');
- } else {
- editorRef?.current?.focus();
- emitToast(validatedFields.errors.content!, 'error');
- }
-
- setDisabled(false);
- return;
+ if (!validatedFields.success) {
+ // Order to check should be aligned with the order of elements in the DOM
+ // title -> movieName -> editor
+ if (validatedFields.errors.title) {
+ titleRef.current?.focus();
+ emitToast(validatedFields.errors.title, 'error');
+ } else if (validatedFields.errors.movieName) {
+ movieNameRef.current?.focus();
+ emitToast(validatedFields.errors.movieName, 'error');
+ } else {
+ editorRef?.current?.focus();
+ emitToast(validatedFields.errors.content!, 'error');
}
- void createReview(validatedFields.data)
- .then(({ review }) => {
- emitToast('리뷰 등록 완료', 'success');
- router.push(`/review/${review.id}`);
- })
- .catch((error) => {
- if (isErrorWithMessage(error)) {
- emitToast(error.message, 'error');
- } else {
- // TODO: throw new Error and move to global error handler
- console.error(error);
- emitToast('알 수 없는 에러가 발생했습니다. 다시 시도해주세요.', 'error');
- }
- })
- .finally(() => setDisabled(false));
+ setDisabled(false);
+ return;
+ }
+
+ return validatedFields.data;
+ };
+
+ const handleApiError = (error: unknown) => {
+ if (isErrorWithMessage(error)) {
+ emitToast(error.message, 'error');
+ } else {
+ // TODO: throw new Error and move to global error handler
+ console.error(error);
+ emitToast('알 수 없는 에러가 발생했습니다. 다시 시도해주세요.', 'error');
+ }
+ };
+
+ const handleCreateReview = async () => {
+ setDisabled(true);
+
+ const data = validateAndGetData();
+
+ if (!data) {
+ return;
+ }
+
+ try {
+ const { review } = await createReview(data);
+
+ emitToast('리뷰 등록 완료', 'success');
+ router.push(`/review/${review.id}`);
+ } catch (error) {
+ handleApiError(error);
+ } finally {
+ setDisabled(false);
}
};
- const className = clsx(' rounded-full border bg-white px-2 py-1', {
- 'pointer-events-none': disabled,
- });
+ const handleClick = () => {
+ void handleCreateReview();
+ };
return (
-