From bd122410cb32b6ac10acdadbf4c361682fad0472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=84=9C=ED=98=84?= Date: Thu, 5 Sep 2024 02:07:39 +0900 Subject: [PATCH] =?UTF-8?q?[Chore]=20=EB=82=98=EC=9D=98=20=EC=8A=A4?= =?UTF-8?q?=ED=84=B0=EB=94=94=20=ED=8E=98=EC=9D=B4=EC=A7=80=20QA=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20(#128)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 출석체크 에러 상태 반영 못하는 부분 수정 * fix: 출석 번호 trim 추가 * fix: 출석번호 textarea 줄바꿈 금지 * chore: 메타데이터 타이틀 수정 * design: 제출한 과제 확인 버튼 줄바꿈 안 되도록 수정 * chore: 오늘의 할 일 과제 박스 버튼 비활성화 상태 삭제 * chore: 오늘의 할 일 맨 마지막에 도달 시 캐러셀 버튼 안 보이도록 수정 * chore: 출석체크 모달에서 api fetch 대신 query parameter로 처리하도록 수정 * design: 출석번호 textfield placeholder ui 변경 * chore: 출석 성공한 경우 error 상태 리셋 * chore: 나의 과제 페이지로 이동 시에는 외부 탭으로 열리지 않도록 수정 * chore: 스터디 기본 정보 api 호출 시 cache 속성 no-store로 가져오게 수정 --- apps/client/apis/myStudyApi.ts | 6 +-- .../app/(afterLogin)/my-page/layout.tsx | 2 +- .../@modal/(.)attendance-check/page.tsx | 31 +++++++++--- .../_components/AssignmentStatusBox.tsx | 1 - .../_components/AttendanceStatusBox.tsx | 18 ++++++- .../_components/DailyTaskCarousel.tsx | 18 ++----- .../my-study/_components/DailyTaskItem.tsx | 2 + .../my-study/_components/StudyCurriculum.tsx | 24 +++++---- .../my-study/my-assignment/layout.tsx | 2 +- apps/client/app/layout.tsx | 10 ++-- .../hooks/useAttendanceCheckSearchParams.ts | 14 ++++++ .../useFetchAttendanceCheckModalInfoData.ts | 49 ------------------- .../useScrollCarouselButtonVisibility.ts | 41 ++++++++++++++++ packages/utils/src/fetcher/index.ts | 1 + 14 files changed, 125 insertions(+), 94 deletions(-) create mode 100644 apps/client/hooks/useAttendanceCheckSearchParams.ts delete mode 100644 apps/client/hooks/useFetchAttendanceCheckModalInfoData.ts create mode 100644 apps/client/hooks/useScrollCarouselButtonVisibility.ts diff --git a/apps/client/apis/myStudyApi.ts b/apps/client/apis/myStudyApi.ts index 5247c055..05cdc6f2 100644 --- a/apps/client/apis/myStudyApi.ts +++ b/apps/client/apis/myStudyApi.ts @@ -27,7 +27,7 @@ export const myStudyApi = { `${apiPath.basicStudyInfo}/${studyId}`, { next: { tags: [tags.basicStudyInfo] }, - cache: "force-cache", + cache: "no-store", } ); @@ -44,9 +44,9 @@ export const myStudyApi = { return response.data; }, - checkAttendance: async (studyId: number, attendanceNumber: string) => { + checkAttendance: async (studyDetailId: number, attendanceNumber: string) => { const response = await fetcher.post( - `${apiPath.studyDetails}/${studyId}/${apiPath.attend}`, + `${apiPath.studyDetails}/${studyDetailId}/${apiPath.attend}`, { attendanceNumber, } diff --git a/apps/client/app/(afterLogin)/my-page/layout.tsx b/apps/client/app/(afterLogin)/my-page/layout.tsx index 407ca326..b97c684e 100644 --- a/apps/client/app/(afterLogin)/my-page/layout.tsx +++ b/apps/client/app/(afterLogin)/my-page/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from "next"; export const metadata: Metadata = { - title: "마이페이지", + title: "마이 페이지", }; const Layout = ({ diff --git a/apps/client/app/(afterLogin)/my-study/@modal/(.)attendance-check/page.tsx b/apps/client/app/(afterLogin)/my-study/@modal/(.)attendance-check/page.tsx index 6a49c209..1560877d 100644 --- a/apps/client/app/(afterLogin)/my-study/@modal/(.)attendance-check/page.tsx +++ b/apps/client/app/(afterLogin)/my-study/@modal/(.)attendance-check/page.tsx @@ -7,8 +7,9 @@ import { useModalRoute } from "@wow-class/ui/hooks"; import { parseISODate } from "@wow-class/utils"; import { myStudyApi } from "apis/myStudyApi"; import { tags } from "constants/tags"; -import useFetchAttendanceCheckModalInfoData from "hooks/useFetchAttendanceCheckModalInfoData"; +import useAttendanceCheckSearchParams from "hooks/useAttendanceCheckSearchParams"; import Image from "next/image"; +import type { KeyboardEventHandler } from "react"; import { useState } from "react"; import { revalidateTagByName } from "utils/revalidateTagByName"; import { validateAttendanceNumber } from "utils/validateAttendanceNumber"; @@ -22,9 +23,8 @@ const AttendanceCheckModal = () => { const { onClose } = useModalRoute(); - const { - studyInfo: { currentWeek, studyDetailId, studyName, deadLine }, - } = useFetchAttendanceCheckModalInfoData(); + const { studyDetailId, studyName, deadLine, currentWeek } = + useAttendanceCheckSearchParams(); const { year, month, day, hours, minutes } = parseISODate(deadLine); @@ -48,11 +48,17 @@ const AttendanceCheckModal = () => { }; const handleClickAttendanceCheckButton = async () => { - if (!isAttendanceNumberValid(attendanceNumber)) { + const trimmedAttendanceNumber = attendanceNumber.trim(); + + if (!isAttendanceNumberValid(trimmedAttendanceNumber)) { return setError(true); } - const success = await checkAttendance(studyDetailId, attendanceNumber); + const success = await checkAttendance( + +studyDetailId, + trimmedAttendanceNumber + ); + if (!success) { return setError(true); } @@ -62,12 +68,19 @@ const AttendanceCheckModal = () => { const handleAttendanceSuccess = () => { setAttended(true); + setError(false); revalidateTagByName(tags.dailyTask); setTimeout(() => { onClose(); }, 500); }; + const handleKeyDown: KeyboardEventHandler = (event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + } + }; + return ( {attended ? ( @@ -136,9 +149,12 @@ const AttendanceCheckModal = () => { error={error} helperText={error ? textfieldHelperText : ""} label="출결번호 입력" - placeholder="Ex. 0000" + placeholder="ex) 0000" style={textfieldStyle} value={attendanceNumber} + textareaProps={{ + onKeyDown: handleKeyDown, + }} onChange={handleChangeAttendanceNumber} /> @@ -198,6 +201,7 @@ const rightColStyle = css({ const assignmentButtonStyle = { minWidth: "131px", margin: "21px 25px", + whiteSpace: "nowrap", }; const weekContainerStyle = css({ diff --git a/apps/client/app/(afterLogin)/my-study/my-assignment/layout.tsx b/apps/client/app/(afterLogin)/my-study/my-assignment/layout.tsx index 1d26f66e..e166a99a 100644 --- a/apps/client/app/(afterLogin)/my-study/my-assignment/layout.tsx +++ b/apps/client/app/(afterLogin)/my-study/my-assignment/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from "next"; export const metadata: Metadata = { - title: "나의 과제 | GDSC Hongik 스터디 서비스, 와우클래스", + title: "나의 과제 | 와우클래스", }; const Layout = ({ children }: { children: React.ReactNode }) => { diff --git a/apps/client/app/layout.tsx b/apps/client/app/layout.tsx index a2963883..605c48be 100644 --- a/apps/client/app/layout.tsx +++ b/apps/client/app/layout.tsx @@ -10,22 +10,22 @@ import { JotaiProvider } from "../components/JotaiProvider"; export const metadata: Metadata = { title: { - default: "와우 클래스 | GDSC Hongik 스터디 서비스", - template: "%s | 와우 클래스", + default: "와우클래스 | GDSC Hongik 스터디 서비스", + template: "%s | 와우클래스", }, description: "와우클래스는 GDSC Hongik이 제공하는 스터디 관리 플랫폼입니다. 이 서비스는 정규 스터디 과제 제출, 출석 체크 등 전반적인 스터디 활동을 효율적으로 관리할 수 있는 기능을 제공합니다.", openGraph: { - title: "와우 클래스 | GDSC Hongik 스터디 서비스", + title: "와우클래스 | GDSC Hongik 스터디 서비스", description: "와우클래스는 GDSC Hongik이 제공하는 스터디 관리 플랫폼입니다. 이 서비스는 정규 스터디 과제 제출, 출석 체크 등 전반적인 스터디 활동을 효율적으로 관리할 수 있는 기능을 제공합니다.", images: ["/images/og-image.png"], - siteName: "와우 클래스 | GDSC Hongik 스터디 서비스", + siteName: "와우클래스 | GDSC Hongik 스터디 서비스", type: "website", }, twitter: { card: "summary_large_image", - title: "와우 클래스 | GDSC Hongik 스터디 서비스", + title: "와우클래스 | GDSC Hongik 스터디 서비스", description: "와우클래스는 GDSC Hongik이 제공하는 스터디 관리 플랫폼입니다. 이 서비스는 정규 스터디 과제 제출, 출석 체크 등 전반적인 스터디 활동을 효율적으로 관리할 수 있는 기능을 제공합니다.", images: ["/images/og-image.png"], diff --git a/apps/client/hooks/useAttendanceCheckSearchParams.ts b/apps/client/hooks/useAttendanceCheckSearchParams.ts new file mode 100644 index 00000000..b8d6302e --- /dev/null +++ b/apps/client/hooks/useAttendanceCheckSearchParams.ts @@ -0,0 +1,14 @@ +import { useSearchParams } from "next/navigation"; + +const useAttendanceCheckSearchParams = () => { + const searchParams = useSearchParams(); + + const studyDetailId = searchParams.get("study-detail-id") || "0"; + const studyName = searchParams.get("study-name"); + const deadLine = searchParams.get("deadline") || ""; + const currentWeek = searchParams.get("week"); + + return { studyDetailId, studyName, deadLine, currentWeek }; +}; + +export default useAttendanceCheckSearchParams; diff --git a/apps/client/hooks/useFetchAttendanceCheckModalInfoData.ts b/apps/client/hooks/useFetchAttendanceCheckModalInfoData.ts deleted file mode 100644 index df2dbc53..00000000 --- a/apps/client/hooks/useFetchAttendanceCheckModalInfoData.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { myStudyApi } from "apis/myStudyApi"; -import { useEffect, useState } from "react"; - -const useFetchAttendanceCheckModalInfoData = () => { - const [studyInfo, setStudyInfo] = useState({ - currentWeek: 0, - studyName: "", - studyDetailId: 0, - deadLine: "", - }); - - useEffect(() => { - const fetchAttendanceCheckModalInfoData = async () => { - const myOngoingStudyData = await myStudyApi.getMyOngoingStudyInfo(); - - if (!myOngoingStudyData?.studyId) { - return null; - } - - const dailyTaskListData = await myStudyApi.getDailyTaskList( - myOngoingStudyData?.studyId - ); - const basicStudyInfoData = await myStudyApi.getBasicStudyInfo( - myOngoingStudyData?.studyId - ); - - const attendanceDailyTask = dailyTaskListData?.find( - (dailyTask) => dailyTask.todoType === "ATTENDANCE" - ); - - if (!attendanceDailyTask || !basicStudyInfoData) { - return null; - } - - setStudyInfo({ - currentWeek: attendanceDailyTask?.week, - studyName: basicStudyInfoData?.title, - studyDetailId: attendanceDailyTask?.studyDetailId, - deadLine: attendanceDailyTask?.deadLine, - }); - }; - - fetchAttendanceCheckModalInfoData(); - }, []); - - return { studyInfo }; -}; - -export default useFetchAttendanceCheckModalInfoData; diff --git a/apps/client/hooks/useScrollCarouselButtonVisibility.ts b/apps/client/hooks/useScrollCarouselButtonVisibility.ts new file mode 100644 index 00000000..2b88dd3c --- /dev/null +++ b/apps/client/hooks/useScrollCarouselButtonVisibility.ts @@ -0,0 +1,41 @@ +import type { MutableRefObject } from "react"; +import { useEffect, useState } from "react"; + +const useScrollCarouselButtonVisibility = ( + containerRef: MutableRefObject +) => { + const [showRightButton, setShowRightButton] = useState(false); + + useEffect(() => { + const updateScrollRightButtonVisibility = () => { + if (containerRef.current) { + const container = containerRef.current; + const containerWidth = container.offsetWidth; + const scrollWidth = container.scrollWidth; + const scrollLeft = container.scrollLeft; + + setShowRightButton(scrollWidth - containerWidth > scrollLeft); + } + }; + + updateScrollRightButtonVisibility(); + + const container = containerRef.current; + if (container) { + container.addEventListener("scroll", updateScrollRightButtonVisibility); + } + + return () => { + if (container) { + container.removeEventListener( + "scroll", + updateScrollRightButtonVisibility + ); + } + }; + }, [containerRef]); + + return showRightButton; +}; + +export default useScrollCarouselButtonVisibility; diff --git a/packages/utils/src/fetcher/index.ts b/packages/utils/src/fetcher/index.ts index 451775a3..00e69839 100644 --- a/packages/utils/src/fetcher/index.ts +++ b/packages/utils/src/fetcher/index.ts @@ -106,6 +106,7 @@ class Fetcher { let response: ApiResponse = await fetch(fullUrl, fetchOptions); const data = await this.parseJsonResponse(response); + await this.handleError(response, data); response = await this.interceptResponse(response);