Skip to content

Commit

Permalink
✨ 피드 메인 페이지 로딩 및 에러 처리 (#27)
Browse files Browse the repository at this point in the history
* style: feed 컴포넌트 위치 이동
* feat: feed public api 제거
* design: profile 스타일 설정
* feat: profile 스켈레톤 UI
* chore: vite host 옵션 추가
* feat: 피드 메인 페이지 Skeleton UI 추가
* design: Skeleton 애니메이션 추가
* design: 스켈레톤 UI 적용
* design: body reset 값 설정
* design: 피드 메인 페이지 헤더 디자인 조정
* feat: 로딩 화면 스크롤 비활성화
* fix: API 인터셉터 onError 메서드 수정
* feat: 네트워크 에러 컴포넌트
* style: 네트워크 에러 이미지 경로 수정
* feat: 피드 메인 페이지 public api 추가
* feat: feed 더미 데이터 추가 및 pageCount 개수 조정
* design: 피드 리스트 디자인 조정
* chore: react-toastify 라이브러리 추가
* feat: caution icon 추가
* feat: 인터넷 연결 불안정 토스트 UI
* feat: 네트워크 에러 토스트 UI 수정
* style: @todo 주석 추가
  • Loading branch information
BangDori authored May 4, 2024
1 parent 7d93c31 commit cad930f
Show file tree
Hide file tree
Showing 36 changed files with 523 additions and 31 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
Expand All @@ -19,6 +19,7 @@
"react-dom": "^18.2.0",
"react-infinite-scroller": "^1.2.6",
"react-router-dom": "6.22.2",
"react-toastify": "^10.0.5",
"zustand": "^4.5.2"
},
"devDependencies": {
Expand Down
Binary file added public/assets/image/network_error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 90 additions & 12 deletions public/assets/sprites/common.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions src/app/mocks/consts/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Feed } from '@/shared/consts';
import { comments } from './comment';
import { likes } from './like';
import { users } from './user';
import { reports } from './report';

interface Feeds {
[feedId: number]: Feed;
Expand Down Expand Up @@ -235,3 +236,25 @@ export const feeds: Feeds = {
updatedAt: '2024-04-16 13:20:00',
},
};

for (let i = 10; i < 100; i++) {
reports[i] = false;
comments[i] = [];
likes[i] = { totalCount: i, isLiked: false };
feeds[i] = {
id: i,
user: users[1],
title: `Feed Title ${i}`,
content: `Feed Content ${i}`,
images: [],

likeCount: likes[i].totalCount,
commentCount: comments[i].length,

isLiked: likes[i].isLiked,
isBookmark: false,

createdAt: '2024-05-03 12:00:00',
updatedAt: '2024-05-03 12:00:00',
};
}
6 changes: 4 additions & 2 deletions src/app/mocks/handler/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ interface ReportForm {

export const feedHandlers = [
// 1️⃣ 피드 목록 조회
/**
* @todo pageCount를 쿼리 파라미터로 받도록 수정
*/
http.get('/feeds', ({ request }) => {
const url = new URL(request.url);
const page = url.searchParams.get('page') || 1;
Expand All @@ -35,9 +38,8 @@ export const feedHandlers = [
}

const formattedPage = Number(page);
const pageCount = 5; // 임시 5개 -> 추후 10개 변경 예정
const pageCount = 10;

// 임시 5개 -> 추후 10개 변경 예정
const feedsData = Object.values(feeds)
.slice((formattedPage - 1) * pageCount, formattedPage * pageCount)
.filter((feed) => !reports[feed.id]);
Expand Down
1 change: 1 addition & 0 deletions src/pages/feed-main/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FeedMainPage } from './ui/FeedMainPage';
2 changes: 1 addition & 1 deletion src/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { FeedMainPage } from './feed-main/ui/FeedMainPage';
export { FeedMainPage } from './feed-main';
2 changes: 1 addition & 1 deletion src/shared/axios/config/interceptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ export async function onError(error: AxiosError | Error) {
logApiErrorOnDev(error);
}

return error;
return Promise.reject(error);
}
1 change: 1 addition & 0 deletions src/shared/styles/_index.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@forward 'colors.scss';
@forward 'fonts.scss';
@forward 'box.scss';
@forward 'skeleton.scss';
5 changes: 5 additions & 0 deletions src/shared/styles/reset.scss
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,8 @@ button {
justify-content: center;
align-items: center;
}

body {
width: 100vw;
height: 100vh;
}
24 changes: 24 additions & 0 deletions src/shared/styles/skeleton.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@keyframes loading {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}

@mixin skeletonAnimation() {
animation-duration: 2s;
animation-iteration-count: infinite;
animation-name: loading;
animation-timing-function: linear;
background-image: linear-gradient(
to right,
#d8d8d8 0%,
#bdbdbd 20%,
#d8d8d8 40%,
#d8d8d8 100%
);

background-repeat: no-repeat;
}
5 changes: 3 additions & 2 deletions src/shared/ui/icon/consts/sprite.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export type IconName =
| 'back'
| 'notification'
| 'like'
| 'share'
| 'kebab-menu'
| 'writing'
| 'bookmark'
| 'pennyway-logo'
| 'like'
| 'chat'
| 'search';
| 'search'
| 'caution';
4 changes: 3 additions & 1 deletion src/shared/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { ActiveButton, BasicButton } from './button';
export { PageHeader } from './header';
export { ConfirmModal, ConfirmReportModal, BottomSheetModal } from './modal';
export { Profile } from './profile';
export { Profile, SkeletonProfile } from './profile';
export { Icon } from './icon';
export { NetworkError } from './network-error';
export { NetworkToastError } from './network-toast-error';
47 changes: 47 additions & 0 deletions src/shared/ui/network-error/NetworkError.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@use '@/shared/styles/_index.scss' as *;

.network-error-wrapper {
z-index: 999;

position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;

background-color: #fff;

.network-error {
display: flex;
flex-direction: column;
align-items: center;

padding-top: 121px;

.error-message {
display: flex;
flex-direction: column;
align-items: center;

gap: 8px;

margin: 27.5px 0 16px;

.error-title {
color: $gray7;
}

.error-description {
color: $gray4;
}
}

.refetch-btn {
@include responsive-box(107px, 36px);
border-radius: 6px;
background-color: $mint3;

color: #ffffff;
}
}
}
25 changes: 25 additions & 0 deletions src/shared/ui/network-error/NetworkError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import NetworkErrorImage from '/assets/image/network_error.png';
import './NetworkError.scss';

interface NetworkErrorProps {
refetch: () => void;
}

export const NetworkError: React.FC<NetworkErrorProps> = ({ refetch }) => {
return (
<div className='network-error-wrapper'>
<div className='network-error'>
<img src={NetworkErrorImage} alt='network-error' />
<div className='error-message'>
<h1 className='error-title h2semi'>인터넷에 연결되지 않았어요</h1>
<p className='error-description b1md'>
연결 확인 후 다시 시도해주세요
</p>
</div>
<button className='refetch-btn b1semi' onClick={refetch}>
재시도하기
</button>
</div>
</div>
);
};
1 change: 1 addition & 0 deletions src/shared/ui/network-error/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NetworkError } from './NetworkError';
75 changes: 75 additions & 0 deletions src/shared/ui/network-toast-error/NetworkToastError.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@use '@/shared/styles/_index.scss' as *;

@keyframes bounceInUp {
from,
60%,
75%,
90%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
from {
opacity: 0;
transform: translate3d(0, 100%, 0);
}
60% {
opacity: 1;
transform: translate3d(0, -20px, 0);
}
75% {
transform: translate3d(0, 10px, 0);
}
90% {
transform: translate3d(0, -5px, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}

.Toastify {
position: fixed;
bottom: 8px;
left: 0;
right: 0;

@include responsive-box(280px, 40px);
margin: 0 auto;

.network-error-toast {
animation: bounceInUp 0.75s cubic-bezier(0.68, -0.55, 0.27, 1.55) both;

width: 100%;
height: 100%;
border-radius: 6px;

display: flex;
align-items: center;

color: #ffffff;
background: #98a3b4e5;

.Toastify__toast--default {
color: #ffffff;

width: 100%;

.Toastify__toast-body {
display: flex;
align-items: center;

gap: 6px;

.Toastify__toast-icon {
@include responsive-box(20px, 20px);

margin-left: 12px;
}
}

.Toastify__close-button {
display: none;
}
}
}
}
19 changes: 19 additions & 0 deletions src/shared/ui/network-toast-error/NetworkToastError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ToastContainer } from 'react-toastify';

import './NetworkToastError.scss';
import { Icon } from '..';

export const NetworkToastError = () => {
return (
<ToastContainer
className='network-error-toast b1semi'
position='bottom-center'
autoClose={false}
icon={<Icon name='caution' width='20' height='20' />}
hideProgressBar={true}
rtl={false}
limit={1}
theme='colored'
/>
);
};
1 change: 1 addition & 0 deletions src/shared/ui/network-toast-error/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NetworkToastError } from './NetworkToastError';
2 changes: 0 additions & 2 deletions src/shared/ui/profile/Profile.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@use '@/shared/styles/_index.scss' as *;

.profile {
@include responsive-box(200px, 45px);
display: flex;
align-items: center;
border-radius: 8px;
Expand All @@ -15,7 +14,6 @@
}

.name-section {
@include responsive-box(155px, 32px);
margin-left: 8px;

display: flex;
Expand Down
27 changes: 27 additions & 0 deletions src/shared/ui/profile/SkeletonProfile.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@use '@/shared/styles/_index.scss' as *;

.skeleton-profile {
display: flex;
align-items: center;
border-radius: 8px;
border: none;
background-color: white;

.skeleton-profile-image {
@include skeletonAnimation();
@include responsive-box(32px, 32px);
border-radius: 50%;
overflow: hidden;

background-color: $gray2;
}

.skeleton-name-section {
@include skeletonAnimation();
@include responsive-box(56px, 14px);

margin: 0 0 8px 8px;
background-color: $gray2;
border-radius: 4px;
}
}
10 changes: 10 additions & 0 deletions src/shared/ui/profile/SkeletonProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import './SkeletonProfile.scss';

export const SkeletonProfile = () => {
return (
<div className='skeleton-profile'>
<div className='skeleton-profile-image' />
<div className='skeleton-name-section' />
</div>
);
};
1 change: 1 addition & 0 deletions src/shared/ui/profile/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { Profile } from './Profile';
export { SkeletonProfile } from './SkeletonProfile';
4 changes: 2 additions & 2 deletions src/widgets/feed-main-header/ui/FeedMainHeader.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
@use '@/shared/styles/_index.scss' as *;

#feed-main-header {
padding: 44px 3px 0 20px;
padding: 0 3px 0 20px;

display: flex;
justify-content: space-between;
align-items: center;
align-items: baseline;

.header-right {
display: flex;
Expand Down
Loading

0 comments on commit cad930f

Please sign in to comment.