From db46bf1c9a7ac0e5e0537b0b5293e2e0d4795b17 Mon Sep 17 00:00:00 2001 From: chchaeun Date: Sun, 9 Oct 2022 19:16:35 +0900 Subject: [PATCH 01/15] =?UTF-8?q?Feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=ED=99=95=EC=9D=B8=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20(LEAR-318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/password-notice.tsx | 77 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 pages/password-notice.tsx diff --git a/pages/password-notice.tsx b/pages/password-notice.tsx new file mode 100644 index 0000000..c695f8e --- /dev/null +++ b/pages/password-notice.tsx @@ -0,0 +1,77 @@ +import React from "react"; +import { SubmitHandler, useForm } from "react-hook-form"; +import HeadTitle from "../components/common/Title"; + +type PasswordValidForm = { + password: string; + passwordConform: string; +}; + +function PasswordNoticePage() { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + const onPasswordValid: SubmitHandler = async (data) => { + const { password, passwordConform } = data; + + if (password === passwordConform) { + // mutatePasswordChange(data); + } else { + alert("비밀번호와 비밀번호 확인이 다릅니다."); + } + }; + + return ( + <> + +
+

로그인 방식이 변경됩니다.

+

+ 회원님들의 소중한 개인정보를 보호하기 위해 로그인 방식이 변경됩니다. + 다음 로그인부터는 안내드린 이메일과 함께 새 비밀번호로 로그인해주시길 + 바랍니다. 감사합니다. +

+
+
+ + +
+ +
+
+ + ); +} + +export default PasswordNoticePage; From abeb514b67bbaabb643f232cd94ee8909bbb49b3 Mon Sep 17 00:00:00 2001 From: chchaeun Date: Mon, 10 Oct 2022 20:16:41 +0900 Subject: [PATCH 02/15] =?UTF-8?q?Feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B3=B4=EC=95=88=20=EA=B6=8C=EC=9E=A5=20=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EA=B5=AC=ED=98=84=20(LEAR-325)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/password-notice.tsx | 104 +++++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 19 deletions(-) diff --git a/pages/password-notice.tsx b/pages/password-notice.tsx index c695f8e..fc9b0fe 100644 --- a/pages/password-notice.tsx +++ b/pages/password-notice.tsx @@ -1,23 +1,44 @@ -import React from "react"; +import { Icon } from "@iconify/react"; +import React, { useState } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; import HeadTitle from "../components/common/Title"; type PasswordValidForm = { password: string; - passwordConform: string; + passwordConfirm: string; }; function PasswordNoticePage() { + const inputType = { + password: "password", + text: "text", + }; + const [showPassword, setShowPassword] = useState(false); + const [showPasswordConfirm, setShowPasswordConfirm] = useState(false); + + const onShowPasswordClick = () => { + setShowPassword((prev) => !prev); + }; + + const onShowPasswordConfirmClick = () => { + setShowPasswordConfirm((prev) => !prev); + }; + const { register, handleSubmit, formState: { errors }, + watch, } = useForm(); + const passwordField = watch("password"); + + console.log(passwordField); + const onPasswordValid: SubmitHandler = async (data) => { - const { password, passwordConform } = data; + const { password, passwordConfirm } = data; - if (password === passwordConform) { + if (password === passwordConfirm) { // mutatePasswordChange(data); } else { alert("비밀번호와 비밀번호 확인이 다릅니다."); @@ -41,25 +62,70 @@ function PasswordNoticePage() {
+ + + )} + + )} + {quizDetail?.quizExplanation && ( + + 해설 + onInputFocus("explanation")} + /> + {isExplanationFocus && ( + + + + + )} + + )} + {quizDetail?.quizRubric && ( + + + 채점 기준 + {!rubricEdit && ( + + )} + + {!rubricEdit && + quizDetail?.quizRubric?.map((rubric, index) => ( + + {rubric.content} + {rubric.score}점 + + ))} + {rubricEdit && ( +
+ {fields.map((item, index) => ( + + + + + + ))} + + {errors.rubric && errors.rubric[0]?.score?.message} + + + {errors.rubric && errors.rubric[0]?.content?.message} + +
+ +
+
+ )} + {rubricEdit && ( + + + + + )} +
+ )} + + + ); +} + +export default MyQuizAnswerBlock; + +const Container = styled.div` + display: flex; + flex-direction: column; + + width: 100%; +`; + +const Title = styled.h2` + font-weight: 700; + font-size: 16px; + line-height: 19px; + color: #111827; +`; + +const ContentBox = styled.div` + box-sizing: border-box; + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 16px; + gap: 10px; + + width: 100%; + background: #ffffff; + + border: 1px solid #e5e7eb; + border-radius: 8px; +`; + +const Form = styled.form` + display: flex; + flex-direction: column; + gap: 32px; +`; + +const ColDiv = styled.div` + display: flex; + flex-direction: column; + gap: 10px; +`; +const RowDiv = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + + button { + font-size: 14px; + + color: #8f8f8f; + } +`; +const InputBox = styled.textarea` + box-sizing: border-box; + + display: flex; + align-items: flex-start; + padding: 16px; + + width: 100%; + + background: #ffffff; + + border: 1px solid #e5e7eb; + border-radius: 8px; + + font-weight: 400; + font-size: 14px; + line-height: 21px; + + resize: none; + + &:focus { + outline: none !important; + border: 1px solid #6366f1; + } +`; + +const Buttons = styled.div` + display: flex; + justify-content: flex-end; + gap: 4px; + + width: 100%; + + button { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 10px 24px; + gap: 6px; + + width: 72px; + height: 36px; + + border-radius: 8px; + + font-weight: 500; + font-size: 12px; + line-height: 16px; + + &:first-child { + background: #ffffff; + + &:hover { + background: #f9fafb; + } + } + &:last-child { + background: #4f46e5; + color: #ffffff; + + &:hover { + background: #4338ca; + } + } + } +`; + +const ContentText = styled.span` + font-weight: 500; + font-size: 14px; + line-height: 17px; + + color: #000000; +`; + +const AccentText = styled.span` + font-weight: 700; + font-size: 14px; + + color: #4f46e5; +`; + +const RubricBlock = styled.div` + display: flex; + gap: 8px; + width: 100%; + + margin: 8px 0px; + + input { + display: flex; + align-items: flex-start; + padding: 16px; + + background: #ffffff; + + border: 1px solid #e5e7eb; + border-radius: 8px; + + font-weight: 400; + font-size: 14px; + line-height: 17px; + + &:focus { + outline: none !important; + border: 1px solid #6366f1; + } + &:first-child { + width: 90%; + } + &:last-child { + width: 5%; + } + } + button { + display: flex; + align-items: center; + padding: 0px 4px; + + width: 5%; + + font-size: 26px; + } +`; diff --git a/components/quiz-detail/blocks/QuizAnswerBlock.tsx b/components/quiz-detail/blocks/QuizAnswerBlock.tsx new file mode 100644 index 0000000..a46a7e1 --- /dev/null +++ b/components/quiz-detail/blocks/QuizAnswerBlock.tsx @@ -0,0 +1,123 @@ +import { Icon } from "@iconify/react"; +import { useRouter } from "next/router"; +import React, { useState } from "react"; +import styled from "styled-components"; +import { useQuizDetailQuery } from "../../../api/quizzes/hooks"; +import ContentsFormat from "../../../utils/ContentsFormat"; +import QuizRubric from "./QuizRubric"; +interface Props { + challengeId: string; + quizId: string; +} + +function QuizAnswerBlock({ challengeId, quizId }: Props) { + const { data: quizDetail } = useQuizDetailQuery({ quizId }); + + const [showAnswer, setShowAnswer] = useState(false); + + const onAnswerClick = () => { + if (quizDetail?.solveAnswer) { + setShowAnswer((prev) => !prev); + } else { + alert("정답을 제출해야 열람할 수 있습니다."); + } + }; + + return ( + + {!showAnswer && 정답 확인} + {quizDetail && showAnswer && ( + <> + + 정답 확인 + + + + + + 해설 + + + + + + + )} + + + {showAnswer ? ( + <> + 닫기 + + + ) : ( + <> + 정답 보기 + + + )} + + + ); +} + +export default QuizAnswerBlock; + +const Container = styled.div` + display: flex; + flex-direction: column; + gap: 32px; + + width: 100%; +`; +const Block = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; +const Title = styled.h2` + font-weight: 700; + font-size: 16px; + line-height: 19px; + color: #111827; +`; + +const ContentBox = styled.div` + box-sizing: border-box; + + display: flex; + flex-direction: row; + align-items: flex-start; + padding: 16px; + gap: 10px; + + width: 100%; + background: #ffffff; + + border: 1px solid #e5e7eb; + border-radius: 8px; +`; + +const LongButton = styled.button` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 10px 24px; + gap: 6px; + + width: 100%; + + background: #f3f4f6; + border-radius: 8px; + + font-weight: 600; + font-size: 12px; + line-height: 16px; + + color: #4b5563; + + &:hover { + background: #e5e7eb; + } +`; diff --git a/components/quiz-detail/blocks/QuizComment.tsx b/components/quiz-detail/blocks/QuizComment.tsx index 3355122..01632dd 100644 --- a/components/quiz-detail/blocks/QuizComment.tsx +++ b/components/quiz-detail/blocks/QuizComment.tsx @@ -1,8 +1,10 @@ +import { Icon } from "@iconify/react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import React from "react"; +import React, { useState } from "react"; +import styled from "styled-components"; import { deleteComment } from "../../../api/comments"; -import api from "../../../api/my-api"; import { Comment } from "../../../types/Comment"; +import ContentsFormat from "../../../utils/ContentsFormat"; import { getDateFormat } from "../../../utils/DateFormat"; interface Props { quizId: string; @@ -10,6 +12,9 @@ interface Props { } function QuizComment({ quizId, comment }: Props) { const queryClient = useQueryClient(); + + const [showDropdown, setShowDropdown] = useState(false); + const { commentId, commentContent, @@ -32,39 +37,173 @@ function QuizComment({ quizId, comment }: Props) { mutateCommentDelete(commentId); }; + const onDropdownClick = () => { + setShowDropdown((prev) => !prev); + }; + return ( -
-
-
- {writerName} - {isQuizWriter && ( - 작성자 + + + + + + + <RowDiv> + <span>{writerName}</span> + {isQuizWriter && ( + <span className="text-sm text-[#5c3cde]">작성자</span> + )} + <span className="text-sm text-gray-600"> + {getDateFormat(commentCreatedDate)} + </span> + </RowDiv> + {isMine && ( + <DropdownBlock> + <ThreeDotsButton clicked={showDropdown}> + <Icon icon="bi:three-dots-vertical" onClick={onDropdownClick} /> + </ThreeDotsButton> + {showDropdown && ( + <Dropdown> + <DeleteButton + type="button" + onClick={() => onDeleteClick(commentId)} + > + 삭제 + </DeleteButton> + </Dropdown> + )} + </DropdownBlock> )} - </div> - {isMine && ( - <button - type="button" - onClick={() => onDeleteClick(commentId)} - className="text-sm text-gray-700 hover:underline" - > - 삭제 - </button> - )} - </div> - - <p> - {commentContent?.split("\n").map((content, index) => ( - <span key={index}> - {content} - <br /> - </span> - ))} - </p> - <span className="text-sm text-gray-600"> - {getDateFormat(commentCreatedDate)} - </span> - </div> + + + + ); } export default QuizComment; + +const Container = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + padding: 0px; + gap: 12px; + + width: 100%; +`; + +const Block = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + + width: 100%; + + p { + font-weight: 400; + font-size: 14px; + line-height: 17px; + + color: #111827; + } +`; + +const RoundProfile = styled.span` + display: flex; + justify-content: center; + align-items: center; + + width: 42px; + height: 42px; + + border-radius: 100%; + + background: #e5e7eb; +`; + +const Title = styled.div` + display: flex; + justify-content: space-between; + align-items: flex-start; + + width: 100%; +`; + +const RowDiv = styled.div` + display: flex; + align-items: center; + gap: 8px; + + span { + &:first-child { + font-weight: 600; + font-size: 14px; + line-height: 17px; + + color: #111827; + } + &:last-child { + font-weight: 400; + font-size: 14px; + line-height: 17px; + + color: #6b7280; + } + } +`; +const DropdownBlock = styled.div` + display: flex; + justify-content: flex-end; +`; +const ThreeDotsButton = styled.button<{ clicked: boolean }>` + display: flex; + align-items: center; + justify-content: center; + + width: 20px; + height: 20px; + + background: ${(p) => (p.clicked ? "#f3f4f6" : "#ffffff")}; + + &:hover { + background: #f3f4f6; + mix-blend-mode: multiply; + border-radius: 2px; + } +`; +const Dropdown = styled.span` + display: flex; + flex-direction: column; + align-items: center; + padding: 4px 0px; + + position: absolute; + width: 72px; + margin: 20px 0px; + + background: #ffffff; + + border: 1px solid #f3f4f6; + box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.1); + border-radius: 6px; +`; + +const DeleteButton = styled.button` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + width: 100%; + + padding: 8px 12px; + font-weight: 400; + font-size: 14px; + line-height: 17px; + color: #ef4444; + + &:hover { + background: #f3f4f6; + } +`; diff --git a/components/quiz-detail/blocks/QuizCommentForm.tsx b/components/quiz-detail/blocks/QuizCommentForm.tsx index 243fda2..b940d14 100644 --- a/components/quiz-detail/blocks/QuizCommentForm.tsx +++ b/components/quiz-detail/blocks/QuizCommentForm.tsx @@ -1,6 +1,8 @@ +import { Icon } from "@iconify/react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import React, { useState } from "react"; import { useForm } from "react-hook-form"; +import styled from "styled-components"; import { updateComment } from "../../../api/comments"; interface Props { quizId: string; @@ -11,7 +13,18 @@ type CommentValidForm = { function QuizCommentForm({ quizId }: Props) { const queryClient = useQueryClient(); - const { register, handleSubmit, resetField } = useForm(); + const { + register, + handleSubmit, + resetField, + watch, + reset, + formState: { errors }, + } = useForm(); + + const commentRef = watch("comment"); + + const [isFocus, setIsFocus] = useState(false); const [disabledSubmit, setDisabledSubmit] = useState(false); const { mutate: mutateCommentSubmit } = useMutation( @@ -44,43 +57,168 @@ function QuizCommentForm({ quizId }: Props) { mutateCommentSubmit(commentBody); }; + const onInputFocus = () => { + setIsFocus(true); + }; + + const onCancelClick = () => { + if (commentRef) { + const cancelConfirm = confirm( + "작성한 내용이 사라집니다. 취소하시겠습니까?" + ); + + if (cancelConfirm) { + reset(); + } else { + return; + } + } else { + reset(); + } + setIsFocus(false); + }; + return ( -
-