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);