Skip to content

Commit 8574b1e

Browse files
authored
v1.1.0
- Release v1.1.0
2 parents 5697585 + d40f314 commit 8574b1e

File tree

95 files changed

+1733
-490
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+1733
-490
lines changed

next.config.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,13 @@ const nextConfig = {
3131
},
3232
async rewrites() {
3333
return [
34-
{
35-
source: '/service-api/:url*',
36-
destination: `${baseURL}/api/:url*`,
37-
},
3834
{
3935
source: '/aladin-api',
4036
has: [{ type: 'query', key: 'QueryType', value: '(?<QueryType>.*)' }],
4137
destination: `${aladinURL}/api/ItemList.aspx?ttbkey=${ALADIN_API_KEY}&QueryType=:QueryType&MaxResults=10&start=1&SearchTarget=Book&output=JS&Version=20131101`,
4238
},
4339
];
4440
},
45-
async redirects() {
46-
return [
47-
{
48-
source: '/',
49-
destination: '/bookarchive',
50-
permanent: false,
51-
},
52-
];
53-
},
5441
images: {
5542
remotePatterns: [
5643
{
@@ -79,6 +66,9 @@ const nextConfig = {
7966
},
8067
],
8168
},
69+
experimental: {
70+
serverActions: true,
71+
},
8272
};
8373

8474
module.exports = nextConfig;

package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
"dev": "next dev -H local.dev.dadok.app",
77
"build": "next build",
88
"start": "next start -H local.dev.dadok.app",
9-
"dev-ssl": "node scripts/server.js local.dev.dadok.app",
10-
"start-ssl": "NODE_ENV=production node scripts/server.js local.dev.dadok.app",
9+
"dev-ssl": "node scripts/server.mjs local.dev.dadok.app",
10+
"start-ssl": "NODE_ENV=production node scripts/server.mjs local.dev.dadok.app",
1111
"lint": "next lint",
1212
"prepare": "husky install",
1313
"storybook": "storybook dev -p 6006",
1414
"build-storybook": "storybook build",
15-
"update-host": "node scripts/updateDevHost.js local.dev.dadok.app",
15+
"update-host": "node scripts/updateDevHost.mjs local.dev.dadok.app",
1616
"init-https": "sh scripts/init-mkcert.sh local.dev.dadok.app",
1717
"postinstall": "patch-package"
1818
},
@@ -26,12 +26,14 @@
2626
"@types/react-dom": "18.0.10",
2727
"axios": "^1.3.4",
2828
"colorthief": "^2.4.0",
29+
"jose": "^5.5.0",
2930
"next": "13.4.7",
3031
"react": "18.2.0",
3132
"react-dom": "18.2.0",
3233
"react-error-boundary": "^3.1.4",
3334
"react-hook-form": "^7.43.2",
34-
"react-intersection-observer": "^9.4.3"
35+
"react-intersection-observer": "^9.4.3",
36+
"sharp": "^0.32.6"
3537
},
3638
"devDependencies": {
3739
"@babel/core": "^7.22.8",
@@ -44,6 +46,7 @@
4446
"@storybook/react": "^7.0.26",
4547
"@storybook/testing-library": "^0.0.14-next.2",
4648
"@svgr/webpack": "^6.5.1",
49+
"@types/gtag.js": "^0.0.20",
4750
"@typescript-eslint/eslint-plugin": "^5.52.0",
4851
"@typescript-eslint/parser": "^5.61.0",
4952
"autoprefixer": "^10.4.14",
5.21 KB
Loading

public/pwaServiceWorker.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
self.addEventListener('install', () => {
2-
console.log('Service worker installed');
2+
// 서비스 워커 설치 됐을 때 동작
33
});
44

55
self.addEventListener('activate', () => {
6-
console.log('Service worker activated');
6+
// 서비스 워커 작동할 때 동작
77
});

scripts/server.js renamed to scripts/server.mjs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
/* eslint-disable @typescript-eslint/no-var-requires */
2-
const fs = require('fs');
3-
const https = require('https');
4-
const path = require('path');
5-
const { parse } = require('url');
6-
const { execSync } = require('child_process');
7-
const next = require('next');
1+
import fs from 'fs';
2+
import https from 'https';
3+
import path from 'path';
4+
import { parse } from 'url';
5+
import { execSync } from 'child_process';
6+
import next from 'next';
87

98
const dev = process.env.NODE_ENV !== 'production';
109
const hostname = process.argv[2];

scripts/updateDevHost.js renamed to scripts/updateDevHost.mjs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
/* eslint-disable @typescript-eslint/no-var-requires */
2-
const fs = require('fs');
3-
const { EOL } = require('os');
1+
import * as fs from 'fs';
2+
import { EOL } from 'os';
43

54
const LOCALHOST = '127.0.0.1';
65
const IS_WINDOWS = process.platform === 'win32';

src/apis/core/axios.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import axios, { CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
22

33
import { AuthRefreshIgnoredError } from '@/types/customError';
4-
import { ACCESS_TOKEN_STORAGE_KEY, SERVICE_ERROR_MESSAGE } from '@/constants';
4+
import { SERVICE_ERROR_MESSAGE, SESSION_COOKIES_KEYS } from '@/constants';
55
import {
66
isAuthFailedError,
77
isAuthRefreshError,
88
isAxiosErrorWithCustomCode,
99
} from '@/utils/helpers';
10-
import webStorage from '@/utils/storage';
10+
import { deleteAuthSession, setAuthSession } from '@/server/session';
11+
import { deleteCookie } from '@/utils/cookie';
1112

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

2626
const requestHandler = (config: InternalAxiosRequestConfig) => {
2727
const { data, method } = config;
28-
const accessToken = storage.get();
29-
30-
if (accessToken) {
31-
setAxiosAuthHeader(config, accessToken);
32-
}
3328

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

5348
if (isAuthFailedError(code)) {
54-
removeToken();
49+
await removeToken();
5550
}
5651
} else {
5752
console.error('예상하지 못한 오류가 발생했어요.\n', error);
@@ -63,12 +58,10 @@ const responseHandler = async (error: unknown) => {
6358
const silentRefresh = async (originRequest: InternalAxiosRequestConfig) => {
6459
try {
6560
const newToken = await updateToken();
66-
storage.set(newToken);
67-
setAxiosAuthHeader(originRequest, newToken);
68-
61+
await setAuthSession(newToken);
6962
return await publicApi(originRequest);
7063
} catch (error) {
71-
removeToken();
64+
await removeToken();
7265
return Promise.reject(error);
7366
}
7467
};
@@ -93,15 +86,9 @@ const updateToken = () =>
9386
.finally(() => (isTokenRefreshing = false));
9487
});
9588

96-
const removeToken = () => {
97-
storage.remove();
98-
};
99-
100-
const setAxiosAuthHeader = (
101-
config: InternalAxiosRequestConfig,
102-
token: string
103-
) => {
104-
config.headers['Authorization'] = `Bearers ${token}`;
89+
const removeToken = async () => {
90+
SESSION_COOKIES_KEYS.map(key => deleteCookie(key));
91+
await deleteAuthSession();
10592
};
10693

10794
publicApi.interceptors.request.use(requestHandler);

src/app/api/imageOptimize/route.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import sharp from 'sharp';
3+
4+
import fs from 'fs';
5+
import path from 'path';
6+
7+
export async function GET(request: NextRequest) {
8+
const { searchParams } = new URL(request.url);
9+
10+
const src = searchParams.get('src');
11+
// const width = searchParams.get('width');
12+
// const height = searchParams.get('height');
13+
14+
if (!src || typeof src !== 'string') {
15+
return new NextResponse('Missing or invalid "src" query parameter', {
16+
status: 400,
17+
});
18+
}
19+
20+
// const widthInt = width ? parseInt(width as string, 10) : null;
21+
// const heightInt = height ? parseInt(height as string, 10) : null;
22+
const isGif = src.endsWith('.gif');
23+
24+
const getImageBuffer = async () => {
25+
if (src.startsWith('http://') || src.startsWith('https://')) {
26+
// 외부 이미지 URL 처리
27+
const response = await fetch(src, {
28+
next: { revalidate: 60 * 60 * 24 },
29+
headers: {
30+
responseType: 'arraybuffer',
31+
},
32+
});
33+
const imageBuffer = await response.arrayBuffer();
34+
35+
return imageBuffer;
36+
} else {
37+
// 로컬 이미지 경로 처리
38+
const imagePath = path.join('./public', src);
39+
const imageBuffer = fs.readFileSync(imagePath);
40+
41+
return imageBuffer;
42+
}
43+
};
44+
45+
try {
46+
const imageBuffer = await getImageBuffer();
47+
48+
// 이미지 최적화 작업
49+
const image = isGif
50+
? sharp(imageBuffer, { animated: true }).gif()
51+
: sharp(imageBuffer).webp();
52+
53+
// // 이미지 리사이징
54+
// if (widthInt || heightInt) {
55+
// image.resize(widthInt, heightInt);
56+
// }
57+
58+
const optimizedImageBuffer = await image.toBuffer();
59+
60+
// 응답 헤더 설정
61+
const contentTypeHeader = isGif
62+
? {
63+
'Content-Type': 'image/gif',
64+
}
65+
: {
66+
'Content-Type': 'image/webp',
67+
};
68+
69+
// 최적화된 이미지 전송
70+
return new NextResponse(optimizedImageBuffer, {
71+
status: 200,
72+
headers: contentTypeHeader,
73+
});
74+
} catch (error) {
75+
console.error('Error optimizing image:', error);
76+
return new NextResponse('Error optimizing image', {
77+
status: 500,
78+
});
79+
}
80+
}

src/app/book/[bookId]/page.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ import { SERVICE_ERROR_MESSAGE } from '@/constants';
1616
import Skeleton from '@/components/common/Skeleton';
1717
import SSRSafeSuspense from '@/components/common/SSRSafeSuspense';
1818
import TopNavigation from '@/components/common/TopNavigation';
19-
import BottomActionButton from '@/components/common/BottomActionButton';
19+
import StickyFooter from '@/components/common/StickyFooter';
2020
import LoginBottomActionButton from '@/components/common/LoginBottomActionButton';
2121
import CommentDrawer from '@/components/comment/CommentDrawer';
2222
import BackButton from '@/components/common/BackButton';
2323
import BookInfo, { BookInfoSkeleton } from '@/components/book/detail/BookInfo';
2424
import BookCommentList from '@/components/comment/BookCommentList';
25+
import Button from '@/components/common/Button';
2526

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

126127
return (
127128
<>
128-
<BottomActionButton onClick={onDrawerOpen}>
129-
코멘트 작성하기
130-
</BottomActionButton>
129+
<StickyFooter>
130+
<Button size="full" onClick={onDrawerOpen}>
131+
코멘트 작성하기
132+
</Button>
133+
</StickyFooter>
131134
<CommentDrawer
132135
isOpen={isDrawerOpen}
133136
onClose={onDrawerClose}

src/app/book/search/page.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import bookAPI from '@/apis/book';
1414
import SSRSafeSuspense from '@/components/common/SSRSafeSuspense';
1515
import useDebounceValue from '@/hooks/useDebounce';
1616
import useQueryParams from '@/hooks/useQueryParams';
17+
import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';
1718
import { checkAuthentication } from '@/utils/helpers';
1819

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

50+
const { isScrollAtTop } = useIsScrollAtTop();
51+
4952
/* debounce된 keyword값에 따라 queryParameter를 수정하는 useEffect */
5053
useEffect(() => {
5154
const queryValue = getQueryParam(KEYWORD);
@@ -57,24 +60,29 @@ const BookSearchPage = () => {
5760
}
5861
}, [debouncedKeyword, getQueryParam, setQueryParams, removeQueryParam]);
5962

60-
/* TopHeader가 사라졌을 때 input의 위치 top: 5.8rem */
61-
const inputPositionClasses = watchedKeyword && 'sticky top-[5.8rem]';
63+
/* TopHeader가 사라졌을 때 input의 위치 top: topSafeArea + 6.15rem */
64+
const inputPositionClasses =
65+
watchedKeyword && 'sticky top-[calc(env(safe-area-inset-top)+6.15rem)]';
66+
67+
/* 검색어가 입력되었을 때 각 컨테이너의 애니메이션 class */
68+
const discoverPageAnimationClasses = `transition duration-500 ${
69+
watchedKeyword ? '-translate-y-[6.05rem]' : 'translate-y-0'
70+
}`;
71+
const headingOpacityClasses = `${
72+
watchedKeyword ? 'opacity-0' : 'opacity-100'
73+
}`;
6274

6375
return (
6476
<>
65-
<div
66-
className={`transition duration-500 ${
67-
watchedKeyword
68-
? '-translate-y-[5.8rem] opacity-0'
69-
: 'translate-y-0 opacity-100'
70-
}`}
71-
>
72-
<TopHeader text={'Discover'} />
73-
</div>
77+
<TopHeader blur={!isScrollAtTop} className={discoverPageAnimationClasses}>
78+
<h1
79+
className={`text-main-900 font-heading-bold ${headingOpacityClasses}`}
80+
>
81+
Discover
82+
</h1>
83+
</TopHeader>
7484
<article
75-
className={`flex w-full flex-col gap-[3rem] transition duration-500 ${
76-
watchedKeyword ? '-translate-y-[5.8rem]' : 'translate-y-0'
77-
}`}
85+
className={`flex w-full flex-col gap-[3rem] ${discoverPageAnimationClasses}`}
7886
>
7987
<Input
8088
type="search"

0 commit comments

Comments
 (0)