Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import LetterBoxDetailPage from './pages/LetterBoxDetail';
import LetterDetailPage from './pages/LetterDetail';
import LoginPage from './pages/Login';
import MyPage from './pages/MyPage';
import MyBoardPage from './pages/MyPage/components/MyBoardPage';
import NotFoundPage from './pages/NotFound';
import NotificationsPage from './pages/Notifications';
import OnboardingPage from './pages/Onboarding';
Expand Down Expand Up @@ -57,7 +58,7 @@ const App = () => {
</Route>
<Route path="mypage" element={<Layout />}>
<Route index element={<MyPage />} />
<Route path="board" element={<LetterBoardPage />} />
<Route path="board" element={<MyBoardPage />} />
<Route path="notifications" element={<NotificationsPage />} />
</Route>
</Route>
Expand Down
5 changes: 5 additions & 0 deletions src/apis/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,15 @@ export const deleteUserInfo = async () => {

export const postLogout = async () => {
try {
console.log(' before logout');

const response = await client.post('/api/logout', { withCredentials: true });
console.log('logout', response);
if (!response) throw new Error('postLogout: failed to logout');
return response;
} catch (error) {
console.log('logout error');

console.error(error);
}
};
81 changes: 41 additions & 40 deletions src/apis/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ const client = axios.create({
headers: { 'Content-Type': 'application/json' },
});

type FailedRequest = {
resolve: (token: string) => void;
reject: (error: unknown) => void;
};
// type FailedRequest = {
// resolve: (token: string) => void;
// reject: (error: unknown) => void;
// };

let isRefreshing = false;
let failedQueue: FailedRequest[] = [];

const processQueue = (error: unknown, token: string | null = null) => {
failedQueue.forEach((prom) => {
if (error) {
prom.reject(error);
} else {
if (token) {
prom.resolve(token);
}
}
});

failedQueue = [];
};
// let failedQueue: FailedRequest[] = [];

// const processQueue = (error: unknown, token: string | null = null) => {
// failedQueue.forEach((prom) => {
// if (error) {
// prom.reject(error);
// } else {
// if (token) {
// prom.resolve(token);
// }
// }
// });

// failedQueue = [];
// };
Comment on lines +18 to +32
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 주석들은 임시처리인가요 아니면 삭제될 코드들인가요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

임시 처리입니다! 혹시 요청이 여러번 생기는 경우에 어떻게 될지 몰라서 일단은 이 로직을 참고하려고 놔뒀어요!


const callReissue = async () => {
try {
Expand All @@ -41,6 +41,8 @@ const callReissue = async () => {
}
};

let retry = false;

client.interceptors.request.use(
(config) => {
console.log('response again', config);
Expand Down Expand Up @@ -68,43 +70,42 @@ client.interceptors.response.use(
return Promise.reject(error);
}

if (
(error.response?.status === 401 || error.response?.status === 403) &&
!originalRequest._retry
) {
originalRequest._retry = true;

if ((error.response?.status === 401 || error.response?.status === 403) && !retry) {
retry = true;
if (isRefreshing) {
try {
return new Promise((resolve, reject) => {
failedQueue.push({
resolve: (token: string) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(client(originalRequest));
},
reject: (err: unknown) => reject(err),
});
});
} catch (e) {
return Promise.reject(e);
}
if (isLoggedIn) logout();
// try {
// return new Promise((resolve, reject) => {
// failedQueue.push({
// resolve: (token: string) => {
// originalRequest.headers.Authorization = `Bearer ${token}`;
// resolve(client(originalRequest));
// },
// reject: (err: unknown) => reject(err),
// });
// });
// } catch (e) {
// return Promise.reject(e);
// }
Comment on lines +77 to +89
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요것두!

} else {
isRefreshing = true;
try {
const newToken = await callReissue();
setAccessToken(newToken);
processQueue(null, newToken);
// processQueue(null, newToken);
isRefreshing = false;
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return client(originalRequest);
} catch (e) {
processQueue(e, null);
// processQueue(e, null);
isRefreshing = false;
if (isLoggedIn) logout();
return Promise.reject(e);
}
}
}
if (isLoggedIn) logout();
console.error('Failed to refresh token', error);
return Promise.reject(error);
},
);
Expand Down
10 changes: 10 additions & 0 deletions src/apis/myPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,13 @@ export const fetchMyPageInfo = async () => {
console.error(error);
}
};

export const getMySharePostList = async () => {
try {
const response = await client.get('/api/share-proposals/inbox');
if (!response) throw new Error('error while fetching my share post list');
return response.data;
} catch (error) {
console.error(error);
}
};
31 changes: 25 additions & 6 deletions src/apis/share.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ export interface SharePostApproval {
}

// 공유 게시글 목록 조회
export const getSharePostList = async (
page: number = 1,
size: number = 10,
): Promise<SharePostResponse> => {
export const getSharePostList = async (page: number = 1, size: number = 10) => {
try {
const response = await client.get('/api/share-posts', {
params: { page, size },
Expand Down Expand Up @@ -70,14 +67,12 @@ export const getSharePostDetail = async (sharePostId: number): Promise<SharePost
// 공유 요청 보내기
export const postShareProposals = async (
letterIds: number[],
requesterId: number,
recipientId: number,
message: string,
) => {
try {
const response = await client.post('/api/share-proposals', {
letterIds: letterIds,
requesterId,
recipientId,
message,
});
Expand Down Expand Up @@ -105,3 +100,27 @@ export const postShareProposalApproval = async (
throw new Error(`편지 공유 ${action === 'approve' ? '수락' : '거부'} 실패`);
}
};

// 편지 좋아요 추가, 취소
export const postSharePostLike = async (sharePostId: number) => {
try {
const response = await client.post(`/api/share-posts/${sharePostId}/likes`);
if (!response) throw new Error('error while posting like');
return response.data;
} catch (error) {
console.error('❌ 편지 좋아요 중 에러가 발생했습니다', error);
throw new Error('편지 좋아요 실패');
}
};

// 편지 좋아요 갯수
export const getSharePostLikeCount = async (sharePostId: number) => {
try {
const response = await client.get(`/api/share-posts/${sharePostId}/likes`);
if (!response) throw new Error('error while fetching likes');
return response.data;
} catch (error) {
console.error('❌ 편지 좋아요 갯수 조회 중 에러가 발생했습니다', error);
throw new Error('편지 좋아요 갯수 조회 실패');
}
};
4 changes: 3 additions & 1 deletion src/layouts/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const Header = () => {
<Link to="/mypage/notifications">
<AlarmIcon className="h-6 w-6 text-white" />
</Link>
<PersonIcon className="h-6 w-6 text-white" />
<Link to="/mypage">
<PersonIcon className="h-6 w-6 text-white" />
</Link>
</div>
</header>
);
Expand Down
27 changes: 14 additions & 13 deletions src/pages/LetterBoard/components/LetterPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import { Link } from 'react-router';
import { forwardRef } from 'react';
import { useNavigate } from 'react-router';

import LetterWrapper from '@/components/LetterWrapper';

interface LetterPreviewProps {
id: string;
id: number;
to: string;
from: string;
content: string;
}

const LetterPreview = ({ id, to, from, content }: LetterPreviewProps) => {
const LetterPreview = forwardRef<HTMLDivElement, LetterPreviewProps>((props, ref) => {
const { id, to, from, content }: LetterPreviewProps = props;
const navigate = useNavigate();
return (
<Link to={id}>
<LetterWrapper className="px-3 py-2">
<div className="caption-r flex flex-col gap-2">
<p>From.{from}</p>
<p className="line-clamp-2 font-light">{content}</p>
<p className="place-self-end">To.{to}</p>
</div>
</LetterWrapper>
</Link>
<LetterWrapper className="px-3 py-2" ref={ref} onClick={() => navigate(`/board/letter/${id}`)}>
<div className="caption-r flex flex-col gap-2">
<p>From.{from}</p>
<p className="line-clamp-2 font-light">{content}</p>
<p className="place-self-end">To.{to}</p>
</div>
</LetterWrapper>
);
};
});

export default LetterPreview;
95 changes: 71 additions & 24 deletions src/pages/LetterBoard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,87 @@
import { twMerge } from 'tailwind-merge';
import { useInfiniteQuery } from '@tanstack/react-query';
import { useEffect } from 'react';
import { useInView } from 'react-intersection-observer';
import { useNavigate } from 'react-router';

import { getSharePostList } from '@/apis/share';
import BackgroundBottom from '@/components/BackgroundBottom';
import NoticeRollingPaper from '@/components/NoticeRollingPaper';
import PageTitle from '@/components/PageTitle';

import LetterPreview from './components/LetterPreview';

const LetterBoardPage = () => {
const isMyBoard = window.location.pathname.includes('/mypage');
const navigate = useNavigate();
const { ref, inView } = useInView();

const fetchPostList = async (page: number = 1) => {
try {
const response = await getSharePostList(page);
if (!response) throw new Error('게시글 목록을 불러오는데 실패했습니다.');
console.log('page', response);
return response as SharePostResponse;
} catch (e) {
console.error(e);
}
};

const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfiniteQuery({
queryKey: ['sharePostList'],
queryFn: ({ pageParam = 1 }) => fetchPostList(pageParam),
enabled: true,
initialPageParam: 1,
getNextPageParam: (res) => {
if (!res || res.currentPage >= res.totalPages) {
return undefined;
}
return res.currentPage + 1;
},
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 10,
});

const postLists = data?.pages.flatMap((page) => page?.content) || [];

useEffect(() => {
if (!hasNextPage) return;
if (inView && !isFetchingNextPage) {
fetchNextPage();
}
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);

if (isError) {
navigate('/notFound');
}

return (
<>
<main className={twMerge('flex grow flex-col px-5 pt-20 pb-10', !isMyBoard && 'mt-[-25px]')}>
{isMyBoard ? (
<PageTitle className="mx-auto mb-11">내가 올린 게시물</PageTitle>
<main className="mt-[-25px] flex grow flex-col px-5 pt-20 pb-10">
<>
<NoticeRollingPaper />
<PageTitle className="mx-auto mt-4">게시판</PageTitle>
<p className="text-gray-60 caption-m mt-4.5 text-center">
따숨이에게 힘이 되었던 다양한 편지들을 모아두었어요
</p>
</>
{isLoading ? (
<p>loading</p>
) : (
<>
<NoticeRollingPaper />
<PageTitle className="mx-auto mt-4">게시판</PageTitle>
<p className="text-gray-60 caption-m mt-4.5 text-center">
따숨이에게 힘이 되었던 다양한 편지들을 모아두었어요
</p>
</>
<section className="mt-6 grid grid-cols-2 gap-x-5 gap-y-4">
{postLists.map((item, index) => {
return (
<LetterPreview
key={index}
id={item?.sharePostId || 0}
to={item?.receiverZipCode || 'ERROR'}
from={item?.writerZipCode || 'ERROR'}
content={item?.content || 'no Data'}
ref={index === postLists.length - 1 ? ref : null}
/>
);
})}
</section>
)}
<section className="mt-6 grid grid-cols-2 gap-x-5 gap-y-4">
{Array.from({ length: 10 }).map((_, index) => (
<LetterPreview
key={index}
id={`${index}`}
to="12E21"
from="12E21"
content="저희가 주고 받은 행운의 편지 저희가 주고 받은 행운의 편지 저희가 주고 받은 행운의 편지
저희가 주고 받은 행운의 편지"
/>
))}
</section>
</main>
<BackgroundBottom />
</>
Expand Down
Loading