Skip to content

Commit

Permalink
Merge pull request #27 from team-Ollie/10-feature-Home-API
Browse files Browse the repository at this point in the history
feat: 홈화면 API 연결
  • Loading branch information
iOdiO89 authored Jun 29, 2024
2 parents ff37fee + b98ed30 commit a43cb4a
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 41 deletions.
40 changes: 38 additions & 2 deletions apis/challenge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import client, { ResponseBody } from "./client";
import client, { ResponseBody, ResponseBody2 } from "./client";

interface GetMyChallengeListResponse extends ResponseBody {
result: Challenge[];
Expand All @@ -13,9 +13,45 @@ export interface Challenge {
attendanceRate: number;
totalAttendanceRate: number;
}
interface GetChallengeAdsResponse extends ResponseBody2 {
result: {
mostParticipatedChallenge: Challenge;
mostAttendancedChallenge: Challenge;
mostRecentlyStartedChallenge: Challenge;
};
}

interface getChallengeSearchResponse extends ResponseBody2 {
result: Challenge[];
}

async function getMyChallengeList(): Promise<GetMyChallengeListResponse> {
const { data } = await client.get(`/challenges`);
return data;
}

export { getMyChallengeList };
async function getChallengeAds(): Promise<GetChallengeAdsResponse> {
const { data } = await client.get(`/challenges/ads`);
return data;
}

async function getChallengeSearch(
keyword: string,
): Promise<getChallengeSearchResponse> {
const { data } = await client.get(`/challenges/search?searchWord=${keyword}`);
return data;
}

async function postNewChallenge(challengeIdx: number): Promise<ResponseBody> {
const { data } = await client.post(`/challenges/participation`, {
challengeIdx,
});
return data;
}

export {
getMyChallengeList,
getChallengeAds,
getChallengeSearch,
postNewChallenge,
};
6 changes: 5 additions & 1 deletion apis/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ interface ResponseBody {
code: number;
message: string;
}
interface ResponseBody2 {
isSuccess: boolean;
message: string;
}

export const setTokenFromLocalStorage = (access_token: string) => {
localStorage.setItem("access_token", access_token);
Expand Down Expand Up @@ -49,4 +53,4 @@ client.interceptors.request.use(
);

export default client;
export type { ResponseBody };
export type { ResponseBody, ResponseBody2 };
55 changes: 52 additions & 3 deletions apis/hooks/challenge.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { getMyChallengeList } from "../challenge";
import { useQuery } from "@tanstack/react-query";
import {
getChallengeAds,
getChallengeSearch,
getMyChallengeList,
postNewChallenge,
} from "../challenge";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

function useGetMyChallengeList() {
const { data } = useQuery({
Expand All @@ -10,4 +15,48 @@ function useGetMyChallengeList() {
return { data };
}

export { useGetMyChallengeList };
function useGetChallengeAds() {
const { data } = useQuery({
queryKey: ["getChallengeAds"],
queryFn: getChallengeAds,
});

return { data };
}

function useGetChallengeSearch(keyword: string) {
const { data } = useQuery({
queryKey: ["getChallengeSearch", keyword],
queryFn: () => getChallengeSearch(keyword),
enabled: keyword.trim().length !== 0,
});

return { data };
}

function usePostNewChallenge(
challengeIdx: number,
challengeName: string,
notify: (title: string) => void,
) {
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationKey: ["postNewChallenge", challengeIdx],
mutationFn: () => postNewChallenge(challengeIdx),
onSuccess: () => {
notify(challengeName);
queryClient.invalidateQueries({
queryKey: ["getChallengeSearch"],
});
},
});

return { mutate };
}

export {
useGetMyChallengeList,
useGetChallengeAds,
useGetChallengeSearch,
usePostNewChallenge,
};
25 changes: 19 additions & 6 deletions components/home/carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import { NextPage } from "next";
import FlexBox from "../Flexbox";
import { PopularChallenge } from "./popular";
import PopularChallenge from "./popular";
import useEmblaCarousel from "embla-carousel-react";
import Autoplay from "embla-carousel-autoplay";
import { useGetChallengeAds } from "@/apis/hooks/challenge";

export const HomeCarousel: NextPage = () => {
const HomeCarousel: NextPage = () => {
const [emblaRef] = useEmblaCarousel({ loop: true }, [Autoplay()]);
const { data } = useGetChallengeAds();

return (
<div className="w-full embla overflow-hidden rounded-lg " ref={emblaRef}>
<div className="w-full embla overflow-hidden rounded-lg" ref={emblaRef}>
<FlexBox className="embla__container">
<PopularChallenge />
<PopularChallenge />
<PopularChallenge />
<PopularChallenge
challengeInfo={data?.result.mostAttendancedChallenge}
type="mostAttendancedChallenge"
/>
<PopularChallenge
challengeInfo={data?.result.mostParticipatedChallenge}
type="mostParticipatedChallenge"
/>
<PopularChallenge
challengeInfo={data?.result.mostRecentlyStartedChallenge}
type="mostRecentlyStartedChallenge"
/>
</FlexBox>
</div>
);
};

export default HomeCarousel;
2 changes: 1 addition & 1 deletion components/home/certifyModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function CertifyModal({
return (
<>
<FlexBox className="justify-between items-start">
<div className="h3 mb-4">풍물패 두드림 챌린지 인증하기</div>
<div className="h3 mb-4">챌린지 인증하기</div>
<div onClick={() => setIsModalVisible(false)}>
<Image src={"/svgs/Close.svg"} width={20} height={20} />
</div>
Expand Down
21 changes: 15 additions & 6 deletions components/home/challenge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import FlexBox from "../Flexbox";
import { useRouter } from "next/router";
import { useAtom } from "jotai";
import { isAdminAtom } from "@/utils/atom";
import { Challenge as ChallengeType } from "@/apis/challenge";

interface ChallengeProps {
setIsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
challengeInfo: ChallengeType;
}

const Challenge: NextPage<ChallengeProps> = ({ setIsModalVisible }) => {
const Challenge: NextPage<ChallengeProps> = ({
setIsModalVisible,
challengeInfo,
}) => {
const router = useRouter();
const [isAdmin] = useAtom(isAdminAtom);

Expand All @@ -20,10 +25,14 @@ const Challenge: NextPage<ChallengeProps> = ({ setIsModalVisible }) => {
onClick={isAdmin ? null : () => router.push("/challenge")}
>
<FlexBox className="w-full justify-between items-start">
<div className="h2">풍물패 두드림</div>
<div className="h4 text-gray-500">12명 참여 중</div>
<div className="h2">{challengeInfo.name}</div>
<div className="h4 text-gray-500">
{challengeInfo.participantsNum}명 참여 중
</div>
</FlexBox>
<div className="h4 self-start">서울시립도서관 4층 | 월 16시</div>
<div className="h4 self-start">
{challengeInfo.location} | {challengeInfo.schedule}
</div>
</FlexBox>
<FlexBox direction="col" className="w-full gap-1">
<FlexBox
Expand All @@ -33,12 +42,12 @@ const Challenge: NextPage<ChallengeProps> = ({ setIsModalVisible }) => {
{!isAdmin && (
<FlexBox className="gap-1">
<div className="h5 text-gray-500">개인달성률</div>
<div className="h3">100%</div>
<div className="h3">{challengeInfo.attendanceRate}%</div>
</FlexBox>
)}
<FlexBox className="gap-1">
<div className="h5 text-gray-500">전체달성률</div>
<div className="h3">100%</div>
<div className="h3">{challengeInfo.totalAttendanceRate}%</div>
</FlexBox>
</FlexBox>
<div
Expand Down
14 changes: 11 additions & 3 deletions components/home/challengeBox.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { NextPage } from "next";
import FlexBox from "../Flexbox";
import Challenge from "./challenge";
import { HomeCarousel } from "./carousel";
import HomeCarousel from "./carousel";
import { useRouter } from "next/router";
import { isAdminAtom } from "@/utils/atom";
import { useAtom } from "jotai";
import CertifyModal from "./certifyModal";
import { useState } from "react";
import ReactModal from "react-modal";
import Image from "next/image";
import { useGetMyChallengeList } from "@/apis/hooks/challenge";

interface HomeChallengeProps {
onNotify: (msg: string) => void;
Expand All @@ -19,6 +20,8 @@ const HomeChallenge: NextPage<HomeChallengeProps> = ({ onNotify }) => {
const [isAdmin] = useAtom(isAdminAtom);
const [isOpen, setIsOpen] = useState<boolean>(false);

const { data: challengeInfo } = useGetMyChallengeList();

return (
<FlexBox direction="col" className="w-full items-start gap-2 p-4">
<div className="h1">
Expand All @@ -31,8 +34,13 @@ const HomeChallenge: NextPage<HomeChallengeProps> = ({ onNotify }) => {
>
{isAdmin ? "새 프로그램 등록" : "참여 프로그램 추가"}
</div>
<Challenge setIsModalVisible={setIsOpen} />
<Challenge setIsModalVisible={setIsOpen} />
{challengeInfo?.result.map((info) => (
<Challenge
setIsModalVisible={setIsOpen}
challengeInfo={info}
key={info.challengeIdx}
/>
))}
<ReactModal
isOpen={isOpen}
style={modalStyle}
Expand Down
34 changes: 30 additions & 4 deletions components/home/popular.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,41 @@ import { NextPage } from "next";
import FlexBox from "../Flexbox";
import React from "react";
import useEmblaCarousel from "embla-carousel-react";
import { Challenge } from "@/apis/challenge";

interface PopularChallengeProps {
challengeInfo: Challenge;
type:
| "mostParticipatedChallenge"
| "mostAttendancedChallenge"
| "mostRecentlyStartedChallenge";
}

export default function PopularChallenge({
challengeInfo,
type,
}: PopularChallengeProps) {
const returnTitle = () => {
switch (type) {
case "mostAttendancedChallenge":
return "출석률이 가장 높은 챌린지 🔥";
break;
case "mostParticipatedChallenge":
return "참여자가 가장 많은 챌린지 🔥";
break;
case "mostRecentlyStartedChallenge":
return "가장 최근에 개설된 챌린지 🔥";
break;
}
};

export const PopularChallenge: NextPage = () => {
return (
<FlexBox
direction="col"
className="w-full bg-gray-100 items-start p-4 min-w-0 flex-[0_0_100%]"
>
<div className="h5 text-gray-700">참여자가 가장 많은 챌린지 🔥</div>
<div className="h2">레전드 영화보기 챌린지</div>
<div className="h5 text-gray-700">{returnTitle()}</div>
<div className="h2">{challengeInfo?.name}</div>
</FlexBox>
);
};
}
24 changes: 20 additions & 4 deletions components/home/searchResult.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import { Challenge } from "@/apis/challenge";
import FlexBox from "../Flexbox";
import { usePostNewChallenge } from "@/apis/hooks/challenge";

interface SearchResultProps {
onClick?: () => void;
notify: (title: string) => void;
challengeInfo: Challenge;
}

export default function SearchResult({ onClick }: SearchResultProps) {
export default function SearchResult({
notify,
challengeInfo,
}: SearchResultProps) {
const { mutate } = usePostNewChallenge(
challengeInfo.challengeIdx,
challengeInfo.name,
notify,
);

const onClick = () => mutate();

return (
<FlexBox className="w-full border-b p-2 justify-between">
<FlexBox direction="col" className="gap-1 items-start">
<div className="h3">풍물패 두드림</div>
<div className="h4 text-gray-700">서울시립도서관 4층 | 월 16시</div>
<div className="h3">{challengeInfo.name}</div>
<div className="h4 text-gray-700">
{challengeInfo.location} | {challengeInfo.schedule}
</div>
</FlexBox>
<div className="h2 text-main-color p-2" onClick={onClick}>
참여
Expand Down
17 changes: 10 additions & 7 deletions pages/challenge/join.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useGetChallengeSearch } from "@/apis/hooks/challenge";
import Divider from "@/components/Divider";
import HeadFunction from "@/components/HeadFunction";
import NavBar from "@/components/NavBar";
Expand All @@ -8,16 +9,17 @@ import Image from "next/image";
import { useState } from "react";
import { toast, ToastContainer, Zoom } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import CheckIcon from "@/public/svgs/Check.svg";

const JoinChallenge: NextPage = () => {
const [keyword, setKeyword] = useState<string>("");

const notify = () => {
toast.success("(챌린지이름)에 성공적으로 참여하셨습니다.", {
const { data } = useGetChallengeSearch(keyword);

const notify = (title: string) => {
toast.success(`${title}에 성공적으로 참여하셨습니다.`, {
position: "bottom-center",
icon: ({ theme, type }) => (
<Image src="/svgs/Check.svg" width={24} height={24} />
),
icon: ({ theme, type }) => <CheckIcon width={24} height={24} />,
});
};

Expand All @@ -27,8 +29,9 @@ const JoinChallenge: NextPage = () => {
<SearchBar value={keyword} setValue={setKeyword} />
<Divider height={8} />
<div className="w-full px-4">
<SearchResult onClick={notify} />
<SearchResult onClick={notify} />
{data?.result.map((info) => (
<SearchResult notify={notify} challengeInfo={info} />
))}
</div>
<NavBar />
<ToastContainer
Expand Down
Loading

0 comments on commit a43cb4a

Please sign in to comment.