-
Notifications
You must be signed in to change notification settings - Fork 2
feat : 편지작성, 랜덤편지, 상세페이지 3차 기능구현 #94
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
Changes from all commits
d81af61
2537f16
78810fb
26ab9c0
cb7053b
ebb37c5
83d6c94
c923aa3
eb8581c
69ffab2
8469d33
f7bdc3f
88f1533
749fea4
bfc0be3
bcf7853
a00ebf5
67633bc
75d35f7
25e4378
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 |
|---|---|---|
|
|
@@ -23,4 +23,17 @@ const deleteLetter = async (letterId: string) => { | |
| } | ||
| }; | ||
|
|
||
| export { getLetter, deleteLetter }; | ||
| const postEvaluateLetter = async (letterId: number, evaluation: LetterEvaluation) => { | ||
| try { | ||
| const res = await client.post(`/api/letters/${letterId}/evaluate`, { | ||
| evaluation: evaluation, | ||
| }); | ||
| if (!res) throw new Error('편지 삭제 요청 도중 에러가 발생했습니다.'); | ||
| console.log(res); | ||
| return res; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } catch (error) { | ||
| console.error(error); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| }; | ||
|
|
||
| export { getLetter, deleteLetter, postEvaluateLetter }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,6 @@ const getRandomLetters = async (category: string | null) => { | |
| try { | ||
| const res = await client.get(`/api/random-letters/${category}`); | ||
| if (!res) throw new Error('랜덤 편지 데이터를 가져오는 도중 에러가 발생했습니다.'); | ||
| console.log(res); | ||
| return res; | ||
| } catch (error) { | ||
| console.error(error); | ||
|
|
@@ -13,8 +12,6 @@ const getRandomLetters = async (category: string | null) => { | |
|
|
||
| const postRandomLettersApprove = async (approveRequest: ApproveRequest, callBack?: () => void) => { | ||
| try { | ||
| console.log('엔드포인트 : /api/random-letters/approve'); | ||
| console.log('request', approveRequest); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 필요없는 |
||
| const res = await client.post('/api/random-letters/approve', approveRequest); | ||
| if (!res) throw new Error('랜덤편지 매칭수락 도중 에러가 발생했습니다.'); | ||
| if (callBack) callBack(); | ||
|
|
@@ -30,7 +27,6 @@ const getRandomLetterMatched = async (callBack?: () => void) => { | |
| if (!res) | ||
| throw new Error('랜덤 편지 최종 매칭 시간 검증 데이터를 가자오는 도중 에러가 발생했습니다.'); | ||
| if (callBack) callBack(); | ||
| console.log(res); | ||
| return res; | ||
| } catch (error) { | ||
| console.error(error); | ||
|
|
@@ -43,7 +39,6 @@ const getRandomLetterCoolTime = async (callBack?: () => void) => { | |
| if (!res) | ||
| throw new Error('랜덤 편지 최종 매칭 시간 검증 데이터를 가자오는 도중 에러가 발생했습니다.'); | ||
| if (callBack) callBack(); | ||
| console.log(res); | ||
| return res; | ||
| } catch (error) { | ||
| console.error(error); | ||
|
|
@@ -54,7 +49,6 @@ const deleteRandomLetterMatching = async () => { | |
| try { | ||
| const res = await client.delete('/api/random-letters/matching/cancel'); | ||
| if (!res) throw new Error('매칭 취소 도중 에러가 발생했습니다.'); | ||
| console.log(res); | ||
| return res; | ||
| } catch (error) { | ||
| console.log(error); | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 빨리 데이터 전달 드릴게요!!! 🥹 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,22 +2,21 @@ | |
| import client from './client'; | ||
|
|
||
| const postLetter = async (data: LetterRequest) => { | ||
| console.log('request', data); | ||
| try { | ||
| const res = await client.post('/api/letters', data); | ||
| if (!res) throw new Error('편지 전송과정중에서 오류가 발생했습니다.'); | ||
| console.log(`api 주소 : /api/letters, 전송타입 : post`); | ||
| if (!res) throw new Error('편지 전송과정에서 오류가 발생했습니다.'); | ||
| return res; | ||
| } catch (error) { | ||
| console.error(error); | ||
| } | ||
| }; | ||
|
|
||
| const postFirstReply = async (data: FirstReplyRequest) => { | ||
| console.log('Firstrequest', data); | ||
| try { | ||
| const res = await client.post('/api/random-letters/matching', data); | ||
| if (!res) throw new Error('최초 답장 전송과정중에서 오류가 발생했습니다.'); | ||
| console.log(`api 주소 : /api/random-letters/matching, 전송타입 : post`); | ||
| console.log(res); | ||
| if (!res) throw new Error('최초 답장 전송과정에서 오류가 발생했습니다.'); | ||
| return res; | ||
| } catch (error) { | ||
| console.error(error); | ||
|
|
@@ -27,11 +26,33 @@ const postFirstReply = async (data: FirstReplyRequest) => { | |
| const getPrevLetter = async (letterId: string) => { | ||
| try { | ||
| const res = await client.get(`/api/letters/${letterId}/previous`); | ||
| console.log(res); | ||
| if (!res) throw new Error('이전편지를 불러오는중 오류가 발생했습니다.'); | ||
| return res; | ||
| } catch (error) { | ||
| console.error(error); | ||
| } | ||
| }; | ||
|
|
||
| export { postLetter, postFirstReply, getPrevLetter }; | ||
| // 임시저장 최초 생성 | ||
| const postTemporarySave = async (data: TemporaryRequest) => { | ||
| try { | ||
| const res = client.post(`/api/letters/temporary-save`, data); | ||
| if (!res) throw new Error('편지 임시저장과정에서 오류가 발생했습니다.'); | ||
| return res; | ||
| } catch (error) { | ||
| console.error(error); | ||
| } | ||
| }; | ||
|
|
||
| // 임시저장 수정 | ||
| const PatchTemporarySave = async (data: TemporaryRequest) => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수명은 카멜 케이스로 통일하기로 했으니 바꿔주시면 좋을 것 같아요! |
||
| try { | ||
| const res = client.post(`/api/letters/temporary-save`, data); | ||
| if (!res) throw new Error('편지 임시저장과정에서 오류가 발생했습니다.'); | ||
| return res; | ||
| } catch (error) { | ||
| console.error(error); | ||
| } | ||
| }; | ||
|
|
||
| export { postLetter, postFirstReply, getPrevLetter, postTemporarySave, PatchTemporarySave }; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 함수마다 앞에 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { postEvaluateLetter } from '@/apis/letterDetail'; | ||
| import { CloudIcon, SnowIcon, WarmIcon } from '@/assets/icons'; | ||
|
|
||
| interface DegreeSelector { | ||
| letterDetail: LetterDetail | null; | ||
| setLetterDetail: React.Dispatch<React.SetStateAction<LetterDetail>>; | ||
| } | ||
| export default function DegreeSelector({ letterDetail, setLetterDetail }: DegreeSelector) { | ||
| const handlePostEvaluateLetter = async ( | ||
| letterId: number | undefined, | ||
| evaluation: LetterEvaluation, | ||
| ) => { | ||
| if (!letterId) return alert('편지id값이 담겨있지 않습니다.'); | ||
| const res = await postEvaluateLetter(letterId, evaluation); | ||
| if (res?.status === 200) { | ||
| console.log('평가완료'); | ||
| setLetterDetail((cur) => ({ ...cur, evaluated: true })); | ||
| } | ||
| }; | ||
| const DEGREES = [ | ||
| { | ||
| icon: <WarmIcon className="h-5 w-5" />, | ||
| title: '따뜻해요', | ||
| onClick: () => { | ||
| handlePostEvaluateLetter(letterDetail?.letterId, 'GOOD'); | ||
| }, | ||
| }, | ||
| { | ||
| icon: <CloudIcon className="h-5 w-5" />, | ||
| title: '그럭저럭', | ||
| onClick: () => { | ||
| handlePostEvaluateLetter(letterDetail?.letterId, 'SOSO'); | ||
| }, | ||
| }, | ||
| { | ||
| icon: <SnowIcon className="h-5 w-5" />, | ||
| title: '앗! 차가워', | ||
| onClick: () => { | ||
| handlePostEvaluateLetter(letterDetail?.letterId, 'BAD'); | ||
| }, | ||
| }, | ||
| ]; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서는 |
||
| return ( | ||
| <div className="caption-b text-primary-1 bg-primary-5 absolute top-7 z-40 flex flex-col gap-1 p-2 shadow"> | ||
| {DEGREES.map((degree, idx) => { | ||
| return ( | ||
| <button | ||
| key={idx} | ||
| className="flex items-center justify-start gap-1" | ||
| onClick={() => { | ||
| degree.onClick(); | ||
| }} | ||
| > | ||
| {degree.icon} | ||
| {degree.title} | ||
| </button> | ||
| ); | ||
| })} | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { twMerge } from 'tailwind-merge'; | ||
|
|
||
| import { FONT_TYPE_OBJ } from '@/pages/Write/constants'; | ||
|
|
||
| interface LetterDetailContent { | ||
| letterDetail: LetterDetail; | ||
| } | ||
| export default function LetterDetailContent({ letterDetail }: LetterDetailContent) { | ||
| return ( | ||
| <> | ||
| <div className="flex flex-col gap-3 px-5"> | ||
| <span className="body-b mt-[55px]">TO. 따숨이</span> | ||
| <span className="body-sb">{letterDetail.title}</span> | ||
| </div> | ||
| <textarea | ||
| readOnly | ||
| value={letterDetail.content} | ||
| className={twMerge( | ||
| `body-r basic-theme min-h-full w-full grow resize-none px-6`, | ||
| letterDetail && FONT_TYPE_OBJ[letterDetail.fontType], | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| )} | ||
| ></textarea> | ||
| <span className="body-sb mt-10 flex justify-end">FROM. {letterDetail.zipCode}</span> | ||
| </> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { useEffect, useRef } from 'react'; | ||
|
|
||
| import { ThermostatIcon } from '@/assets/icons'; | ||
|
|
||
| interface LetterDetailDegreeButton { | ||
| letterDetail: LetterDetail | null; | ||
| setDegreeModalOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||
| } | ||
| export default function LetterDetailDegreeButton({ | ||
| letterDetail, | ||
| setDegreeModalOpen, | ||
| }: LetterDetailDegreeButton) { | ||
| const degreeButtonRef = useRef<HTMLButtonElement>(null); | ||
|
|
||
| useEffect(() => { | ||
| const handleOutsideClick = (event: MouseEvent) => { | ||
| const target = event.target as Node; | ||
| if (!target || degreeButtonRef.current?.contains(target)) { | ||
| return; | ||
| } | ||
| setDegreeModalOpen(false); | ||
| }; | ||
|
|
||
| document.body.addEventListener('click', handleOutsideClick); | ||
|
|
||
| return () => { | ||
| document.body.removeEventListener('click', handleOutsideClick); | ||
| }; | ||
| }, [setDegreeModalOpen]); | ||
| return ( | ||
| <> | ||
| {letterDetail?.evaluated ? ( | ||
| <div> | ||
| <span className="caption-b text-primary-1">온도 측정된 편지에요!</span> | ||
| </div> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| ) : ( | ||
| <button | ||
| ref={degreeButtonRef} | ||
| className="flex items-center justify-center gap-1" | ||
| onClick={() => { | ||
| setDegreeModalOpen((cur) => !cur); | ||
| }} | ||
| > | ||
| <ThermostatIcon className="h-6 w-6" /> | ||
| <span className="caption-b text-primary-1">편지 온도</span> | ||
| </button> | ||
| )} | ||
| </> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { useState } from 'react'; | ||
|
|
||
| import { DeleteIcon, SirenOutlinedIcon } from '@/assets/icons'; | ||
| import BackButton from '@/components/BackButton'; | ||
| import useAuthStore from '@/stores/authStore'; | ||
|
|
||
| import DegreeSelector from './DegreeSelector'; | ||
| import LetterDetailDegreeButton from './LetterDetailDegreeButton'; | ||
|
|
||
| interface LetterDetailHeader { | ||
| letterDetail: LetterDetail; | ||
| setLetterDetail: React.Dispatch<React.SetStateAction<LetterDetail>>; | ||
| setDeleteModalOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||
| setReportModalOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||
| } | ||
| export default function LetterDetailHeader({ | ||
| letterDetail, | ||
| setLetterDetail, | ||
| setDeleteModalOpen, | ||
| setReportModalOpen, | ||
| }: LetterDetailHeader) { | ||
| const [degreeModalOpen, setDegreeModalOpen] = useState<boolean>(false); | ||
|
|
||
| const userZipCode = useAuthStore((state) => state.zipCode); | ||
|
|
||
| return ( | ||
| <div className="absolute top-5 left-0 flex w-full justify-between px-5"> | ||
| <BackButton /> | ||
| <div className="flex gap-2"> | ||
| {userZipCode !== letterDetail?.zipCode && ( | ||
| <LetterDetailDegreeButton | ||
| letterDetail={letterDetail} | ||
| setDegreeModalOpen={setDegreeModalOpen} | ||
| /> | ||
| )} | ||
| {userZipCode === letterDetail?.zipCode && ( | ||
| <button | ||
| onClick={() => { | ||
| setDeleteModalOpen(true); | ||
| }} | ||
| > | ||
| <DeleteIcon className="text-primary-1 h-6 w-6" /> | ||
| </button> | ||
| )} | ||
| {userZipCode !== letterDetail?.zipCode && ( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이게 중복으로 등장하는 것 같아 상수로 빼면 깔끔할 것 같습니다! |
||
| <button | ||
| onClick={() => { | ||
| setReportModalOpen(true); | ||
| }} | ||
| > | ||
| <SirenOutlinedIcon className="text-primary-1 h-6 w-6" /> | ||
| </button> | ||
| )} | ||
| {degreeModalOpen && ( | ||
| <DegreeSelector letterDetail={letterDetail} setLetterDetail={setLetterDetail} /> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { useNavigate } from 'react-router'; | ||
|
|
||
| interface LetterDetailReplyButton { | ||
| letterDetail: LetterDetail; | ||
| } | ||
| export default function LetterDetailReplyButton({ letterDetail }: LetterDetailReplyButton) { | ||
| const navigate = useNavigate(); | ||
| return ( | ||
| <button | ||
| className="bg-primary-3 disabled:bg-gray-30 body-m mt-3 w-full rounded-lg py-2 disabled:text-white" | ||
| onClick={() => { | ||
| navigate(`/letter/write/?letterId=${letterDetail.letterId}`); | ||
| }} | ||
| disabled={!letterDetail?.matched} | ||
| > | ||
| {letterDetail?.matched ? '편지 작성하기' : '대화가 종료된 편지입니다.'} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| </button> | ||
| ); | ||
| } | ||
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.
이건 임시로 주석 처리하신 건가용?