Skip to content

Commit

Permalink
Merge branch 'main' into 13-feature-Challenge-Calendar-API
Browse files Browse the repository at this point in the history
  • Loading branch information
leejin-rho authored Jul 1, 2024
2 parents ef75a70 + a43cb4a commit 7b0c483
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 42 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/nextjs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ jobs:
# Build job
build:
runs-on: ubuntu-latest

env:
NEXT_PUBLIC_API_URL: ${{secrets.NEXT_PUBLIC_API_URL}}

steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -75,6 +79,11 @@ jobs:
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- name: Install dependencies
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
- name: Check environment variable
run: echo NEXT_PUBLIC_API_URL
- name: Create .env file
run: |
echo NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }} >> .env
- name: Build with Next.js
run: ${{ steps.detect-package-manager.outputs.runner }} next build
- name: Export with Next.js
Expand All @@ -84,6 +93,7 @@ jobs:
with:
path: ./out


# Deployment job
deploy:
environment:
Expand Down
42 changes: 39 additions & 3 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,6 +13,18 @@ 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;
Expand All @@ -32,8 +44,32 @@ async function getChallengDetail(): Promise<GetChallengeDetailBody> {
const response = await client.get(
`/challenges/attendance/2?year=2024&month=6`,
);
// console.log("challengeData", response.data.result);
return response.data.result;
}

export { getMyChallengeList, getChallengDetail };
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,
getChallengDetail
};
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 };
59 changes: 56 additions & 3 deletions apis/hooks/challenge.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { getMyChallengeList, getChallengDetail } from "../challenge";
import { useQuery } from "@tanstack/react-query";
import {
getChallengeAds,
getChallengeSearch,
getMyChallengeList,
postNewChallenge,
getChallengDetail,
} from "../challenge";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";


function useGetMyChallengeList() {
const { data } = useQuery({
Expand All @@ -18,4 +25,50 @@ function useGetChallengeDetail() {
return { data };
}

export { useGetMyChallengeList, useGetChallengeDetail };
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,
useGetChallengeDetail,
};

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
Loading

0 comments on commit 7b0c483

Please sign in to comment.