diff --git a/README.md b/README.md
index 2c917be3d..2970e4d20 100644
--- a/README.md
+++ b/README.md
@@ -78,3 +78,13 @@ state 관리하고 있는 params과 usePageSize로 관리하는 pageSize의 변
- url주소 기반으로 처리하는 useFetch를 쓰게되면, 만약에 api url이 변경되거나 요청전 작업이 수정되면 수정하러 이곳저곳을 돌아다녀야 할 것 같음.
- 비동기 요청함수를 받아서 처리하는 훅으로 만들어둔 useAsync를 쓰는게 더 적합해보임
- 주소를 기반으로 fetch만을 처리하는 훅으로 useFetch를 수정하고 남겨두기로 결정
+
+#### axios와 abortcontroller 사용기
+
+- fetch와 axios는 abort될시에 error name이 다르다.
+- 기존 fetch에서는 'AbortError'로 예외처리를 했는데, axios에서는 'CanceledError'로 예외처리를 해야했음.
+
+#### react router의 loader를 사용할시 추가로 설정해줘야했던 설정들
+
+- React의 Suspense와 fallback, React router의 loader, hydrateFallback을 조사해보고 정리해보았습니다.
+- https://heavy-bear.tistory.com/13
diff --git a/package-lock.json b/package-lock.json
index a8a5ee206..b896223fb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,10 @@
"name": "1-weekly-mission",
"version": "0.1.0",
"dependencies": {
+ "axios": "^1.7.8",
"clsx": "^2.1.1",
+ "dayjs": "^1.11.13",
+ "jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -1393,6 +1396,23 @@
"vite": "^4.2.0 || ^5.0.0"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.7.8",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
+ "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
@@ -1486,6 +1506,18 @@
"node": ">=6"
}
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -1493,6 +1525,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/dayjs": {
+ "version": "1.11.13",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@@ -1511,6 +1549,15 @@
}
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@@ -1595,6 +1642,40 @@
"node": ">=8"
}
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+ "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -1705,6 +1786,15 @@
"node": ">=6"
}
},
+ "node_modules/jwt-decode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+ "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -1748,6 +1838,27 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -1839,6 +1950,12 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
diff --git a/package.json b/package.json
index 34cfb71bd..d3161d571 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,10 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "axios": "^1.7.8",
"clsx": "^2.1.1",
+ "dayjs": "^1.11.13",
+ "jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
diff --git a/src/assets/img/icon/icon_back.svg b/src/assets/img/icon/icon_back.svg
new file mode 100644
index 000000000..9ba5ad945
--- /dev/null
+++ b/src/assets/img/icon/icon_back.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/img/icon/icon_dots.svg b/src/assets/img/icon/icon_dots.svg
new file mode 100644
index 000000000..63a0344c3
--- /dev/null
+++ b/src/assets/img/icon/icon_dots.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/img/icon/icon_heart_fill.svg b/src/assets/img/icon/icon_heart_fill.svg
new file mode 100644
index 000000000..91aded985
--- /dev/null
+++ b/src/assets/img/icon/icon_heart_fill.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/img/icon/icon_inquiry_empty.svg b/src/assets/img/icon/icon_inquiry_empty.svg
new file mode 100644
index 000000000..5444cbbbc
--- /dev/null
+++ b/src/assets/img/icon/icon_inquiry_empty.svg
@@ -0,0 +1,17 @@
+
diff --git a/src/assets/scss/base/_variables.scss b/src/assets/scss/base/_variables.scss
index 76c2dfb75..8a730c130 100644
--- a/src/assets/scss/base/_variables.scss
+++ b/src/assets/scss/base/_variables.scss
@@ -14,6 +14,9 @@
--color-secondary-100: #f3f4f6;
--color-secondary-50: #f9fafb;
--color-error: #f74747;
+ --color-error-100: #f74747;
+ --color-error-200: #da2d2d;
+ --color-error-300: #bd2020;
--color-white: #fff;
--color-grey: #dfdfdf;
--color-black: #4b5563;
diff --git a/src/assets/scss/common/_index.scss b/src/assets/scss/common/_index.scss
deleted file mode 100644
index 3b9a7c4f3..000000000
--- a/src/assets/scss/common/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@forward "input";
diff --git a/src/assets/scss/common/_input.scss b/src/assets/scss/common/_input.scss
deleted file mode 100644
index 76ae5927a..000000000
--- a/src/assets/scss/common/_input.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-.field {
- position: relative;
-
- .field-box {
- display: block;
- width: 100%;
- height: 5.6rem;
- padding: 0 2.4rem;
- border: 1px solid transparent;
- border-radius: 12px;
- background: var(--color-secondary-100);
-
- &:focus,
- &.valid {
- border: 1px solid var(--color-primary-100);
- outline: none;
- }
-
- &.error {
- border: 1px solid var(--color-error);
- outline: none;
- }
-
- &::placeholder {
- color: var(--color-secondary-400);
- }
-
- &:has(~ button) {
- padding-right: 5rem;
- }
- }
-
- textarea.field-box {
- height: 28.2rem;
- padding: 2.4rem;
- resize: none;
- }
-}
diff --git a/src/assets/scss/style.scss b/src/assets/scss/style.scss
index c09ad6faf..271103ab3 100644
--- a/src/assets/scss/style.scss
+++ b/src/assets/scss/style.scss
@@ -1,2 +1 @@
@use "base";
-@use "common";
diff --git a/src/components/Button/BackToList.jsx b/src/components/Button/BackToList.jsx
new file mode 100644
index 000000000..b7d49eeba
--- /dev/null
+++ b/src/components/Button/BackToList.jsx
@@ -0,0 +1,13 @@
+import { Button } from "@components/ui";
+import iconBack from "@assets/img/icon/icon_back.svg";
+import styles from "./BackToList.module.scss";
+
+export function BackToList() {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/Button/BackToList.module.scss b/src/components/Button/BackToList.module.scss
new file mode 100644
index 000000000..eb5e3e7a6
--- /dev/null
+++ b/src/components/Button/BackToList.module.scss
@@ -0,0 +1,7 @@
+.controls {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 1rem;
+ margin: 4rem 0;
+}
diff --git a/src/components/Button/More.jsx b/src/components/Button/More.jsx
new file mode 100644
index 000000000..8d454d949
--- /dev/null
+++ b/src/components/Button/More.jsx
@@ -0,0 +1,22 @@
+import { Dropdown } from "@components/ui";
+import dotsIcon from "@assets/img/icon/icon_dots.svg";
+import styles from "./More.module.scss";
+
+export function More({ options }) {
+ return (
+
+
+
+
+
+
+
+ {options.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/Button/More.module.scss b/src/components/Button/More.module.scss
new file mode 100644
index 000000000..cefe02487
--- /dev/null
+++ b/src/components/Button/More.module.scss
@@ -0,0 +1,11 @@
+.icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 3rem;
+ height: 3rem;
+ border-radius: 4px;
+ &:hover {
+ background: var(--color-secondary-100);
+ }
+}
diff --git a/src/components/Button/index.js b/src/components/Button/index.js
new file mode 100644
index 000000000..6c091c715
--- /dev/null
+++ b/src/components/Button/index.js
@@ -0,0 +1,2 @@
+export * from "./BackToList";
+export * from "./More";
diff --git a/src/components/Comment/Comment.jsx b/src/components/Comment/Comment.jsx
new file mode 100644
index 000000000..c83f8414b
--- /dev/null
+++ b/src/components/Comment/Comment.jsx
@@ -0,0 +1,65 @@
+import { useState } from "react";
+import { useAuth } from "@context/AuthContext";
+import useComment from "./useComment";
+import { Author } from "@components/ui";
+import { More } from "@components/Button";
+import { CommentForm } from ".";
+import styles from "./Comment.module.scss";
+
+export function Comment({ name, comment }) {
+ const {
+ auth: { user },
+ } = useAuth();
+ const [isModify, setIsModify] = useState(false);
+ const {
+ content,
+ updatedAt,
+ writer: { nickname, image, id: writerId },
+ } = comment;
+ const { handleUpdate, handleDelete } = useComment(name, comment);
+
+ function handleModify() {
+ if (user?.id !== writerId) {
+ return alert("작성자만 수정이 가능합니다.");
+ }
+ setIsModify(true);
+ }
+
+ function handleClose() {
+ setIsModify(false);
+ }
+
+ if (isModify) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/Comment/Comment.module.scss b/src/components/Comment/Comment.module.scss
new file mode 100644
index 000000000..62947252c
--- /dev/null
+++ b/src/components/Comment/Comment.module.scss
@@ -0,0 +1,42 @@
+.item {
+ margin-bottom: 2.4rem;
+ padding-bottom: 2.4rem;
+ border-bottom: 1px solid var(--color-secondary-200);
+
+ .form {
+ margin-bottom: -4rem;
+ }
+
+ .comment {
+ display: flex;
+ gap: 2rem;
+
+ .content {
+ flex: 1;
+
+ .text {
+ margin-bottom: 2.4rem;
+ white-space: pre-wrap;
+ }
+
+ .footer {
+ display: flex;
+ justify-content: space-between;
+
+ .controls {
+ display: flex;
+ gap: 0.4rem;
+ }
+ }
+ }
+
+ .actions {
+ flex: 0 0 2.4rem;
+ }
+
+ textarea {
+ height: 8rem;
+ margin-bottom: 1.6rem;
+ }
+ }
+}
diff --git a/src/components/Comment/CommentAdd.jsx b/src/components/Comment/CommentAdd.jsx
new file mode 100644
index 000000000..3fc0fd404
--- /dev/null
+++ b/src/components/Comment/CommentAdd.jsx
@@ -0,0 +1,7 @@
+import { CommentForm } from ".";
+import useComment from "./useComment";
+
+export function CommentAdd({ name }) {
+ const { handleSubmit } = useComment(name);
+ return ;
+}
diff --git a/src/components/Comment/CommentForm.jsx b/src/components/Comment/CommentForm.jsx
new file mode 100644
index 000000000..f3c542ad7
--- /dev/null
+++ b/src/components/Comment/CommentForm.jsx
@@ -0,0 +1,86 @@
+import useForm from "@hooks/useForm";
+import { FieldItem, Form, Textarea } from "@components/Field";
+import { Section } from "@components/Section";
+import { Author, Button } from "@components/ui";
+import { commentSchema } from "./schema";
+import styles from "./CommentForm.module.scss";
+
+export function CommentForm({
+ initialData = {},
+ onCommentSubmit,
+ onClose,
+ isEdit,
+}) {
+ const { formError, isFormValid, isLoading, handleSubmit, register, reset } =
+ useForm(commentSchema, initialData);
+
+ function handleClose() {
+ reset();
+ onClose();
+ }
+
+ const message = isEdit
+ ? "성공적으로 수정했습니다."
+ : "성공적으로 작성했습니다.";
+
+ async function onSubmit(data) {
+ try {
+ await onCommentSubmit(data);
+ alert(message);
+ window.location.reload();
+ } catch (err) {
+ throw err;
+ }
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/Comment/CommentForm.module.scss b/src/components/Comment/CommentForm.module.scss
new file mode 100644
index 000000000..5fa0bed5d
--- /dev/null
+++ b/src/components/Comment/CommentForm.module.scss
@@ -0,0 +1,10 @@
+.meta {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+
+ .controls {
+ display: flex;
+ gap: 1rem;
+ }
+}
diff --git a/src/components/Comment/CommentList.jsx b/src/components/Comment/CommentList.jsx
new file mode 100644
index 000000000..ceb647326
--- /dev/null
+++ b/src/components/Comment/CommentList.jsx
@@ -0,0 +1,46 @@
+import useComments from "./useComments";
+import { Message } from "@components/ui";
+import { Comment } from "./Comment";
+import emptyIcon from "@assets/img/icon/icon_inquiry_empty.svg";
+import styles from "./CommentList.module.scss";
+
+export function CommentList({ name, comments: initialComments }) {
+ const { comments, handleLoad, isLoading, error } = useComments(
+ name,
+ initialComments
+ );
+ const { list, nextCursor } = comments;
+
+ return (
+
+ {list.length === 0 ? (
+
+ 아직 문의가 없어요
+
+ ) : (
+
+ {list?.map((comment) => (
+
+ ))}
+
+ )}
+ {error &&
{error.message}}
+
+ {nextCursor && (
+
+ {isLoading ? (
+ 문의를 더 불러오고 있습니다.
+ ) : (
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/Comment/CommentList.module.scss b/src/components/Comment/CommentList.module.scss
new file mode 100644
index 000000000..349a00689
--- /dev/null
+++ b/src/components/Comment/CommentList.module.scss
@@ -0,0 +1,19 @@
+.comments {
+ .control {
+ display: flex;
+ justify-content: center;
+ }
+
+ .button {
+ padding: 1rem 2rem;
+ border-radius: 9999px;
+ background: #eee;
+ font-size: 1.4rem;
+ color: var(--color-secondary-500);
+
+ &:hover {
+ background: #ddd;
+ color: var(--color-secondary-700);
+ }
+ }
+}
diff --git a/src/components/Comment/index.js b/src/components/Comment/index.js
new file mode 100644
index 000000000..64e566826
--- /dev/null
+++ b/src/components/Comment/index.js
@@ -0,0 +1,4 @@
+export * from "./Comment";
+export * from "./CommentList";
+export * from "./CommentAdd";
+export * from "./CommentForm";
diff --git a/src/components/Comment/schema.js b/src/components/Comment/schema.js
new file mode 100644
index 000000000..000816141
--- /dev/null
+++ b/src/components/Comment/schema.js
@@ -0,0 +1,10 @@
+import { COMMENT_VALIDATION_MESSAGE as MESSAGE } from "@util/validation";
+
+export const commentSchema = {
+ content: {
+ value: "",
+ rule: {
+ required: MESSAGE.COMMENT_REQUIRED,
+ },
+ },
+};
diff --git a/src/components/Comment/useComment.js b/src/components/Comment/useComment.js
new file mode 100644
index 000000000..aa3ef1d88
--- /dev/null
+++ b/src/components/Comment/useComment.js
@@ -0,0 +1,49 @@
+import { useParams } from "react-router-dom";
+import { addComment, removeComment, updateComment } from "@service/comments";
+import { useAuth } from "@context/AuthContext";
+
+export default function useComment(name, comment = {}) {
+ const {
+ auth: { user },
+ } = useAuth();
+ const { id: productId } = useParams();
+ const isOwner = user?.id === comment.writer?.id;
+
+ async function handleSubmit(data) {
+ try {
+ await addComment(name, productId, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+
+ async function handleUpdate(data) {
+ if (!isOwner) {
+ return alert("작성자만 수정이 가능합니다.");
+ }
+
+ try {
+ await updateComment(comment.id, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+
+ async function handleDelete() {
+ if (!isOwner) {
+ return alert("작성자만 삭제가 가능합니다.");
+ }
+
+ if (confirm("정말 삭제할까요?")) {
+ try {
+ await removeComment(comment.id);
+ alert("문의를 삭제했습니다.");
+ window.location.reload();
+ } catch (err) {
+ console.log(err);
+ }
+ }
+ }
+
+ return { handleSubmit, handleUpdate, handleDelete };
+}
diff --git a/src/components/Comment/useComments.js b/src/components/Comment/useComments.js
new file mode 100644
index 000000000..0b562383b
--- /dev/null
+++ b/src/components/Comment/useComments.js
@@ -0,0 +1,29 @@
+import { useState } from "react";
+import { useParams } from "react-router-dom";
+import useAsync from "@hooks/useAsync";
+import { getComments } from "@service/comments";
+
+export default function useComments(name, initialComments = {}) {
+ const { id: productId } = useParams();
+ const [comments, setComments] = useState(initialComments);
+ const { isLoading, error, wrappedFn: getData } = useAsync(getComments);
+
+ async function handleLoad() {
+ try {
+ const { list, nextCursor: newCursor } = await getData(name, {
+ productId,
+ cursor: comments.nextCursor,
+ });
+
+ setComments((prev) => ({
+ ...prev,
+ nextCursor: newCursor,
+ list: [...prev.list, ...list],
+ }));
+ } catch (err) {
+ console.log(err);
+ }
+ }
+
+ return { comments, handleLoad, isLoading, error };
+}
diff --git a/src/components/Field/Error.module.scss b/src/components/Field/Error.module.scss
index 4d6717a64..cbd219cad 100644
--- a/src/components/Field/Error.module.scss
+++ b/src/components/Field/Error.module.scss
@@ -3,5 +3,5 @@
padding-left: 1.6rem;
font-size: 1.4rem;
font-weight: 600;
- color: var(--color-error);
+ color: var(--color-error-100);
}
diff --git a/src/components/Field/Input.jsx b/src/components/Field/Input.jsx
index 329c070cb..6ed1b2f0c 100644
--- a/src/components/Field/Input.jsx
+++ b/src/components/Field/Input.jsx
@@ -8,6 +8,11 @@ import styles from "./Input.module.scss";
export function Input({ type = "text", error, value, ...props }) {
const [currentType, setCurrentType] = useState(type);
const valid = value && !error;
+ const css = clsx(
+ styles["field-box"],
+ valid && styles.valid,
+ error && styles.error
+ );
function handleVisibility() {
setCurrentType((prev) => (prev === "password" ? "text" : "password"));
@@ -15,13 +20,8 @@ export function Input({ type = "text", error, value, ...props }) {
return (
<>
-
-
+
+
{type === "password" && (