Skip to content

Commit

Permalink
feat: study waiting page
Browse files Browse the repository at this point in the history
  • Loading branch information
SeungJL committed May 28, 2024
1 parent 33fb817 commit 02e5582
Show file tree
Hide file tree
Showing 20 changed files with 451 additions and 149 deletions.
22 changes: 18 additions & 4 deletions components/molecules/DateVoteBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Box, Button, Flex } from "@chakra-ui/react";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/router";
import { useRecoilValue } from "recoil";

import { DateVoteButtonProps } from "../../pageTemplates/home/studyController/StudyControllerVoteButton";
Expand All @@ -11,18 +13,30 @@ interface DateVoteBlockProps {
}

function DateVoteBlock({ buttonProps, func, cnt }: DateVoteBlockProps) {
const router = useRouter();
const searchParams = useSearchParams();
const date = searchParams.get("date");
const locationEn = searchParams.get("location");

const myStudy = useRecoilValue(myStudyState);

return (
<Flex w="100%" justify="space-between" align="center">
<Box fontSize="16px" fontWeight={500}>
<Box as="span" mr="4px">
현재 참여 인원:
</Box>

<Box display="inline-block" color="var(--color-mint)">
<Box display="inline-block" w="16px" textAlign="center">
{cnt === undefined ? "- " : cnt}
</Box>
{cnt !== undefined ? (
<button onClick={() => router.push(`/study/waiting/${locationEn}/${date}`)}>
<u>{cnt}</u>
</button>
) : (
<Box w="22px" textAlign="center">
-
</Box>
)}
</Box>
</Box>

Expand Down
67 changes: 67 additions & 0 deletions components/molecules/layouts/ImageTileFlexLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Box, Flex } from "@chakra-ui/react";
import Image from "next/image";
import styled from "styled-components";

export interface IImageTileData {
imageUrl: string;
text: string;
url?: string;
func?: () => void;
id?: string;
}

interface IImageTileGridLayout {
imageDataArr: IImageTileData[];
selectedId?: string[];
selectedSubId?: string[];
}
export default function ImageTileFlexLayout({
imageDataArr,

selectedId,
selectedSubId,
}: IImageTileGridLayout) {
function ImageTileLayout({ url, text }: { url: string; text: string }) {
return (
<Flex direction="column" alignItems="center">
<Box w="64px" h="64px" borderRadius="8px" overflow="hidden">
<Image width={64} height={64} src={url} alt="studyPlaceImage" />
</Box>
<Box mt="12px">{text}</Box>
</Flex>
);
}

return (
<Flex overflow="auto" pb="12px">
{imageDataArr.map((imageData, idx) => (
<Button
key={idx}
$isSelected={
selectedId?.includes(imageData?.id)
? "main"
: selectedSubId?.includes(imageData?.id)
? "sub"
: null
}
onClick={imageData.func}
>
<ImageTileLayout url={imageData.imageUrl} text={imageData.text} />
</Button>
))}
</Flex>
);
}

const Button = styled.button<{ $isSelected: "main" | "sub" | null }>`
background-color: ${(props) =>
props.$isSelected === "main"
? "var(--color-mint)"
: props.$isSelected === "sub"
? "var(--color-orange)"
: null};
color: ${(props) => (props.$isSelected ? "white" : "inherit")};
border-radius: var(--rounded);
margin-right: 12px;
height: 100px;
`;
32 changes: 21 additions & 11 deletions components/services/studyVote/StudyVoteTimeRulletDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ export default function StudyVoteTimeRulletDrawer({
drawerOptions,
setIsModal,
}: IStudyVoteTimeRulletDrawer) {
return (
<>
<BottomDrawerLg options={drawerOptions} setIsModal={setIsModal}>
<StudyVoteTimeRullets setVoteTime={setVoteTime} />
</BottomDrawerLg>
</>
);
}

interface StudyVoteTimeRulletsProps {
setVoteTime: Dispatch<{ start: Dayjs; end: Dayjs }>;
}

export function StudyVoteTimeRullets({ setVoteTime }: StudyVoteTimeRulletsProps) {
const leftDefaultIdx = 8;
const rightDefaultIdx = 10;

Expand All @@ -43,16 +57,12 @@ export default function StudyVoteTimeRulletDrawer({
}, [rulletValue]);

return (
<>
<BottomDrawerLg options={drawerOptions} setIsModal={setIsModal}>
<RulletPickerTwo
leftDefaultIdx={leftDefaultIdx}
rightDefaultIdx={rightDefaultIdx}
leftRulletArr={startItemArr}
rightRulletArr={endTimeArr}
setRulletValue={setRulletValue}
/>
</BottomDrawerLg>
</>
<RulletPickerTwo
leftDefaultIdx={leftDefaultIdx}
rightDefaultIdx={rightDefaultIdx}
leftRulletArr={startItemArr}
rightRulletArr={endTimeArr}
setRulletValue={setRulletValue}
/>
);
}
1 change: 1 addition & 0 deletions constants/serviceConstants/pointSystemConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const POINT_SYSTEM_PLUS = {
second: { value: 5, message: "스터디 두번째 투표" },
third: { value: 2, message: "스터디 세번째 투표" },
},
STUDY_VOTE_DAILY: { value: 2, message: "당일 스터디 참여" },
STUDY_INVITE: { value: 2, message: "친구 초대 보너스" },
DAILY_ATTEND: { value: 3, message: "일일 출석" },
PROMOTION: { value: 100, message: "홍보 리워드" },
Expand Down
2 changes: 1 addition & 1 deletion libs/study/getMyStudy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const getMyStudy = (
}
}),
);

console.log(34, participations, myStudy);
return myStudy;
};

Expand Down
2 changes: 2 additions & 0 deletions libs/study/getStudyVoteCnt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IParticipation } from "../../types/models/studyTypes/studyDetails";

export const getStudyVoteCnt = (studyVoteData: IParticipation[], filterUid?: string) => {
if (!studyVoteData) return undefined;
const temp = new Set();

studyVoteData?.forEach((par) => {
Expand All @@ -10,5 +11,6 @@ export const getStudyVoteCnt = (studyVoteData: IParticipation[], filterUid?: str
temp.add(who.user.uid);
});
});

return temp.size;
};
25 changes: 25 additions & 0 deletions modals/study/StudyPointGuideModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import CheckList from "../../components/atoms/CheckList";
import { IModal } from "../../types/components/modalTypes";
import { IFooterOptions, ModalLayout } from "../Modals";

function StudyPointGuideModal({ setIsModal }: IModal) {
const footerOptions: IFooterOptions = {
main: {
text: "확인",
},
};

const STUDY_CONTENTS = [
"결과 발표 이전에 신청한 경우 5 POINT + 선택한 서브 장소 만큼 추가 포인트 획득!",
"가장 먼저 신청한 인원은 +10 POINT, 두번째 인원은 +5 POINT, 세번째 인원은 +2 POINT를 추가 획득!",
"당일 참여나 자유 스터디의 경우 +2 POINT",
];

return (
<ModalLayout setIsModal={setIsModal} title="스터디 포인트" footerOptions={footerOptions}>
<CheckList contents={STUDY_CONTENTS} />
</ModalLayout>
);
}

export default StudyPointGuideModal;
157 changes: 157 additions & 0 deletions modals/study/StudySimpleVoteModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { Box } from "@chakra-ui/react";
import dayjs, { Dayjs } from "dayjs";
import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { useQueryClient } from "react-query";
import { useRecoilValue } from "recoil";

import Selector from "../../components/atoms/Selector";
import ImageTileFlexLayout from "../../components/molecules/layouts/ImageTileFlexLayout";
import { IImageTileData } from "../../components/molecules/layouts/ImageTitleGridLayout";
import { StudyVoteTimeRullets } from "../../components/services/studyVote/StudyVoteTimeRulletDrawer";
import { STUDY_VOTE } from "../../constants/keys/queryKeys";
import { POINT_SYSTEM_PLUS } from "../../constants/serviceConstants/pointSystemConstants";
import { useToast } from "../../hooks/custom/CustomToast";
import {
useStudyOpenFreeMutation,
useStudyParticipationMutation,
} from "../../hooks/study/mutations";
import { usePointSystemMutation } from "../../hooks/user/mutations";
import { usePointSystemLogQuery } from "../../hooks/user/queries";
import { myStudyState } from "../../recoils/studyRecoils";
import { ModalSubtitle } from "../../styles/layout/modal";
import { IModal } from "../../types/components/modalTypes";
import { IParticipation } from "../../types/models/studyTypes/studyDetails";
import { IStudyVote } from "../../types/models/studyTypes/studyInterActions";
import { LocationEn } from "../../types/services/locationTypes";
import { convertLocationLangTo } from "../../utils/convertUtils/convertDatas";
import { dayjsToStr } from "../../utils/dateTimeUtils";
import { IFooterOptions, ModalLayout } from "../Modals";

interface StudySimpleVoteModalProps extends IModal {
studyVoteData: IParticipation[];
}

function StudySimpleVoteModal({ studyVoteData, setIsModal }: StudySimpleVoteModalProps) {
const toast = useToast();
const searchParams = useSearchParams();
const date = searchParams.get("date");
const locationEn = searchParams.get("location") as LocationEn;
const location = convertLocationLangTo(locationEn, "kr");

const myStudy = useRecoilValue(myStudyState);
const [selectedPlace, setSelectedPlace] = useState<string>();
const [isFirstPage, setIsFirstPage] = useState(true);
const [myVote, setMyVote] = useState<IStudyVote>();
const [voteTime, setVoteTime] = useState<{ start: Dayjs; end: Dayjs }>();

const { data: pointLog } = usePointSystemLogQuery("point", true, {
enabled: !!myStudy,
});
const { mutate: getPoint } = usePointSystemMutation("point");

const { mutateAsync: openFree } = useStudyOpenFreeMutation(date, {});

const { mutate: patchAttend, isLoading } = useStudyParticipationMutation(dayjs(date), "post", {
onSuccess() {
handleSuccess();
},
});

useEffect(() => {
setMyVote((old) => ({
...old,
place: selectedPlace,
start: voteTime?.start,
end: voteTime?.end,
}));
}, [selectedPlace, voteTime]);

const queryClient = useQueryClient();

const handleSuccess = async () => {
queryClient.invalidateQueries([STUDY_VOTE, date, location]);
if (myPrevVotePoint) {
await getPoint({
message: "스터디 투표 취소",
value: -myPrevVotePoint,
});
}
await getPoint({
...POINT_SYSTEM_PLUS.STUDY_VOTE_DAILY,
sub: date,
});
toast(
"success",
`참여 완료! ${POINT_SYSTEM_PLUS.STUDY_VOTE_DAILY.value} 포인트가 적립되었습니다."}`,
);
setIsModal(false);
};

const myPrevVotePoint = pointLog?.find(
(item) => item.message === "스터디 투표" && item.meta.sub === dayjsToStr(dayjs(date)),
)?.meta.value;

const handleVote = async () => {
if (!myVote?.place || !myVote?.start || !myVote?.end) {
toast("error", "누락된 정보가 있습니다.");
return;
}
const findPlace = studyVoteData?.find((par) => par.place._id === myVote.place);

if (findPlace.status === "dismissed") {
await openFree(myVote?.place);
setTimeout(() => {
patchAttend(myVote);
}, 500);
} else patchAttend(myVote);
};

const imageDataArr: IImageTileData[] = studyVoteData
?.filter((par) => par.status === "dismissed")
.map((par) => ({
imageUrl: par.place.image,
text: par.place.branch,
id: par.place._id,
func: () => setSelectedPlace(par.place._id),
}))
.sort((a) => (a.text === "개인 스터디" ? 1 : -1));

const dismissedPlaces = studyVoteData
?.filter((par) => par.status === "dismissed")
.map((par) => par.place.branch);

const footerOptions: IFooterOptions = {
main: {
text: isFirstPage ? "선택 완료" : "참여 신청",
func: isFirstPage ? () => setIsFirstPage(false) : handleVote,
isLoading,
},
};

return (
<ModalLayout title="당일 참여 신청" footerOptions={footerOptions} setIsModal={setIsModal}>
<Box>
{isFirstPage ? (
<>
<ModalSubtitle>
오픈된 스터디에 참여하거나 자유 스터디로 오픈할 수 있습니다.
</ModalSubtitle>
<ImageTileFlexLayout imageDataArr={imageDataArr} selectedId={[selectedPlace]} />
<Box mt="20px">
<Selector
options={["선택 없음", ...dismissedPlaces]}
defaultValue={selectedPlace}
setValue={setSelectedPlace}
/>
</Box>
</>
) : (
<StudyVoteTimeRullets setVoteTime={setVoteTime} />
)}
</Box>
</ModalLayout>
);
}

export default StudySimpleVoteModal;
2 changes: 1 addition & 1 deletion modals/userRequest/StudyPresetModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function StudyPresetModal() {

const { data: studyPreference } = useStudyPreferenceQuery();

const { data: studyPlaces } = useStudyPlacesQuery(location, {
const { data: studyPlaces } = useStudyPlacesQuery(location, "active", {
enabled: !!location,
onSuccess(data) {
if (data.length === 0) {
Expand Down
Loading

0 comments on commit 02e5582

Please sign in to comment.