-
{" "}
-
-
-
-
{" "}
-
예약 확정 기간
+
+
+ 커피 챗 시간
+
+
+ 커피 챗의 날짜와 시간을 확인해보세요.
+
+
+
+
+
-
-
-
-
-
- {getDate({ date: date + "" })}
-
- {!target.length && (
-
-
-
-
-
예약된 커피챗이
-
존재하지 않습니다.
-
- )}
- {!!target.length && (
-
- {target.map((time) => (
- -
-
-
-
- {getTime(time.start_time)}
-
-
-
-
- ))}
-
- )}
+ }
+ dataComponent={
+
-
-
+ }
+ />
)
}
-type ReservedTimeProps = {
- time: CoffeeChatReservationTime
- title: string
- reservation_id: number
-}
-
-function ReservedTime({ time, title, reservation_id }: ReservedTimeProps) {
- if (time && time.mentee_nickname) {
- return (
-
-
-
-
-
-
- {time.mentee_nickname} 님
- {`\n과(와)의 커피챗이 예정되어 있습니다.`}
-
-
-
-
- )
- }
-
- return null
-}
-
export default ReservationForMentor
diff --git a/src/page/coffee-chat/detail/reservation/mentor/reservation-time/ReservedTime.tsx b/src/page/coffee-chat/detail/reservation/mentor/reservation-time/ReservedTime.tsx
new file mode 100644
index 00000000..32e4f28f
--- /dev/null
+++ b/src/page/coffee-chat/detail/reservation/mentor/reservation-time/ReservedTime.tsx
@@ -0,0 +1,113 @@
+import { CircleIcons, Icons } from "@/components/icons/Icons"
+import { CoffeeChatReservationTime } from "@/interfaces/dto/coffee-chat/coffeechat-reservation-detail.dto"
+import { getTime } from "@/util/getDate"
+import Image from "next/image"
+import EnterCoffeeChatButton from "../../../EnterCoffeeChat/EnterCoffeeChatButton"
+
+interface ReservedTimeProps {
+ chatTitle: string
+ reservation_id: number
+ time: CoffeeChatReservationTime
+}
+
+function ReservedTime({ chatTitle, reservation_id, time }: ReservedTimeProps) {
+ return (
+
+
+
+
+
+ {time.mentee_nickname ? (
+ <>
+
+
+
+
+ >
+ ) : (
+
+ )}
+
+
+ )
+}
+
+export default ReservedTime
+
+const NotHasReservedMember = () => {
+ return (
+
예약된 일정이 없습니다.
+ )
+}
+
+const ReservedMember = ({
+ nickname,
+ profileImage,
+}: {
+ nickname: string
+ profileImage: string | null
+}) => {
+ return (
+
+
+
+ {nickname}
+ {` 님 과(와)의\n커피 챗이 예정되어 있습니다.`}
+
+
+ )
+}
+
+const Time = ({ timeFormat, fill }: { timeFormat: string; fill?: boolean }) => {
+ return (
+
+ {fill ? (
+
+ ) : (
+
+ )}
+
+
+ )
+}
+
+const UserProfile = ({
+ nickname,
+ profileImage,
+}: {
+ nickname: string | null
+ profileImage: string | null
+}) => {
+ if (!nickname) return null
+
+ if (!profileImage) {
+ return (
+
+ )
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/src/page/coffee-chat/detail/reservation/mentor/reservation-time/ReservedTimes.tsx b/src/page/coffee-chat/detail/reservation/mentor/reservation-time/ReservedTimes.tsx
new file mode 100644
index 00000000..c67a16f7
--- /dev/null
+++ b/src/page/coffee-chat/detail/reservation/mentor/reservation-time/ReservedTimes.tsx
@@ -0,0 +1,68 @@
+"use client"
+
+import { CoffeeChatReservationTime } from "@/interfaces/dto/coffee-chat/coffeechat-reservation-detail.dto"
+import { ReservationSelectedDateAtom } from "@/recoil/atoms/coffee-chat/date"
+import { getDate } from "@/util/getDate"
+import { useRecoilValue } from "recoil"
+import CoffeeChatAnimation from "@/components/shared/animation/CoffeeChat"
+import ReservedTime from "./ReservedTime"
+
+interface TimeOptionProps {
+ startTime: string
+ reservation: CoffeeChatReservationTime[]
+ chatTitle: string
+}
+
+function ReservedTimes({ startTime, reservation, chatTitle }: TimeOptionProps) {
+ const selectedDate = useRecoilValue(ReservationSelectedDateAtom)
+
+ const targetReservation = reservation.filter(
+ (reservation) =>
+ getDate({ date: reservation.start_time }) ===
+ getDate({ date: selectedDate ? selectedDate.toString() : startTime }),
+ )
+
+ if (!targetReservation?.length) {
+ return (
+
+
+
+
+
+
예약된 커피챗이
+
존재하지 않습니다.
+
+
+ )
+ }
+
+ return (
+
+
+ {targetReservation.map((time) => (
+ -
+
+
+ ))}
+
+
+
+ )
+}
+
+export default ReservedTimes
+
+const ReservedTimesWrapper = ({ children }: { children: React.ReactNode }) => {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/page/coffee-chat/detail/section/CoffeeChatHashTags.tsx b/src/page/coffee-chat/detail/section/CoffeeChatHashTags.tsx
new file mode 100644
index 00000000..cbbfc066
--- /dev/null
+++ b/src/page/coffee-chat/detail/section/CoffeeChatHashTags.tsx
@@ -0,0 +1,25 @@
+"use client"
+
+import HashTag from "@/components/shared/tag/HashTag"
+import { CoffeeChatReservationHashTag } from "@/interfaces/dto/coffee-chat/get-all-coffeechat-reservation.dto"
+import { useId } from "react"
+
+interface CoffeeChatHashTagsProps {
+ hashTags: CoffeeChatReservationHashTag[]
+}
+
+function CoffeeChatHashTags({ hashTags }: CoffeeChatHashTagsProps) {
+ const id = useId()
+
+ return (
+
+ {hashTags.map((tag) => (
+ -
+ {tag.content}
+
+ ))}
+
+ )
+}
+
+export default CoffeeChatHashTags
diff --git a/src/page/coffee-chat/detail/section/CoffeeChatTitle.tsx b/src/page/coffee-chat/detail/section/CoffeeChatTitle.tsx
new file mode 100644
index 00000000..ccd5c368
--- /dev/null
+++ b/src/page/coffee-chat/detail/section/CoffeeChatTitle.tsx
@@ -0,0 +1,15 @@
+interface CoffeeChatTitleProps {
+ title: string
+}
+
+function CoffeeChatTitle({ title }: CoffeeChatTitleProps) {
+ return (
+
+ )
+}
+
+export default CoffeeChatTitle
diff --git a/src/page/coffee-chat/detail/section/author/CoffeeChatAuthor.tsx b/src/page/coffee-chat/detail/section/author/CoffeeChatAuthor.tsx
new file mode 100644
index 00000000..09b9f7e2
--- /dev/null
+++ b/src/page/coffee-chat/detail/section/author/CoffeeChatAuthor.tsx
@@ -0,0 +1,27 @@
+import UserInfo, { UserProfileInfo } from "@/components/shared/user/UserInfo"
+import CoffeeChatAuthorMenu from "./CoffeeChatAuthorMenu"
+import CoffeeChatCreatedTime from "./CoffeeChatCreatedTime"
+
+interface CoffeeChatAuthorProps {
+ author: UserProfileInfo
+ articleId: number
+ createdAt: string
+}
+
+function CoffeeChatAuthor({
+ author,
+ articleId,
+ createdAt,
+}: CoffeeChatAuthorProps) {
+ return (
+
+ )
+}
+
+export default CoffeeChatAuthor
diff --git a/src/page/coffee-chat/detail/section/author/CoffeeChatAuthorMenu.tsx b/src/page/coffee-chat/detail/section/author/CoffeeChatAuthorMenu.tsx
new file mode 100644
index 00000000..6313e587
--- /dev/null
+++ b/src/page/coffee-chat/detail/section/author/CoffeeChatAuthorMenu.tsx
@@ -0,0 +1,120 @@
+"use client"
+
+import Button from "@/components/shared/button/Button"
+import ConfirmModal from "@/components/shared/confirm-modal/ConfirmModal"
+import { UserProfileInfo } from "@/components/shared/user/UserInfo"
+import cancelMessage from "@/constants/message/cancel"
+import { errorMessage } from "@/constants/message/error"
+import successMessage from "@/constants/message/success"
+import queryKey from "@/constants/queryKey"
+import { useClientSession } from "@/hooks/useClientSession"
+import useModal from "@/hooks/useModal"
+import { APIResponse } from "@/interfaces/dto/api-response"
+import SuccessModalContent from "@/page/qna-detail/components/SuccessModalContent"
+import { deleteCoffeeChatPost } from "@/service/coffee-chat"
+import { useQueryClient } from "@tanstack/react-query"
+import { AxiosError } from "axios"
+import { useRouter } from "next/navigation"
+import { toast } from "react-toastify"
+
+interface CoffeeChatAuthorMenuProps {
+ author: UserProfileInfo
+ articleId: number
+}
+
+function CoffeeChatAuthorMenu({
+ author,
+ articleId,
+}: CoffeeChatAuthorMenuProps) {
+ const { user } = useClientSession()
+
+ if (!user) return null
+ if (author.id !== user.member_id) return null
+
+ return (
+
+
+
+ )
+}
+
+export default CoffeeChatAuthorMenu
+
+CoffeeChatAuthorMenu.Delete = function CoffeeChatAuthorMenuDelete({
+ articleId,
+}: {
+ articleId: number
+}) {
+ const { replace } = useRouter()
+
+ const queryClient = useQueryClient()
+ const { openModal } = useModal()
+
+ const onSuccess = async () => {
+ try {
+ const res = await deleteCoffeeChatPost({
+ postId: articleId,
+ })
+
+ openModal({
+ content: (
+
+ ),
+ onClose() {
+ queryClient.invalidateQueries({
+ queryKey: [queryKey.chat],
+ })
+ },
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: [queryKey.question],
+ })
+
+ replace("/chat")
+ } catch (err) {
+ if (err instanceof AxiosError) {
+ const { response } = err as AxiosError
+
+ toast.error(response?.data.msg ?? errorMessage.deleteChatReservation, {
+ toastId: "failToDeleteChatReservation",
+ position: "top-center",
+ })
+ return
+ }
+
+ toast.error(errorMessage.deleteChatReservation, {
+ toastId: "failToDeleteChatReservation",
+ position: "top-center",
+ })
+ }
+ }
+
+ const onCancel = () => {
+ toast.error(cancelMessage.deleteCoffeeChatPost, {
+ position: "top-center",
+ })
+ }
+
+ const openDeleteConfirmModal = () => {
+ openModal({
+ containsHeader: false,
+ content: (
+
+ ),
+ })
+ }
+
+ return (
+
+ )
+}
diff --git a/src/page/coffee-chat/detail/section/author/CoffeeChatCreatedTime.tsx b/src/page/coffee-chat/detail/section/author/CoffeeChatCreatedTime.tsx
new file mode 100644
index 00000000..bbcce6e5
--- /dev/null
+++ b/src/page/coffee-chat/detail/section/author/CoffeeChatCreatedTime.tsx
@@ -0,0 +1,21 @@
+import { getKorRelativeTime } from "@/util/getDate"
+import dayjs from "dayjs"
+
+interface CoffeeChatCreatedTimeProps {
+ createdAt: string
+}
+
+function CoffeeChatCreatedTime({ createdAt }: CoffeeChatCreatedTimeProps) {
+ const now = dayjs().format()
+
+ return (
+
+ {getKorRelativeTime({
+ now,
+ targetDate: createdAt,
+ })}
+
+ )
+}
+
+export default CoffeeChatCreatedTime
diff --git a/src/page/qna-detail/QnADetail.tsx b/src/page/qna-detail/QnADetail.tsx
index 0f7bd21e..616e749b 100644
--- a/src/page/qna-detail/QnADetail.tsx
+++ b/src/page/qna-detail/QnADetail.tsx
@@ -9,8 +9,8 @@ import ContentLoading from "@/components/shared/animation/ContentLoading"
import { answerQueries } from "@/react-query/answers"
import { useClientSession } from "@/hooks/useClientSession"
import NotFound from "@/app/not-found"
-import LinkToQnaList from "./components/LinkToQnaList"
import Spacing from "@/components/shared/Spacing"
+import LinkToListPage from "@/components/LinkToListPage"
const QnADetail: React.FC<{ id: string }> = ({ id }) => {
const { data, isPending } = questionQueries.useQuestionData({
@@ -38,7 +38,7 @@ const QnADetail: React.FC<{ id: string }> = ({ id }) => {
-
+
diff --git a/src/page/qna-detail/components/LinkToQnaList.tsx b/src/page/qna-detail/components/LinkToQnaList.tsx
deleted file mode 100644
index fedc4f6f..00000000
--- a/src/page/qna-detail/components/LinkToQnaList.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-"use client"
-
-import { DirectionIcons } from "@/components/icons/Icons"
-import { getHistorySessionPath } from "@/util/historySession/path"
-import { useRouter } from "next/navigation"
-
-function LinkToQnaList() {
- const { push } = useRouter()
-
- const viewQnAList = () => {
- const historySession = getHistorySessionPath()
-
- if (!historySession) {
- push("/qna?page=0")
- return
- }
-
- const prevURL = new URL(
- historySession.prevPath ?? "/",
- process.env.NEXT_PUBLIC_SITE_URL,
- )
-
- if (!prevURL.pathname.startsWith("/qna")) {
- push("/qna?page=0")
- return
- }
-
- push(`/qna?${prevURL.searchParams.toString()}`)
- }
-
- return (
-
-
- 목록 보기
-
- )
-}
-
-export default LinkToQnaList
diff --git a/src/page/qna-detail/components/MyAnswer.tsx b/src/page/qna-detail/components/MyAnswer.tsx
index 4bf1201b..838be3b5 100644
--- a/src/page/qna-detail/components/MyAnswer.tsx
+++ b/src/page/qna-detail/components/MyAnswer.tsx
@@ -56,7 +56,6 @@ const MyAnswer: React.FC
= ({
questionId,
answer,
onError(errorCase, error) {
- console.log({ errorCase, error })
if (errorCase === "unauthorized") {
clientSessionReset()
diff --git a/src/page/qna-detail/hooks/useAnswerVote.tsx b/src/page/qna-detail/hooks/useAnswerVote.tsx
index 8f27196c..30def17d 100644
--- a/src/page/qna-detail/hooks/useAnswerVote.tsx
+++ b/src/page/qna-detail/hooks/useAnswerVote.tsx
@@ -13,7 +13,7 @@ import { VoteStatus, type Answer } from "@/interfaces/answer"
import type { ModalState } from "@/interfaces/modal"
import { answerQueries } from "@/react-query/answers"
import { pendingMessage } from "@/constants/message/pending"
-import cancleMessage from "@/constants/message/cancle"
+import cancleMessage from "@/constants/message/cancel"
import successMessage from "@/constants/message/success"
import notificationMessage from "@/constants/message/notification"
diff --git a/src/page/qna-detail/hooks/useHandleMyAnswer.tsx b/src/page/qna-detail/hooks/useHandleMyAnswer.tsx
index 7f984107..1a6bef05 100644
--- a/src/page/qna-detail/hooks/useHandleMyAnswer.tsx
+++ b/src/page/qna-detail/hooks/useHandleMyAnswer.tsx
@@ -14,7 +14,7 @@ import { answerQueries } from "@/react-query/answers"
import type { Answer } from "@/interfaces/answer"
import type { ModalState } from "@/interfaces/modal"
import { findImageLinkUrlFromMarkdown } from "@/util/editor"
-import cancleMessage from "@/constants/message/cancle"
+import cancleMessage from "@/constants/message/cancel"
import successMessage from "@/constants/message/success"
import { validationMessage } from "@/constants/message/validation"
import { AxiosError } from "axios"
diff --git a/src/page/qna-detail/hooks/useHandleQuestion.tsx b/src/page/qna-detail/hooks/useHandleQuestion.tsx
index 284f7adf..f7a864ee 100644
--- a/src/page/qna-detail/hooks/useHandleQuestion.tsx
+++ b/src/page/qna-detail/hooks/useHandleQuestion.tsx
@@ -11,7 +11,7 @@ import { useDeleteImage } from "@/hooks/image/useDeleteImage"
import type { ModalState } from "@/interfaces/modal"
import type { Question } from "@/interfaces/question"
import { findImageLinkUrlFromMarkdown } from "@/util/editor"
-import cancleMessage from "@/constants/message/cancle"
+import cancleMessage from "@/constants/message/cancel"
export interface QuestionProps {
questionId: number
diff --git a/src/page/qna-detail/hooks/useMatchedRoom.tsx b/src/page/qna-detail/hooks/useMatchedRoom.tsx
new file mode 100644
index 00000000..1e882a81
--- /dev/null
+++ b/src/page/qna-detail/hooks/useMatchedRoom.tsx
@@ -0,0 +1,88 @@
+"use client"
+
+import queryKey from "@/constants/queryKey"
+import { MatchedRoom } from "@/interfaces/coffee-chat"
+import { GetCoffeeChatReservationDetailResponse } from "@/interfaces/dto/coffee-chat/coffeechat-reservation-detail.dto"
+import { GetMyCoffeeChatReservationListResponse } from "@/interfaces/dto/coffee-chat/get-my-coffeechat-reservation"
+import {
+ getCoffeeChatReservationDetail,
+ getMyCoffeeChatReservation,
+} from "@/service/coffee-chat"
+import { findMatchedRoom } from "@/util/chat/reservation"
+import { UseQueryResult, useQueries } from "@tanstack/react-query"
+import { AxiosError, AxiosResponse, HttpStatusCode } from "axios"
+import { useCallback } from "react"
+
+interface UseMathcedRoom {
+ coffeeChatId: number
+}
+
+type CombineReturn = {
+ matchedRoom: MatchedRoom | null
+ pending: boolean
+ unauthorizedStatus: "idle" | boolean
+}
+type Combine = (
+ results: [
+ UseQueryResult<
+ AxiosResponse,
+ Error
+ >,
+ UseQueryResult<
+ AxiosResponse,
+ Error
+ >,
+ ],
+) => CombineReturn
+
+export function useMatchedRoom({ coffeeChatId }: UseMathcedRoom) {
+ const combine: Combine = useCallback((results) => {
+ const [coffeeChatDetail, myCoffeeChatReservations] = results
+
+ if (!coffeeChatDetail || !myCoffeeChatReservations) {
+ return {
+ matchedRoom: null,
+ pending: true,
+ unauthorizedStatus: "idle",
+ }
+ }
+
+ const matchedRoom = findMatchedRoom({
+ myCoffeeChatReservations:
+ myCoffeeChatReservations?.data?.data.data?.reservation_responses ??
+ null,
+ coffeeChatReservations:
+ coffeeChatDetail?.data?.data.data?.date_times ?? [],
+ })
+
+ const unauthorizedStatus = myCoffeeChatReservations.isPending
+ ? ("idle" as const)
+ : !!myCoffeeChatReservations.error
+ ? myCoffeeChatReservations.error instanceof AxiosError &&
+ myCoffeeChatReservations.error.response?.status ===
+ HttpStatusCode.Unauthorized
+ : false
+
+ return {
+ matchedRoom: unauthorizedStatus === true ? null : matchedRoom,
+ pending: coffeeChatDetail.isPending || myCoffeeChatReservations.isPending,
+ unauthorizedStatus,
+ }
+ }, [])
+
+ const combinedResult = useQueries({
+ queries: [
+ {
+ queryKey: [queryKey.chat, coffeeChatId],
+ queryFn: () => getCoffeeChatReservationDetail({ postId: coffeeChatId }),
+ },
+ {
+ queryKey: [queryKey.myChatReservation],
+ queryFn: () => getMyCoffeeChatReservation(),
+ },
+ ],
+ combine,
+ })
+
+ return combinedResult
+}
diff --git a/src/page/user-profile/hooks/useIntroduction.tsx b/src/page/user-profile/hooks/useIntroduction.tsx
index c7fde518..9a70ecd4 100644
--- a/src/page/user-profile/hooks/useIntroduction.tsx
+++ b/src/page/user-profile/hooks/useIntroduction.tsx
@@ -6,7 +6,7 @@ import { useRecoilState } from "recoil"
import { useQueryClient } from "@tanstack/react-query"
import queryKey from "@/constants/queryKey"
import { memberQueries } from "@/react-query/member"
-import cancleMessage from "@/constants/message/cancle"
+import cancleMessage from "@/constants/message/cancel"
import successMessage from "@/constants/message/success"
import notificationMessage from "@/constants/message/notification"
import { validationMessage } from "@/constants/message/validation"
diff --git a/src/page/user-profile/hooks/useProfileImage.tsx b/src/page/user-profile/hooks/useProfileImage.tsx
index 6339c276..62827be3 100644
--- a/src/page/user-profile/hooks/useProfileImage.tsx
+++ b/src/page/user-profile/hooks/useProfileImage.tsx
@@ -10,7 +10,7 @@ import { useQueryClient } from "@tanstack/react-query"
import { memberQueries } from "@/react-query/member"
import Limitation from "@/constants/limitation"
import { useDeleteImage } from "@/hooks/image/useDeleteImage"
-import cancleMessage from "@/constants/message/cancle"
+import cancleMessage from "@/constants/message/cancel"
import successMessage from "@/constants/message/success"
import { validationMessage } from "@/constants/message/validation"
diff --git a/src/react-query/coffee-chat.ts b/src/react-query/coffee-chat.ts
deleted file mode 100644
index 6fface54..00000000
--- a/src/react-query/coffee-chat.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-import queryKey from "@/constants/queryKey"
-import { CreateCoffeeChatPostRequest } from "@/interfaces/dto/coffee-chat/create-coffeechat-post.dto"
-import { DeleteCoffeeChatRequest } from "@/interfaces/dto/coffee-chat/delete-coffeechat.dto"
-import { DeleteReservationRequest } from "@/interfaces/dto/coffee-chat/delete-reservation.dto"
-import { MakeReservationRequest } from "@/interfaces/dto/coffee-chat/make-reservation.dto"
-import {
- createCoffeeChatPost,
- deleteCoffeeChatPost,
- deleteCoffeeChatReservation,
- getMyCoffeeChatReservation,
- makeReservation,
-} from "@/service/coffee-chat"
-import { useMutation, useQuery } from "@tanstack/react-query"
-
-// 커피챗 등록글 생성
-const useCreateCoffeeChatPost = () => {
- const {
- data,
- mutate: createCoffeeChatPostMutate,
- isPending: isCoffeeChatPost,
- isError: isCoffeeChatPostError,
- isSuccess: isCoffeeChatPostSuccess,
- } = useMutation({
- mutationKey: [queryKey.chat],
- mutationFn: ({
- member_id,
- title,
- content,
- hash_tags,
- date_times,
- introduction,
- }: CreateCoffeeChatPostRequest) =>
- createCoffeeChatPost({
- member_id,
- title,
- content,
- hash_tags,
- date_times,
- introduction,
- }),
- })
-
- return {
- createCoffeeChatPostResponse: data,
- createCoffeeChatPost: createCoffeeChatPostMutate,
- createCoffeeChatPostStatus: {
- isCoffeeChatPost,
- isCoffeeChatPostError,
- isCoffeeChatPostSuccess,
- },
- }
-}
-
-// 커피챗 등록글 삭제
-const useDeleteCoffeeChatPost = () => {
- const {
- mutate: deleteCoffeeChatMutate,
- isPending: isdeleteCoffeeChat,
- isError: isdeleteCoffeeChatError,
- isSuccess: isdeleteCoffeeChatSuccess,
- } = useMutation({
- mutationKey: [queryKey.chat],
- mutationFn: ({ postId }: DeleteCoffeeChatRequest) =>
- deleteCoffeeChatPost({
- postId,
- }),
- })
-
- return {
- deleteCoffeeChatPost: deleteCoffeeChatMutate,
- deleteCoffeeChatPostStatus: {
- isdeleteCoffeeChat,
- isdeleteCoffeeChatError,
- isdeleteCoffeeChatSuccess,
- },
- }
-}
-
-// 내가 한 커피챗 예약 조회
-const useGetMyCoffeeChatReservation = () =>
- useQuery({
- queryKey: [queryKey.chat],
- queryFn: () => getMyCoffeeChatReservation(),
- staleTime: 1000 * 60 * 2,
- retry: 0,
- select(payload) {
- return payload
- },
- })
-
-// 커피챗 예약
-const useCreateCoffeeChatReservation = () => {
- const {
- mutate: createCoffeeChatReservationMutate,
- isPending: isCoffeeChatReservation,
- isError: isCoffeeChatReservationError,
- isSuccess: isCoffeeChatReservationSuccess,
- } = useMutation({
- mutationKey: [queryKey.chat],
- mutationFn: ({
- reservation_article_id,
- reservation_id,
- member_id,
- reservation_start_time,
- }: MakeReservationRequest) =>
- makeReservation({
- reservation_article_id,
- reservation_id,
- member_id,
- reservation_start_time,
- }),
- })
-
- return {
- createCoffeeChatReservation: createCoffeeChatReservationMutate,
- createCoffeeChatReservationStatus: {
- isCoffeeChatReservation,
- isCoffeeChatReservationError,
- isCoffeeChatReservationSuccess,
- },
- }
-}
-
-// 커피챗 예약 삭제
-const useDeleteCoffeeChatReservation = () => {
- const {
- mutate: deleteCoffeeChatReservationMutate,
- isPending: isdeleteCoffeeChatReservation,
- isError: isdeleteCoffeeChatReservationError,
- isSuccess: isdeleteCoffeeChatReservationSuccess,
- } = useMutation({
- mutationKey: [queryKey.chat],
- mutationFn: ({ reservationId }: DeleteReservationRequest) =>
- deleteCoffeeChatReservation({
- reservationId,
- }),
- })
-
- return {
- deleteCoffeeChatReservation: deleteCoffeeChatReservationMutate,
- deleteCoffeeChatReservationStatus: {
- isdeleteCoffeeChatReservation,
- isdeleteCoffeeChatReservationError,
- isdeleteCoffeeChatReservationSuccess,
- },
- }
-}
-export const CoffeeChatQueries = {
- useCreateCoffeeChatPost,
- useDeleteCoffeeChatPost,
- useGetMyCoffeeChatReservation,
- useCreateCoffeeChatReservation,
- useDeleteCoffeeChatReservation,
-}
diff --git a/src/recoil/atoms/coffee-chat/date.tsx b/src/recoil/atoms/coffee-chat/date.tsx
new file mode 100644
index 00000000..c3978541
--- /dev/null
+++ b/src/recoil/atoms/coffee-chat/date.tsx
@@ -0,0 +1,48 @@
+import { Value } from "@/interfaces/calendar"
+import { atom, selector } from "recoil"
+import dayjs from "dayjs"
+import utc from "dayjs/plugin/utc"
+
+type SelectedDate = Value
+
+type SelectedChatTimesMap = Record
+
+export const MAXIMUM_SELCTE_CHAT_TIME_NUM = 10
+
+export const ReservationSelectedDateAtom = atom({
+ key: "chat-reservation-selected-date-atom",
+ default: null,
+})
+
+export const SelectedChatTimesMapAtom = atom({
+ key: "selected-chat-times-map-atom",
+ default: null,
+})
+
+export const SelectedChatTimesListSelector = selector({
+ key: "selected-chat-times-list-selector",
+ get(opts) {
+ dayjs.extend(utc)
+
+ const timesMap = opts.get(SelectedChatTimesMapAtom)
+ if (!timesMap) return null
+
+ const timesList = Array.from(Object.values(timesMap)).flatMap(
+ (time) => time,
+ )
+ if (!timesList?.length) return null
+
+ return [
+ ...timesList.map((time) => {
+ return dayjs(time as Date)
+ .utc()
+ .format()
+ }),
+ ]
+ },
+})
+
+export const SelectedDayTab = atom({
+ key: "selected-day-tab-atom",
+ default: null,
+})
diff --git a/src/recoil/atoms/coffee-chat/schedule.tsx b/src/recoil/atoms/coffee-chat/schedule.tsx
deleted file mode 100644
index 1699c424..00000000
--- a/src/recoil/atoms/coffee-chat/schedule.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import type { Value } from "@/interfaces/calendar"
-import { atom, atomFamily } from "recoil"
-
-export const ScheduleList = atom({
- key: "Schedule-atom",
- default: [],
-})
-
-interface ScheduleListState {
- schedule: string[]
-}
-
-export const ScheduleListAtomFamily = atomFamily({
- key: "answer-editor-atom-family",
- default: {
- schedule: [],
- },
-})
-
-export const CoffeeChatStartDate = atom({
- key: "coffee-chat-start-date-atom",
- default: undefined,
-})
-
-export const SelectedDate = atom({
- key: "selected-date-atom",
- default: "",
-})
-
-export const TimeCount = atom({
- key: "selected-time-count",
- default: 0,
-})
diff --git a/src/styles/react-calendar/Base.css b/src/styles/react-calendar/Base.css
index 4fb9bf1c..bd3c1c8c 100644
--- a/src/styles/react-calendar/Base.css
+++ b/src/styles/react-calendar/Base.css
@@ -16,10 +16,7 @@
font-size: 16px;
margin-top: 8px;
}
-.react-calendar__navigation button:enabled:hover,
-.react-calendar__navigation button:enabled:focus {
- background-color: #f8f8fa;
-}
+
.react-calendar__navigation button[disabled] {
background-color: white;
}
@@ -31,58 +28,45 @@
/*개별 날짜 버튼*/
.react-calendar__tile {
- min-height: 60px;
+ height: 60px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ border-radius: 0 !important;
font-weight: bold;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
+.react-calendar__tile.holiday {
+ color: #d10000;
+}
.react-calendar__tile:disabled {
color: lightgray;
background-color: white;
font-weight: lighter;
}
-.react-calendar__tile:enabled:hover,
-.react-calendar__tile:enabled:focus {
- background: #00c47133;
- color: #00c471;
- border-radius: 6px;
+.react-calendar__tile:disabled.react-calendar__tile--now {
+ color: #828282;
+ font-weight: bold;
}
+
.react-calendar__tile--now {
border-radius: 6px;
font-weight: bold;
color: #00c471;
background-color: white;
}
-.react-calendar__tile--now:enabled:hover,
-.react-calendar__tile--now:enabled:focus {
- background: #00c47133;
- border-radius: 6px;
- font-weight: bold;
- color: #00c471;
-}
-.react-calendar__tile--hasActive:enabled:hover,
-.react-calendar__tile--hasActive:enabled:focus {
- background: #f8f8fa;
- border-color: #00c471;
- border: 1px;
-}
-.react-calendar__tile--active {
+
+.react-calendar__tile--active,
+.react-calendar__tile--active:not(:focus).holiday {
background: #00c471;
border-radius: 6px;
font-weight: bold;
color: white;
}
-.react-calendar__tile--active:enabled:hover,
-.react-calendar__tile--active:enabled:focus {
- background: #00c471;
- color: white;
-}
-.react-calendar--selectRange .react-calendar__tile--hover {
- background-color: #f8f8fa;
-}
+
.react-calendar__tile--range {
background: #f8f8fa;
color: #00c471;
@@ -104,3 +88,50 @@
background: #00c471;
color: white;
}
+
+@media (hover: hover) and (pointer: fine) {
+ .react-calendar__navigation button:enabled:hover,
+ .react-calendar__navigation button:enabled:focus {
+ background-color: #f8f8fa;
+ }
+
+ .react-calendar__tile:enabled:hover,
+ .react-calendar__tile:enabled:focus {
+ background: #00c47133;
+ color: #00c471;
+ border-radius: 6px;
+ }
+
+ .react-calendar__tile--now:enabled:hover,
+ .react-calendar__tile--now:enabled:focus {
+ background: #00c47133;
+ border-radius: 6px;
+ font-weight: bold;
+ color: #00c471;
+ }
+
+ .react-calendar__tile--hasActive:enabled:hover,
+ .react-calendar__tile--hasActive:enabled:focus {
+ background: #f8f8fa;
+ border-color: #00c471;
+ border: 1px;
+ }
+
+ .react-calendar__tile--active:enabled:hover,
+ .react-calendar__tile--active:enabled:focus {
+ background: #00c471;
+ color: white;
+ }
+
+ .react-calendar--selectRange .react-calendar__tile--hover {
+ background-color: #f8f8fa;
+ }
+}
+
+@media (hover: none) {
+ .react-calendar__tile--active:enabled:hover,
+ .react-calendar__tile--active:enabled:focus {
+ background: #00c471;
+ color: white;
+ }
+}
diff --git a/src/styles/react-calendar/CoffeeChatTile.css b/src/styles/react-calendar/CoffeeChatTile.css
index 1a2e1445..903f5d12 100644
--- a/src/styles/react-calendar/CoffeeChatTile.css
+++ b/src/styles/react-calendar/CoffeeChatTile.css
@@ -15,7 +15,16 @@
color: #fff;
border-radius: 6px;
}
-.react-calendar__tile:not(.disabled).mentoring:hover {
- background: #00c47133;
- color: #00c471;
+.react-calendar__tile--active.mentoring abbr {
+ color: #ffff80;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .react-calendar__tile:not(.disabled).mentoring:hover {
+ background: #00c47133;
+ color: #00c471;
+ }
+ .react-calendar__tile--active.mentoring:hover abbr {
+ color: #00c471;
+ }
}
diff --git a/src/util/chat/popup.ts b/src/util/chat/popup.ts
index 40f75d9e..fc14f7b6 100644
--- a/src/util/chat/popup.ts
+++ b/src/util/chat/popup.ts
@@ -8,6 +8,10 @@ interface PopupStorageFunctionArgs {
}
export function getPopupStorage(): PopupStorage {
+ const windowLocalStorage = globalThis.localStorage
+
+ if (!windowLocalStorage) return []
+
const storage = localStorage.getItem(POPUP_STORAGE_KEY)
if (!storage) {
diff --git a/src/util/chat/reservation.ts b/src/util/chat/reservation.ts
new file mode 100644
index 00000000..6415722b
--- /dev/null
+++ b/src/util/chat/reservation.ts
@@ -0,0 +1,26 @@
+import { CoffeeChatReservationTime } from "@/interfaces/dto/coffee-chat/coffeechat-reservation-detail.dto"
+import { MyCoffeeChatReservation } from "@/interfaces/dto/coffee-chat/get-my-coffeechat-reservation"
+
+type FindMatchedRoomArgs = {
+ myCoffeeChatReservations: MyCoffeeChatReservation[] | null
+ coffeeChatReservations: CoffeeChatReservationTime[]
+}
+
+export function findMatchedRoom({
+ myCoffeeChatReservations,
+ coffeeChatReservations,
+}: FindMatchedRoomArgs) {
+ if (!myCoffeeChatReservations?.length) return null
+ if (!coffeeChatReservations?.length) return null
+
+ const matchedRoom = myCoffeeChatReservations.find((myReservation) => {
+ return !!coffeeChatReservations.find(
+ (chatReservation) =>
+ chatReservation.reservation_id === myReservation.reservation_id,
+ )
+ })
+
+ if (!matchedRoom) return null
+
+ return matchedRoom
+}
diff --git a/src/util/chat/time.ts b/src/util/chat/time.ts
index ab8fbb12..a6e758b2 100644
--- a/src/util/chat/time.ts
+++ b/src/util/chat/time.ts
@@ -37,3 +37,25 @@ export function isBeforeChatStartTime(startTime: string) {
return now.isBefore(startKorTime, "minutes")
}
+
+export function isPenalty({ time, now }: { time: string; now?: string }) {
+ const dateTargetTime = getKorDayjs(time)
+ const dateNow = getKorDayjs(now ?? dayjs())
+
+ const daysBetween = dateNow.diff(dateTargetTime, "days")
+
+ return daysBetween >= -1
+}
+
+export function getChatPeriods({ startTime }: { startTime: string }) {
+ const startDate = dayjs(startTime).startOf("days")
+
+ return {
+ reservationPossible: [
+ dayjs(startDate).subtract(6, "days"),
+ dayjs(startDate).subtract(2, "days"),
+ ],
+ reservationConfirm: dayjs(startDate).subtract(1, "days"),
+ chat: [startDate, dayjs(startDate).add(2, "days")],
+ }
+}
diff --git a/src/util/getDate.ts b/src/util/getDate.ts
index e9ddf27f..ac27a955 100644
--- a/src/util/getDate.ts
+++ b/src/util/getDate.ts
@@ -1,3 +1,4 @@
+import Holidays from "@/constants/holidays"
import dayjs from "dayjs"
import "dayjs/locale/ko"
@@ -217,3 +218,7 @@ export function getCollapsedDate({
"HH:mm",
)}`
}
+
+export function getHoliday(date: Date) {
+ return Holidays.find((day) => day.date === dayjs(date).format("YYYY-MM-DD"))
+}
diff --git a/src/util/historySession/path.ts b/src/util/historySession/path.ts
index 8e221c50..88d0864b 100644
--- a/src/util/historySession/path.ts
+++ b/src/util/historySession/path.ts
@@ -1,3 +1,5 @@
+import { TargetPage } from "@/components/LinkToListPage"
+
export interface HistorySessionPath {
prevPath: string | null
currentPath: string
@@ -27,7 +29,10 @@ export function updateHistorySessionPath() {
if (!path) {
const session: HistorySessionPath = {
prevPath: null,
- currentPath: globalThis.location.href,
+ currentPath: new URL(
+ globalThis.location.href,
+ process.env.NEXT_PUBLIC_SITE_URL,
+ ).href,
}
globalThis.sessionStorage.setItem(
@@ -41,10 +46,15 @@ export function updateHistorySessionPath() {
const correctPath = (currentPath: string) => {
const baseURL = process.env.NEXT_PUBLIC_SITE_URL
- if (currentPath === `${baseURL}/qna`) return "/qna?page=0"
- if (currentPath === `${baseURL}/chat`) return "/chat?page=0"
+ let rewritedPath = null
+ if (currentPath === `${baseURL}/qna`) rewritedPath = "/qna?page=0"
+ if (currentPath === `${baseURL}/chat`) rewritedPath = "/chat?page=0"
if (currentPath === `${baseURL}/coding-meetings`)
- return "/coding-meetings?page=0&size=10&filter=all"
+ rewritedPath = "/coding-meetings?page=0&size=10&filter=all"
+
+ if (rewritedPath) {
+ return new URL(rewritedPath, process.env.NEXT_PUBLIC_SITE_URL).href
+ }
return globalThis.location.href
}
@@ -64,6 +74,18 @@ export function updateHistorySessionPath() {
}
//
+export function pathnameOfBack(pathname: string): TargetPage | null {
+ if (isQuestionDetailPage(pathname)) return "qna"
+ if (isCoffeeChatDetailPage(pathname)) return "chat"
+ if (pathname === "/chat/create") return "chat"
+
+ return null
+}
+
export function isQuestionDetailPage(pathname: string) {
return /^\/question\/[0-9]+$/g.test(pathname)
}
+
+export function isCoffeeChatDetailPage(pathname: string) {
+ return /^\/chat\/[0-9]+$/g.test(pathname)
+}
diff --git a/src/util/validate.ts b/src/util/validate.ts
index 6b53b0de..7a9f32f6 100644
--- a/src/util/validate.ts
+++ b/src/util/validate.ts
@@ -1,3 +1,5 @@
+import Limitation from "@/constants/limitation"
+import Regex from "@/constants/regex"
import validator from "validator"
export const validPasswordSpecialList = ["@", "$", "!", "%", "*", "?", "&"]
@@ -90,6 +92,31 @@ export class Validator {
},
}
}
+
+ validateHashTag(hashTag: string = "") {
+ return {
+ length() {
+ return hashTag.length && hashTag.length < Limitation.hashtags_word
+ },
+ format() {
+ return (
+ !hashTag.match(Regex.preventSpecialCharacter) &&
+ !hashTag.match(Regex.preventEmoji)
+ )
+ },
+ }
+ }
+
+ validateHashTagList(hashTagList: string[]) {
+ return {
+ length() {
+ return hashTagList.length < Limitation.hashtags_cnt
+ },
+ duplicate(hashTag: string) {
+ return !hashTagList.includes(hashTag)
+ },
+ }
+ }
}
export const validatorInstance = new Validator()
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 96ccf3e1..89994a6d 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -28,6 +28,9 @@ const config = {
tabletDevice: { min: "640px", max: "991px" },
pc: { min: "992px" },
calendarRow: { min: "695px" },
+ pointerhover: {
+ raw: "(hover: hover) and (pointer: fine)",
+ },
},
colors: {
colorsGray: "#ced4da",
@@ -36,6 +39,7 @@ const config = {
success: "#198754",
info: "#ecf5ff",
danger: "#dc3545",
+ ["light-green"]: "#EAF7F0",
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",