-
Notifications
You must be signed in to change notification settings - Fork 1
[feat] 나의 후기, 북마크, 의견보내기 api연결 #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "feat/#76/\uB098\uC758\uD6C4\uAE30-\uBD81\uB9C8\uD06C-\uC758\uACAC\uBCF4\uB0B4\uAE30-api\uC5F0\uACB0"
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import api from '@/api/api'; | ||
|
|
||
| interface FeedbackRequest { | ||
| feedbackContent: string; | ||
| } | ||
|
|
||
| export const postFeedback = async (body: FeedbackRequest) => { | ||
| const res = await api.post('/feedback', body); | ||
| return res.data; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import api from '@/api/api'; | ||
| import type { PaginatedBookmarkResponse } from '@/types/review'; | ||
|
|
||
| interface PageRequest { | ||
| page: number; | ||
| size: number; | ||
| } | ||
|
|
||
| export const getMyBookmarks = async ( | ||
| params: PageRequest | ||
| ): Promise<PaginatedBookmarkResponse> => { | ||
| const res = await api.get('/profile/bookmark', { params }); | ||
| return res.data.data; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import api from '@/api/api'; | ||
| import type { ReviewItem } from '@/types/review'; | ||
|
|
||
| interface PageRequest { | ||
| page: number; | ||
| size: number; | ||
| } | ||
|
|
||
| interface PaginatedReviewResponse { | ||
| content: ReviewItem[]; | ||
| page: number; | ||
| size: number; | ||
| totalPages: number; | ||
| totalElements: number; | ||
| } | ||
|
|
||
| export const getMyReviews = async ( | ||
| params: PageRequest | ||
| ): Promise<PaginatedReviewResponse> => { | ||
| const res = await api.get('/profile/reviews', { params }); | ||
| return res.data.data; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,49 +1,57 @@ | ||
| import { useNavigate } from 'react-router-dom'; | ||
| import { useToastStore } from '@/store'; | ||
| import { postFeedback } from '@/api//feedback/feedback.api'; | ||
| import { useState } from 'react'; | ||
| import { Button, Header } from '@/components'; | ||
|
|
||
| export default function MyFeedbackPage() { | ||
| const [feedbackText, setFeedbackText] = useState(''); | ||
| const MAX_LENGTH = 1000; | ||
|
|
||
| const isButtonDisabled = feedbackText.length === 0; | ||
|
|
||
| const navigate = useNavigate(); | ||
| const { show } = useToastStore(); | ||
|
|
||
| const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
| if (e.target.value.length > MAX_LENGTH) { | ||
| setFeedbackText(e.target.value.slice(0, MAX_LENGTH)); | ||
| } else { | ||
| setFeedbackText(e.target.value); | ||
| const value = e.target.value; | ||
| setFeedbackText(value.length > MAX_LENGTH ? value.slice(0, MAX_LENGTH) : value); | ||
| }; | ||
|
|
||
| const handleSubmit = async () => { | ||
| try { | ||
| await postFeedback({ feedbackContent: feedbackText }); | ||
| show('SEEAT 팀에게 의견을 보냈어요!'); | ||
| navigate('/my'); | ||
| } catch (err) { | ||
| console.error('피드백 전송 실패:', err); | ||
| show('의견 보내기에 실패했어요.'); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex h-screen flex-col text-white"> | ||
| <div className="mx-auto flex w-full max-w-md flex-grow flex-col px-4"> | ||
| {/* 헤더 */} | ||
| <Header leftSection="BACK" rightSection="KEBAB" className="bg-gray-900"> | ||
|
||
| 의견 보내기 | ||
| </Header> | ||
|
|
||
| <main className="flex flex-grow flex-col px-2 pt-[88px] pb-4"> | ||
| <div className="flex w-full flex-col gap-2"> | ||
| <label className="text-body-2 text-gray-300">SEEAT에게 하고 싶은 말을 보내주세요</label> | ||
|
|
||
| {/* textarea와 글자수 카운터를 감싸는 컨테이너 */} | ||
| <div className="relative w-full"> | ||
| <textarea | ||
| value={feedbackText} | ||
| onChange={handleTextChange} | ||
| placeholder="피드백을 남겨주시면 SEEAT이 더 좋은 서비스를 제공할 수 있어요!" | ||
| className="placeholder:text-body-2 h-[116px] w-full resize-none rounded-lg border border-gray-800 bg-gray-900 p-3 pr-14 outline-none placeholder:text-gray-500" | ||
| /> | ||
| {/* 글자 수 카운터 */} | ||
| <div className="text-caption-3 absolute right-3 bottom-3 text-gray-500"> | ||
| {feedbackText.length}/{MAX_LENGTH} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <div className="flex-grow" /> | ||
|
|
||
| {/* 하단 버튼 영역 */} | ||
| <div className="mt-4"> | ||
| <Button | ||
| variant="primary" | ||
|
|
@@ -52,7 +60,7 @@ export default function MyFeedbackPage() { | |
| className="w-full" | ||
| fontType="title-3" | ||
| disabled={isButtonDisabled} | ||
| onClick={() => alert('피드백이 전송되었습니다!')} | ||
| onClick={handleSubmit} | ||
| > | ||
| 보내기 | ||
| </Button> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,59 @@ | ||
| import { ReviewCard, Header } from '@/components'; | ||
| import { mockMyReviews } from '@/__mocks/mockReviews'; | ||
| import { useEffect, useState } from 'react'; | ||
| import { Header, ReviewCard } from '@/components'; | ||
| import { getMyReviews } from '@/api/review/myReview.api'; | ||
| import type { ReviewItem } from '@/types/review'; | ||
|
|
||
| export default function MyReviewPage() { | ||
| const [reviews, setReviews] = useState<ReviewItem[]>([]); | ||
| const [loading, setLoading] = useState(true); | ||
| const [error, setError] = useState<string | null>(null); | ||
|
|
||
| useEffect(() => { | ||
| const fetchReviews = async () => { | ||
| try { | ||
| const { content } = await getMyReviews({ page: 1, size: 10 }); | ||
| setReviews(content); | ||
| } catch (err: unknown) { | ||
| console.error(err); | ||
| if (err instanceof Error) { | ||
| setError(err.message); | ||
| } else { | ||
| setError('후기를 불러오지 못했습니다.'); | ||
| } | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| fetchReviews(); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <div> | ||
| <div className="w-full px-4"> | ||
| <Header leftSection="BACK" rightSection="KEBAB" className="bg-gray-900"> | ||
| 나의 후기 | ||
| </Header> | ||
| <div className="w-full px-4"> | ||
| <Header leftSection="BACK" rightSection="KEBAB" className="bg-gray-900"> | ||
|
||
| 나의 후기 | ||
| </Header> | ||
|
|
||
| <main className="flex flex-col gap-y-3 py-4 pt-[88px]"> | ||
| {mockMyReviews.map((review) => ( | ||
| <ReviewCard | ||
| key={review.id} | ||
| imageUrl={review.imageUrl} | ||
| tags={review.tags} | ||
| title={review.title} | ||
| description={review.description} | ||
| likeCount={review.likeCount} | ||
| onClick={() => { | ||
| console.log(`Review ${review.id} clicked`); | ||
| }} | ||
| /> | ||
| ))} | ||
| </main> | ||
| </div> | ||
| <main className="flex flex-col gap-y-3 py-4 pt-[60px]"> | ||
| {loading && <p className="text-white">로딩 중...</p>} | ||
| {error && <p className="text-red-500">{error}</p>} | ||
| {!loading && !error && reviews.length === 0 && ( | ||
| <p className="text-gray-400">작성한 후기가 없습니다.</p> | ||
| )} | ||
| {reviews.map((review) => ( | ||
| <ReviewCard | ||
| key={review.reviewId} | ||
| imageUrl={review.thumbnailUrl} | ||
| tags={(review.hashtags ?? []).map((tag) => tag.hashTagName)} | ||
| title={review.title} | ||
| description={review.content} | ||
| likeCount={review.heartCount} | ||
| onClick={() => { | ||
| console.log(`Review ${review.reviewId} clicked`); | ||
| }} | ||
| /> | ||
| ))} | ||
| </main> | ||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
나의 후기, 북마크, 의견 보내기 페이지 헤더에 있는 오른쪽 섹션 케밥 버튼은 잘못 들어갔다고 하셔서 지워주셔도 될 것 같습니다!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 알겠습니당