Skip to content

Commit

Permalink
v1.1.0
Browse files Browse the repository at this point in the history
- Release v1.1.0
  • Loading branch information
hanyugeon authored Aug 20, 2024
2 parents 5697585 + d40f314 commit 8574b1e
Show file tree
Hide file tree
Showing 95 changed files with 1,733 additions and 490 deletions.
16 changes: 3 additions & 13 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,13 @@ const nextConfig = {
},
async rewrites() {
return [
{
source: '/service-api/:url*',
destination: `${baseURL}/api/:url*`,
},
{
source: '/aladin-api',
has: [{ type: 'query', key: 'QueryType', value: '(?<QueryType>.*)' }],
destination: `${aladinURL}/api/ItemList.aspx?ttbkey=${ALADIN_API_KEY}&QueryType=:QueryType&MaxResults=10&start=1&SearchTarget=Book&output=JS&Version=20131101`,
},
];
},
async redirects() {
return [
{
source: '/',
destination: '/bookarchive',
permanent: false,
},
];
},
images: {
remotePatterns: [
{
Expand Down Expand Up @@ -79,6 +66,9 @@ const nextConfig = {
},
],
},
experimental: {
serverActions: true,
},
};

module.exports = nextConfig;
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
"dev": "next dev -H local.dev.dadok.app",
"build": "next build",
"start": "next start -H local.dev.dadok.app",
"dev-ssl": "node scripts/server.js local.dev.dadok.app",
"start-ssl": "NODE_ENV=production node scripts/server.js local.dev.dadok.app",
"dev-ssl": "node scripts/server.mjs local.dev.dadok.app",
"start-ssl": "NODE_ENV=production node scripts/server.mjs local.dev.dadok.app",
"lint": "next lint",
"prepare": "husky install",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"update-host": "node scripts/updateDevHost.js local.dev.dadok.app",
"update-host": "node scripts/updateDevHost.mjs local.dev.dadok.app",
"init-https": "sh scripts/init-mkcert.sh local.dev.dadok.app",
"postinstall": "patch-package"
},
Expand All @@ -26,12 +26,14 @@
"@types/react-dom": "18.0.10",
"axios": "^1.3.4",
"colorthief": "^2.4.0",
"jose": "^5.5.0",
"next": "13.4.7",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-error-boundary": "^3.1.4",
"react-hook-form": "^7.43.2",
"react-intersection-observer": "^9.4.3"
"react-intersection-observer": "^9.4.3",
"sharp": "^0.32.6"
},
"devDependencies": {
"@babel/core": "^7.22.8",
Expand All @@ -44,6 +46,7 @@
"@storybook/react": "^7.0.26",
"@storybook/testing-library": "^0.0.14-next.2",
"@svgr/webpack": "^6.5.1",
"@types/gtag.js": "^0.0.20",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.61.0",
"autoprefixer": "^10.4.14",
Expand Down
Binary file added public/images/book-illustration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions public/pwaServiceWorker.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
self.addEventListener('install', () => {
console.log('Service worker installed');
// 서비스 워커 설치 됐을 때 동작
});

self.addEventListener('activate', () => {
console.log('Service worker activated');
// 서비스 워커 작동할 때 동작
});
13 changes: 6 additions & 7 deletions scripts/server.js → scripts/server.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const https = require('https');
const path = require('path');
const { parse } = require('url');
const { execSync } = require('child_process');
const next = require('next');
import fs from 'fs';
import https from 'https';
import path from 'path';
import { parse } from 'url';
import { execSync } from 'child_process';
import next from 'next';

const dev = process.env.NODE_ENV !== 'production';
const hostname = process.argv[2];
Expand Down
5 changes: 2 additions & 3 deletions scripts/updateDevHost.js → scripts/updateDevHost.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const { EOL } = require('os');
import * as fs from 'fs';
import { EOL } from 'os';

const LOCALHOST = '127.0.0.1';
const IS_WINDOWS = process.platform === 'win32';
Expand Down
31 changes: 9 additions & 22 deletions src/apis/core/axios.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import axios, { CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';

import { AuthRefreshIgnoredError } from '@/types/customError';
import { ACCESS_TOKEN_STORAGE_KEY, SERVICE_ERROR_MESSAGE } from '@/constants';
import { SERVICE_ERROR_MESSAGE, SESSION_COOKIES_KEYS } from '@/constants';
import {
isAuthFailedError,
isAuthRefreshError,
isAxiosErrorWithCustomCode,
} from '@/utils/helpers';
import webStorage from '@/utils/storage';
import { deleteAuthSession, setAuthSession } from '@/server/session';
import { deleteCookie } from '@/utils/cookie';

const storage = webStorage(ACCESS_TOKEN_STORAGE_KEY);
const options: CreateAxiosDefaults = {
baseURL: process.env.NEXT_HOST,
headers: {
Expand All @@ -25,11 +25,6 @@ export const publicApi = axios.create({

const requestHandler = (config: InternalAxiosRequestConfig) => {
const { data, method } = config;
const accessToken = storage.get();

if (accessToken) {
setAxiosAuthHeader(config, accessToken);
}

if (!data && (method === 'get' || method === 'delete')) {
config.data = {};
Expand All @@ -51,7 +46,7 @@ const responseHandler = async (error: unknown) => {
}

if (isAuthFailedError(code)) {
removeToken();
await removeToken();
}
} else {
console.error('예상하지 못한 오류가 발생했어요.\n', error);
Expand All @@ -63,12 +58,10 @@ const responseHandler = async (error: unknown) => {
const silentRefresh = async (originRequest: InternalAxiosRequestConfig) => {
try {
const newToken = await updateToken();
storage.set(newToken);
setAxiosAuthHeader(originRequest, newToken);

await setAuthSession(newToken);
return await publicApi(originRequest);
} catch (error) {
removeToken();
await removeToken();
return Promise.reject(error);
}
};
Expand All @@ -93,15 +86,9 @@ const updateToken = () =>
.finally(() => (isTokenRefreshing = false));
});

const removeToken = () => {
storage.remove();
};

const setAxiosAuthHeader = (
config: InternalAxiosRequestConfig,
token: string
) => {
config.headers['Authorization'] = `Bearers ${token}`;
const removeToken = async () => {
SESSION_COOKIES_KEYS.map(key => deleteCookie(key));
await deleteAuthSession();
};

publicApi.interceptors.request.use(requestHandler);
Expand Down
80 changes: 80 additions & 0 deletions src/app/api/imageOptimize/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { NextRequest, NextResponse } from 'next/server';
import sharp from 'sharp';

import fs from 'fs';
import path from 'path';

export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);

const src = searchParams.get('src');
// const width = searchParams.get('width');
// const height = searchParams.get('height');

if (!src || typeof src !== 'string') {
return new NextResponse('Missing or invalid "src" query parameter', {
status: 400,
});
}

// const widthInt = width ? parseInt(width as string, 10) : null;
// const heightInt = height ? parseInt(height as string, 10) : null;
const isGif = src.endsWith('.gif');

const getImageBuffer = async () => {
if (src.startsWith('http://') || src.startsWith('https://')) {
// 외부 이미지 URL 처리
const response = await fetch(src, {
next: { revalidate: 60 * 60 * 24 },
headers: {
responseType: 'arraybuffer',
},
});
const imageBuffer = await response.arrayBuffer();

return imageBuffer;
} else {
// 로컬 이미지 경로 처리
const imagePath = path.join('./public', src);
const imageBuffer = fs.readFileSync(imagePath);

return imageBuffer;
}
};

try {
const imageBuffer = await getImageBuffer();

// 이미지 최적화 작업
const image = isGif
? sharp(imageBuffer, { animated: true }).gif()
: sharp(imageBuffer).webp();

// // 이미지 리사이징
// if (widthInt || heightInt) {
// image.resize(widthInt, heightInt);
// }

const optimizedImageBuffer = await image.toBuffer();

// 응답 헤더 설정
const contentTypeHeader = isGif
? {
'Content-Type': 'image/gif',
}
: {
'Content-Type': 'image/webp',
};

// 최적화된 이미지 전송
return new NextResponse(optimizedImageBuffer, {
status: 200,
headers: contentTypeHeader,
});
} catch (error) {
console.error('Error optimizing image:', error);
return new NextResponse('Error optimizing image', {
status: 500,
});
}
}
11 changes: 7 additions & 4 deletions src/app/book/[bookId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ import { SERVICE_ERROR_MESSAGE } from '@/constants';
import Skeleton from '@/components/common/Skeleton';
import SSRSafeSuspense from '@/components/common/SSRSafeSuspense';
import TopNavigation from '@/components/common/TopNavigation';
import BottomActionButton from '@/components/common/BottomActionButton';
import StickyFooter from '@/components/common/StickyFooter';
import LoginBottomActionButton from '@/components/common/LoginBottomActionButton';
import CommentDrawer from '@/components/comment/CommentDrawer';
import BackButton from '@/components/common/BackButton';
import BookInfo, { BookInfoSkeleton } from '@/components/book/detail/BookInfo';
import BookCommentList from '@/components/comment/BookCommentList';
import Button from '@/components/common/Button';

const BookDetailPage = ({
params: { bookId },
Expand Down Expand Up @@ -125,9 +126,11 @@ const AddBookCommentButton = ({ bookId }: { bookId: APIBook['bookId'] }) => {

return (
<>
<BottomActionButton onClick={onDrawerOpen}>
코멘트 작성하기
</BottomActionButton>
<StickyFooter>
<Button size="full" onClick={onDrawerOpen}>
코멘트 작성하기
</Button>
</StickyFooter>
<CommentDrawer
isOpen={isDrawerOpen}
onClose={onDrawerClose}
Expand Down
36 changes: 22 additions & 14 deletions src/app/book/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import bookAPI from '@/apis/book';
import SSRSafeSuspense from '@/components/common/SSRSafeSuspense';
import useDebounceValue from '@/hooks/useDebounce';
import useQueryParams from '@/hooks/useQueryParams';
import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';
import { checkAuthentication } from '@/utils/helpers';

import Loading from '@/components/common/Loading';
Expand Down Expand Up @@ -46,6 +47,8 @@ const BookSearchPage = () => {
const watchedKeyword = watch('searchValue');
const debouncedKeyword = useDebounceValue(watchedKeyword, 1000);

const { isScrollAtTop } = useIsScrollAtTop();

/* debounce된 keyword값에 따라 queryParameter를 수정하는 useEffect */
useEffect(() => {
const queryValue = getQueryParam(KEYWORD);
Expand All @@ -57,24 +60,29 @@ const BookSearchPage = () => {
}
}, [debouncedKeyword, getQueryParam, setQueryParams, removeQueryParam]);

/* TopHeader가 사라졌을 때 input의 위치 top: 5.8rem */
const inputPositionClasses = watchedKeyword && 'sticky top-[5.8rem]';
/* TopHeader가 사라졌을 때 input의 위치 top: topSafeArea + 6.15rem */
const inputPositionClasses =
watchedKeyword && 'sticky top-[calc(env(safe-area-inset-top)+6.15rem)]';

/* 검색어가 입력되었을 때 각 컨테이너의 애니메이션 class */
const discoverPageAnimationClasses = `transition duration-500 ${
watchedKeyword ? '-translate-y-[6.05rem]' : 'translate-y-0'
}`;
const headingOpacityClasses = `${
watchedKeyword ? 'opacity-0' : 'opacity-100'
}`;

return (
<>
<div
className={`transition duration-500 ${
watchedKeyword
? '-translate-y-[5.8rem] opacity-0'
: 'translate-y-0 opacity-100'
}`}
>
<TopHeader text={'Discover'} />
</div>
<TopHeader blur={!isScrollAtTop} className={discoverPageAnimationClasses}>
<h1
className={`text-main-900 font-heading-bold ${headingOpacityClasses}`}
>
Discover
</h1>
</TopHeader>
<article
className={`flex w-full flex-col gap-[3rem] transition duration-500 ${
watchedKeyword ? '-translate-y-[5.8rem]' : 'translate-y-0'
}`}
className={`flex w-full flex-col gap-[3rem] ${discoverPageAnimationClasses}`}
>
<Input
type="search"
Expand Down
2 changes: 1 addition & 1 deletion src/app/book/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const sitemap = ['search', ...booksId];

return sitemap.map(value => ({
url: `${process.env.NEXT_PUBLIC_HOST}/book/${value}`,
url: `${process.env.NEXT_PUBLIC_PRODUCTION_URL}/book/${value}`,
lastModified: new Date(),
}));
}
12 changes: 10 additions & 2 deletions src/app/bookarchive/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ import BookArchiveForAuth from '@/components/bookArchive/BookArchiveForAuth';
import BookArchiveForUnAuth from '@/components/bookArchive/BookArchiveForUnAuth';
import TopHeader from '@/components/common/TopHeader';

import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';

export default function BookArchivePage() {
const { isScrollAtTop } = useIsScrollAtTop();

return (
<div className="flex w-full flex-col gap-[1rem] pb-[2rem]">
<TopHeader text="BookArchive" />
<TopHeader blur={!isScrollAtTop}>
<h1 className="text-main-900 font-heading-bold">BookArchive</h1>
</TopHeader>
{/* TODO: 스켈레톤 컴포넌트로 교체 */}
<SSRSafeSuspense fallback={null}>
<Contents />
Expand All @@ -27,7 +33,9 @@ const Contents = () => {
enabled: isAuthenticated,
});

return isAuthenticated ? (
const hasProfile = isAuthenticated && userData?.job?.jobGroupName;

return hasProfile ? (
<BookArchiveForAuth userJobGroup={userData.job.jobGroupName} />
) : (
<BookArchiveForUnAuth />
Expand Down
Loading

0 comments on commit 8574b1e

Please sign in to comment.