diff --git a/README.md b/README.md
index 380abd2..ffa39ef 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,13 @@ https://zzansuni-fe-vercel.vercel.app/
사용자들에게 챌린지에 대해 공유 할 수 있으며, 랭킹을 통해 서로 경쟁할 수
있습니다.
+### 개발자 소개
+
+| 백엔드 | 백엔드 | 프론트엔드 | 프론트엔드 | 백엔드 |
+| :----------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------: |
+| [](https://github.com/momnpa333) | [](https://github.com/kwonssshyeon) | [](https://github.com/Dobbymin) | [](https://github.com/joojjang) | [](https://github.com/bayy1216) |
+| 권다운 | 권수현 | 김강민 | 김민주 | 손홍석 |
+
### 개발 동기
기획 단계에서 팀원들과 다양한 아이디어에 대해 생각을 해 보았고, 공통적으로
@@ -27,3 +34,20 @@ https://zzansuni-fe-vercel.vercel.app/
그 결과 다양한 주제의 챌린지를 진행할 수 있는 '짠순이' 라는 서비스를 기획하게
되었습니다.
+## 기술 스택
+
+- Frontend: `React`, `vite`, `typescript`, `@emotion/styled`, `@emotion/react`
+- Backend: `Spring`, `JPA`, `MySQL`, `S3`
+
+## 아키텍쳐
+![image](https://github.com/user-attachments/assets/e0bfee6d-4de3-448b-ae20-f78936a7074e)
+
+
+## ERD
+
+![image](https://github.com/user-attachments/assets/8cd2aa03-9bd1-4ee0-a511-451711da40bb)
+
+
+## CLASS DIAGRAM
+
+![image](https://github.com/user-attachments/assets/60e5ea66-eb87-4f9f-84bd-94eb2e70963e)
diff --git a/src/apis/challenge-completes/challenge-completes.api.ts b/src/apis/challenge-completes/challenge-completes.api.ts
new file mode 100644
index 0000000..0b956bf
--- /dev/null
+++ b/src/apis/challenge-completes/challenge-completes.api.ts
@@ -0,0 +1,27 @@
+import { axiosClient } from '../AxiosClient';
+import { ChallengeCompletesResponse } from './challenge-completes.response';
+import { useQuery } from '@tanstack/react-query';
+
+export const challengeCompletesPath = () => '/api/user/challenges/completes';
+
+export const ChallengeCompletesQueryKey = [challengeCompletesPath()];
+
+export const getChallengeCompletes = async (
+ page: number,
+ size: number
+): Promise => {
+ const response = await axiosClient.get(challengeCompletesPath(), {
+ params: {
+ page,
+ size,
+ },
+ });
+ return response.data;
+};
+
+export const useGetChallengeCompletes = (page: number, size: number) => {
+ return useQuery({
+ queryKey: [ChallengeCompletesQueryKey, page, size],
+ queryFn: () => getChallengeCompletes(page, size),
+ });
+};
diff --git a/src/apis/challenge-completes/challenge-completes.response.ts b/src/apis/challenge-completes/challenge-completes.response.ts
new file mode 100644
index 0000000..feec96c
--- /dev/null
+++ b/src/apis/challenge-completes/challenge-completes.response.ts
@@ -0,0 +1,18 @@
+import ApiResponse from '../ApiResponse';
+
+export type ChallengeData = {
+ id: number;
+ challengeGroupId: number;
+ title: string;
+ successDate: string;
+ category: 'HEALTH' | 'ECHO' | 'SHARE' | 'VOLUNTEER';
+ reviewWritten: boolean;
+};
+
+export type ChallengeCompletes = {
+ totalPage: number;
+ hasNext: boolean;
+ data: ChallengeData[];
+};
+
+export type ChallengeCompletesResponse = ApiResponse;
diff --git a/src/components/common/star-rating/index.tsx b/src/components/common/star-rating/index.tsx
index 4717606..dc1a998 100644
--- a/src/components/common/star-rating/index.tsx
+++ b/src/components/common/star-rating/index.tsx
@@ -11,13 +11,16 @@ interface StarRatingProps {
export const StarRating = ({ rating, size = 24, onClick }: StarRatingProps) => {
const [ratingToPercent, setRatingToPercent] = useState(0);
+ // rating 새로 전달받을 때마다 퍼센테이지 계산
useEffect(() => {
if (rating !== undefined) {
setRatingToPercent((rating / 5) * 100);
}
}, [rating]);
- const handleClick = (rating: number) => {
+ // 별점 클릭 핸들러
+ const handleClickStar = (rating: number, e: React.MouseEvent) => {
+ e.preventDefault();
if (onClick) {
onClick(rating + 1); // 클릭한 별점 값 전달 (1부터 시작)
}
@@ -27,14 +30,20 @@ export const StarRating = ({ rating, size = 24, onClick }: StarRatingProps) => {
{[...Array(5)].map((_, index) => (
- handleClick(index)}>
+ handleClickStar(index, e)}
+ >
★
))}
{[...Array(5)].map((_, index) => (
- handleClick(index)}>
+ handleClickStar(index, e)}
+ >
★
))}
diff --git a/src/components/common/form/textarea/tooltip/index.tsx b/src/components/common/tooltip/index.tsx
similarity index 99%
rename from src/components/common/form/textarea/tooltip/index.tsx
rename to src/components/common/tooltip/index.tsx
index ecd13ad..2058701 100644
--- a/src/components/common/form/textarea/tooltip/index.tsx
+++ b/src/components/common/tooltip/index.tsx
@@ -60,7 +60,7 @@ const TooltipBox = styled.div<{ direction: string }>`
font-size: 12px;
font-weight: 500;
white-space: nowrap;
- z-index: 10;
+ z-index: 5;
display: flex;
align-items: center;
diff --git a/src/components/features/layout/nav-bar/index.tsx b/src/components/features/layout/nav-bar/index.tsx
index 28e23f8..9289dc7 100644
--- a/src/components/features/layout/nav-bar/index.tsx
+++ b/src/components/features/layout/nav-bar/index.tsx
@@ -16,7 +16,7 @@ const NavBar = () => {
return (
{navBarData.map((item) => (
- handleNav(item.path)}>
+ handleNav(item.path)}>
))}
diff --git a/src/components/features/layout/top-bar/page-bar.tsx b/src/components/features/layout/top-bar/page-bar.tsx
index 7af2507..1398f40 100644
--- a/src/components/features/layout/top-bar/page-bar.tsx
+++ b/src/components/features/layout/top-bar/page-bar.tsx
@@ -60,7 +60,7 @@ const PageBarLayout = styled(Box)<{
padding: 0.5rem;
gap: 1rem;
background-color: ${(props) => props.backgroundColor};
- z-index: 1;
+ z-index: 10;
position: sticky;
top: ${({ show }) => (show ? '0' : '-100px')};
transition: top 0.3s;
diff --git a/src/pages/challenge-record/records/index.tsx b/src/pages/challenge-record/records/index.tsx
index b4abdeb..2ca9cda 100644
--- a/src/pages/challenge-record/records/index.tsx
+++ b/src/pages/challenge-record/records/index.tsx
@@ -9,7 +9,7 @@ import {
ChallengeRecordData,
ChallengeRecordDetailData,
} from '@/apis/challenge-record/challenge.record.response';
-import Tooltip from '@/components/common/form/textarea/tooltip';
+import Tooltip from '@/components/common/tooltip';
import { formatDate } from '@/utils/formatters';
import { Text } from '@chakra-ui/react';
import styled from '@emotion/styled';
diff --git a/src/pages/my-challenge-record/components/list-item.tsx b/src/pages/my-challenge-record/components/list-item.tsx
index 6da7be0..0d0523f 100644
--- a/src/pages/my-challenge-record/components/list-item.tsx
+++ b/src/pages/my-challenge-record/components/list-item.tsx
@@ -9,12 +9,10 @@ import styled from '@emotion/styled';
type Props = {
challengeId: number;
challengeTitle: string;
- userNickname: string;
- profileImageUrl?: string | null;
};
-const ListItem = ({ challengeId, challengeTitle, profileImageUrl }: Props) => {
- sessionStorage.setItem('activeTab', '0'); // 선택 탭 초기화
+const ListItem = ({ challengeId, challengeTitle }: Props) => {
+ sessionStorage.setItem('activeTab', '0');
const navigate = useNavigate();
@@ -51,11 +49,7 @@ const ListItem = ({ challengeId, challengeTitle, profileImageUrl }: Props) => {
return (
-
+
handleChallengeClick(challengeId, challengeTitle)}
diff --git a/src/pages/my-challenge-record/index.tsx b/src/pages/my-challenge-record/index.tsx
index 9051c5d..91243e1 100644
--- a/src/pages/my-challenge-record/index.tsx
+++ b/src/pages/my-challenge-record/index.tsx
@@ -1,8 +1,8 @@
import { useCallback, useEffect, useState } from 'react';
import ListItem from './components/list-item';
-import { useGetReview } from '@/apis/my-challenge-record/getReview.api';
-import { ChallengeData } from '@/apis/my-challenge-record/getReview.response';
+import { useGetChallengeCompletes } from '@/apis/challenge-completes/challenge-completes.api';
+import { ChallengeData } from '@/apis/challenge-completes/challenge-completes.response';
import EmptyState from '@/components/common/empty-state';
import TopBar, { HEADER_HEIGHT } from '@/components/features/layout/top-bar';
import { Box, Spinner } from '@chakra-ui/react';
@@ -11,7 +11,7 @@ import styled from '@emotion/styled';
const MyChallengeRecord = () => {
const [page, setPage] = useState(0);
const [allChallenges, setAllChallenges] = useState([]);
- const { data, isLoading } = useGetReview(page, 20);
+ const { data, isLoading } = useGetChallengeCompletes(page, 20);
const loadMoreChallenges = useCallback(() => {
if (data?.data.hasNext && !isLoading) {
@@ -54,11 +54,9 @@ const MyChallengeRecord = () => {
{allChallenges.length > 0 ? (
allChallenges.map((challenge, index) => (
))
) : (
diff --git a/src/pages/rank/components/all/index.tsx b/src/pages/rank/components/all/index.tsx
index 0f2f280..6054a9d 100644
--- a/src/pages/rank/components/all/index.tsx
+++ b/src/pages/rank/components/all/index.tsx
@@ -16,7 +16,7 @@ const AllRank = () => {
useEffect(() => {
const fetchUserRanking = async () => {
- const response: UserRankingResponse = await getUserRanking(1, 10);
+ const response: UserRankingResponse = await getUserRanking(0, 10);
const pageData = response.data;
const allUserData: User[] = pageData.data.map((user: UserData) => ({
...user,
diff --git a/src/pages/review-write/index.tsx b/src/pages/review-write/index.tsx
index 401d663..51e36ee 100644
--- a/src/pages/review-write/index.tsx
+++ b/src/pages/review-write/index.tsx
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import { postReview } from '@/apis/review/review.api';
import ChallengeTitle from '@/components/common/challenge-title';
@@ -6,6 +7,7 @@ import CTA, { CTAContainer } from '@/components/common/cta';
import Textarea from '@/components/common/form/textarea';
import { StarRating } from '@/components/common/star-rating';
import TopBar, { HEADER_HEIGHT } from '@/components/features/layout/top-bar';
+import { RouterPath } from '@/routes/path';
import {
formatRating,
formatDifficulty,
@@ -35,6 +37,7 @@ const ReviewWrite = () => {
const [content, setContent] = useState('');
const [isContentValid, setIsContentValid] = useState(true);
const [isButtonDisabled, setIsButtonDisabled] = useState(true);
+ const navigate = useNavigate();
const handleDifficultyClick = (difficulty: number) => {
setSelectedDifficulty(difficulty);
@@ -83,6 +86,7 @@ const ReviewWrite = () => {
})
.then(() => {
alert('리뷰가 등록되었습니다!');
+ navigate(`/${RouterPath.challenge}/${RouterPath.myRecord}`);
})
.catch((error) => {
// API에서 받은 오류 객체일 경우
@@ -242,6 +246,8 @@ const Chip = styled.button<{ isSelected: boolean }>`
font-size: var(--font-size-sm);
font-weight: 600;
text-align: center;
+ cursor: pointer;
+ outline: none;
${({ isSelected }) =>
isSelected &&
`