diff --git a/index.html b/index.html index 5085c2f..e31b9b1 100644 --- a/index.html +++ b/index.html @@ -73,9 +73,12 @@ - + - + +
diff --git a/package-lock.json b/package-lock.json index 6cca1e8..d0966e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,9 +60,11 @@ "kakao.maps.d.ts": "^0.1.40", "postcss": "^8.5.3", "prettier": "^3.5.3", + "rollup-plugin-visualizer": "^6.0.4", "sitemap": "^8.0.0", "storybook": "^8.6.12", "tailwindcss": "^3.4.1", + "terser": "^5.44.0", "ts-jest": "^29.3.2", "ts-node": "^10.9.2", "typescript": "~5.7.2", @@ -1915,6 +1917,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -4188,9 +4201,9 @@ "license": "BSD-3-Clause" }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, "license": "MIT", "bin": { @@ -11329,6 +11342,47 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-visualizer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.4.tgz", + "integrity": "sha512-q8Q7J/6YofkmaGW1sH/fPRAz37x/+pd7VBuaUU7lwvOS/YikuiiEU9jeb9PH8XHiq50XFrUsBbOxeAMYQ7KZkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "open": "^8.0.0", + "picomatch": "^4.0.2", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "rolldown": "1.x || ^1.0.0-beta", + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/rrdom": { "version": "2.0.0-alpha.18", "resolved": "https://registry.npmjs.org/rrdom/-/rrdom-2.0.0-alpha.18.tgz", @@ -12359,6 +12413,43 @@ "node": ">=14.0.0" } }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", diff --git a/package.json b/package.json index 8313d30..6dcc586 100644 --- a/package.json +++ b/package.json @@ -67,9 +67,11 @@ "kakao.maps.d.ts": "^0.1.40", "postcss": "^8.5.3", "prettier": "^3.5.3", + "rollup-plugin-visualizer": "^6.0.4", "sitemap": "^8.0.0", "storybook": "^8.6.12", "tailwindcss": "^3.4.1", + "terser": "^5.44.0", "ts-jest": "^29.3.2", "ts-node": "^10.9.2", "typescript": "~5.7.2", diff --git a/src/components/Community/CategorySelector.tsx b/src/components/Community/CategorySelector.tsx index 9cc4094..37cc23e 100644 --- a/src/components/Community/CategorySelector.tsx +++ b/src/components/Community/CategorySelector.tsx @@ -36,7 +36,7 @@ //
// @@ -112,7 +112,7 @@ export default function CategorySelector({ setIsOpen(true); setTempValue(value); }} - className="w-full border-[1px] border-[#C3C3C3] rounded-[12px] px-4 py-3 text-left text-body-md-description font-regular text-[#919191]" + className="w-full border-[1px] border-sub-gray rounded-[12px] px-4 py-3 text-left text-body-md-description font-regular text-main-gray" > {selectedLabel || '게시글의 카테고리를 선택해주세요'} @@ -138,10 +138,10 @@ export default function CategorySelector({ className="w-full flex items-center gap-5 px-2 py-2 text-left" >
{tempValue === categoryValue && ( -
+
)}
diff --git a/src/components/Community/CommentInput.tsx b/src/components/Community/CommentInput.tsx index 387ee64..0cf6e3e 100644 --- a/src/components/Community/CommentInput.tsx +++ b/src/components/Community/CommentInput.tsx @@ -69,7 +69,7 @@ const CommentInput: React.FC = ({ }; return ( -
+
); diff --git a/src/components/Mypage/MenuListBtn.tsx b/src/components/Mypage/MenuListBtn.tsx index 18df40e..e2c0172 100644 --- a/src/components/Mypage/MenuListBtn.tsx +++ b/src/components/Mypage/MenuListBtn.tsx @@ -14,7 +14,7 @@ const MenuListBtn = ({ const style = variant === 'default' ? 'w-full flex items-center px-[20px] py-[16px] self-stretch rounded-[20px] bg-[#F3F5ED]' - : 'w-full flex items-center px-[20px] py-[8px] self-stretch rounded-[16px] bg-[#F4F6F8]'; + : 'w-full flex items-center px-[20px] py-[8px] self-stretch rounded-[16px] bg-bg-gray'; const textStyle = variant === 'default' ? 'text-[16px] font-semibold leading-[20px]' : 'text-[14px] font-normal leading-[24px]'; @@ -24,7 +24,7 @@ const MenuListBtn = ({ onClick={onClick} className={`justify-between ${style}`} > - {label} + {label} ); diff --git a/src/components/Mypage/MyCommentPostList.tsx b/src/components/Mypage/MyCommentPostList.tsx index 80fbfa8..3316502 100644 --- a/src/components/Mypage/MyCommentPostList.tsx +++ b/src/components/Mypage/MyCommentPostList.tsx @@ -44,10 +44,10 @@ const MyCommentPostList: React.FC = ({ {post.myCommentList.map((comment: any, cidx: number) => (
reply-comment - + {comment.content}
@@ -57,7 +57,7 @@ const MyCommentPostList: React.FC = ({
))} {commentPosts.length === 0 && !loading && ( -
+
{posts.length === 0 && !loading ? ( -
+
엠티뷰 if (products.length === 0 && !loading) { return ( -
+
navigate(`/convenience/post/${product.id}`)} - className="flex justify-between items-center px-[20px] py-[8px] border-b border-[#E6E6E6] cursor-pointer" + className="flex justify-between items-center px-[20px] py-[8px] border-b border-disabled cursor-pointer" style={ idx === products.length - 1 ? { borderBottom: 'none' } @@ -66,7 +66,7 @@ export default function ProductListSection({ {product.isAvailable ? '결제가능' : '결제불가'} diff --git a/src/components/Mypage/MyLikePost.tsx b/src/components/Mypage/MyLikePost.tsx index a723b64..b55d297 100644 --- a/src/components/Mypage/MyLikePost.tsx +++ b/src/components/Mypage/MyLikePost.tsx @@ -28,7 +28,7 @@ const MyLikePost: React.FC = ({ like, noBorder }) => {
navigate(`/community/post/${like.communityPostId}`)} > @@ -38,7 +38,7 @@ const MyLikePost: React.FC = ({ like, noBorder }) => { {like.titlePreview} - + {like.contentPreview}
@@ -61,7 +61,7 @@ const MyLikePost: React.FC = ({ like, noBorder }) => {
-
+
좋아요 {like.likeCount} · {like.createdAt} @@ -71,7 +71,7 @@ const MyLikePost: React.FC = ({ like, noBorder }) => {
- + {like.commentCount}
diff --git a/src/components/Mypage/MyReviewItem.tsx b/src/components/Mypage/MyReviewItem.tsx index 993f5e8..11c5ab7 100644 --- a/src/components/Mypage/MyReviewItem.tsx +++ b/src/components/Mypage/MyReviewItem.tsx @@ -67,7 +67,7 @@ const MyReviewItem: React.FC = ({ const openDeleteModal = () => setOpenModal(true); return ( -
+
{/* 가맹점 이름 + 작성 날짜 + 삭제 버튼 */}
@@ -79,14 +79,14 @@ const MyReviewItem: React.FC = ({ {storeName} - + {date}
@@ -125,7 +125,7 @@ const MyReviewItem: React.FC = ({ {(content || imageUrl) && (

{content || ''}

diff --git a/src/components/Mypage/ProfileSection.tsx b/src/components/Mypage/ProfileSection.tsx index 90966aa..9fc2c49 100644 --- a/src/components/Mypage/ProfileSection.tsx +++ b/src/components/Mypage/ProfileSection.tsx @@ -42,7 +42,7 @@ const ProfileSection = ({ font-bold leading-normal tracking-[0.016px] - text-[#616161] + text-text-gray underline underline-offset-2 decoration-solid diff --git a/src/components/Mypage/TabBar.tsx b/src/components/Mypage/TabBar.tsx index 26e1b27..e51b600 100644 --- a/src/components/Mypage/TabBar.tsx +++ b/src/components/Mypage/TabBar.tsx @@ -14,7 +14,7 @@ const TabBar: React.FC = ({ value, onChange, tabs }) => { key={tab} className={` flex-1 pb-[8px] text-center font-pretendard font-semibold text-[16px] tracking-[0.016px] transition-colors - ${value === tab ? 'text-black' : 'text-[#919191]'} + ${value === tab ? 'text-black' : 'text-main-gray'} `} onClick={() => onChange(tab)} > @@ -23,7 +23,7 @@ const TabBar: React.FC = ({ value, onChange, tabs }) => { ))} {/* 바닥줄 (회색) */} -
+
{/* 하이라이트 바 */}
= ({ weekly }) => { if (isAllDaysClosed) return ( -

+

영업시간 정보 없음

); @@ -91,12 +91,12 @@ const BusinessHours: React.FC = ({ weekly }) => { return (
{status} @@ -127,7 +127,7 @@ const BusinessHours: React.FC = ({ weekly }) => { return (
{day} {Array.isArray(hours) diff --git a/src/components/StoreDetail/ReportReviewButton.tsx b/src/components/StoreDetail/ReportReviewButton.tsx index bdb7cff..bd7b2c4 100644 --- a/src/components/StoreDetail/ReportReviewButton.tsx +++ b/src/components/StoreDetail/ReportReviewButton.tsx @@ -107,7 +107,7 @@ const ReportReviewButton: React.FC = ({ <> diff --git a/src/components/StoreDetail/ReviewItem.tsx b/src/components/StoreDetail/ReviewItem.tsx index 034a3a8..f843357 100644 --- a/src/components/StoreDetail/ReviewItem.tsx +++ b/src/components/StoreDetail/ReviewItem.tsx @@ -7,6 +7,7 @@ import DeleteModal from '../common/DeleteModal'; import MainTag from '@/components/StoreReview/MainTag'; import axios from '@/api/axiosInstance'; import type { StoreReview } from '@/types/store'; +import OptimizedImage from '../common/OptimizedImage'; interface ReviewItemProps { userName: string; @@ -99,14 +100,14 @@ const ReviewItem: React.FC = ({ const closeDeleteModal = () => setShowDeleteModal(false); return ( -
+
- +
{userName} - + {date}
@@ -114,7 +115,7 @@ const ReviewItem: React.FC = ({ {isOwner ? ( @@ -156,11 +157,11 @@ const ReviewItem: React.FC = ({
-

+

{content}

{imageUrl && ( - 리뷰 이미지 = ({
{/* 카테고리, 이름, 태그 */}
-

{category}

+

{category}

{name} @@ -91,7 +91,7 @@ const StoreDetailInfo: React.FC = ({

{/* 주소 */} -

{address}

+

{address}

{/* 영업시간 + 찜 아이콘 */}
@@ -113,7 +113,7 @@ const StoreDetailInfo: React.FC = ({ }} /> )} -

+

업데이트 {dayjs(updatedDate).format('YYYY.MM.DD')}

@@ -124,7 +124,7 @@ const StoreDetailInfo: React.FC = ({ {favoriteCount} diff --git a/src/components/StoreDetail/StoreDetailMap.tsx b/src/components/StoreDetail/StoreDetailMap.tsx index c3a9ae8..977503f 100644 --- a/src/components/StoreDetail/StoreDetailMap.tsx +++ b/src/components/StoreDetail/StoreDetailMap.tsx @@ -288,13 +288,13 @@ const StoreDetailMap: React.FC = ({ {/* 메뉴 보러가기 */} @@ -304,7 +304,7 @@ const StoreDetailMap: React.FC = ({ {!IS_MOBILE && ( @@ -321,7 +321,7 @@ const StoreDetailMap: React.FC = ({ {IS_ANDROID && ( @@ -339,11 +339,11 @@ const StoreDetailMap: React.FC = ({ - + 길 찾기 @@ -352,7 +352,7 @@ const StoreDetailMap: React.FC = ({ {IS_IOS && !iosDeepLinks && ( diff --git a/src/components/StoreDetail/StoreDetailReview.tsx b/src/components/StoreDetail/StoreDetailReview.tsx index e3f371c..920bb49 100644 --- a/src/components/StoreDetail/StoreDetailReview.tsx +++ b/src/components/StoreDetail/StoreDetailReview.tsx @@ -7,6 +7,7 @@ import { useNavigate } from 'react-router-dom'; import React, { useEffect, useState } from 'react'; import axios from '@/api/axiosInstance'; import LoginModal from '@/components/common/LoginRequiredBottomSheet'; +import OptimizedImage from '../common/OptimizedImage'; interface StoreDetailReviewProps { store: StoreDetail; @@ -77,16 +78,16 @@ const StoreDetailReview: React.FC = ({
-

{store.storeName}

+

{store.storeName}

다녀오셨나요?

리뷰를 통해 경험을 공유해주세요!

- +
@@ -143,7 +144,7 @@ const StoreDetailReview: React.FC = ({ {page + 1 < totalPage && ( diff --git a/src/components/StoreMap/SearchOnMapBtn.tsx b/src/components/StoreMap/SearchOnMapBtn.tsx index ed1297f..3c1ebbc 100644 --- a/src/components/StoreMap/SearchOnMapBtn.tsx +++ b/src/components/StoreMap/SearchOnMapBtn.tsx @@ -13,7 +13,7 @@ const SearchOnMapBtn: React.FC = ({ lat, lng, onClick }) => onClick={() => onClick(lat, lng)} > 재검색 아이콘 - 현 지도에서 검색 + 현 지도에서 검색 ); diff --git a/src/components/StoreReview/CheckBox.tsx b/src/components/StoreReview/CheckBox.tsx index adbf745..035ab2c 100644 --- a/src/components/StoreReview/CheckBox.tsx +++ b/src/components/StoreReview/CheckBox.tsx @@ -30,7 +30,7 @@ const CheckBox: React.FC = ({onCheckChange }) => {
- + 네, 실제로 방문해 급식카드로 이용했어요. diff --git a/src/components/StoreReview/CommentBox.tsx b/src/components/StoreReview/CommentBox.tsx index dbcf092..7492c31 100644 --- a/src/components/StoreReview/CommentBox.tsx +++ b/src/components/StoreReview/CommentBox.tsx @@ -10,7 +10,7 @@ const CommentBox: React.FC = ({ value, onChange }) => { return (
-
+
리뷰를 작성해주세요 @@ -27,7 +27,7 @@ const CommentBox: React.FC = ({ value, onChange }) => { onChange={(e) => onChange(e.target.value)} maxLength={maxLength} placeholder={`다녀온 식당 어땠나요?\n맛이나 친절도, 분위기를 솔직하게 공유해주세요!`} - className="placeholder-[#919191] text-[#616161] text-body-md-description px-[16px] pt-[12px] pb-[40px] flex border border-[#C3C3C3] rounded-[12px]" + className="placeholder-main-gray text-text-gray text-body-md-description px-[16px] pt-[12px] pb-[40px] flex border border-sub-gray rounded-[12px]" >
); diff --git a/src/components/StoreReview/MainTag.tsx b/src/components/StoreReview/MainTag.tsx index 9909650..333f443 100644 --- a/src/components/StoreReview/MainTag.tsx +++ b/src/components/StoreReview/MainTag.tsx @@ -10,7 +10,7 @@ interface MainTagProps { const MainTag: React.FC = ({ text, onClick, rounded }) => { const TagElement = onClick ? 'button' : 'span'; const baseClasses = - 'inline-flex w-fit whitespace-nowrap justify-center h-[24px] bg-[#F4F6F8] leading-[24px] text-black-500 border-[1.5px] border-[#65CE58] text-body-md-description px-[12px]'; + 'inline-flex w-fit whitespace-nowrap justify-center h-[24px] bg-bg-gray leading-[24px] text-black-500 border-[1.5px] border-main-color text-body-md-description px-[12px]'; const roundedClass = rounded || 'rounded-[12px]'; return ( diff --git a/src/components/StoreReview/SelectTag.tsx b/src/components/StoreReview/SelectTag.tsx index 8b204fe..3d07514 100644 --- a/src/components/StoreReview/SelectTag.tsx +++ b/src/components/StoreReview/SelectTag.tsx @@ -23,13 +23,13 @@ const SelectTag: React.FC = ({ selected, onChange }) => { return (
-

+

태그를 선택해주세요 {' '} (최대 5개)

-
+

메뉴가 어땠나요?

= ({ {/* 상단 텍스트 영역 */}
-

+

{storeCategory}

@@ -36,7 +36,7 @@ const StoreInfo: React.FC = ({
-

{storeAddress}

+

{storeAddress}

{/* 하단 지도 영역 */} diff --git a/src/components/StoreReview/UploadImage.tsx b/src/components/StoreReview/UploadImage.tsx index 7659131..2474c6d 100644 --- a/src/components/StoreReview/UploadImage.tsx +++ b/src/components/StoreReview/UploadImage.tsx @@ -53,12 +53,17 @@ const UploadImage: React.FC = ({ onFileSelect }) => { maxSizeMB: 1, maxWidthOrHeight: 1024, useWebWorker: true, + fileType: "image/webp", }); - const compressedFile = new File([compressedBlob], file.name, { - type: compressedBlob.type, - lastModified: Date.now(), - }); + const compressedFile = new File( + [compressedBlob], + file.name.replace(/\.[^/.]+$/, ".webp"), + { + type: "image/webp", + lastModified: Date.now(), + } + ); // 압축된 파일 기준으로 미리보기 URL 생성 const imageURL = URL.createObjectURL(compressedFile); @@ -71,6 +76,12 @@ const UploadImage: React.FC = ({ onFileSelect }) => { } }; + useEffect(() => { + return () => { + if (preview) URL.revokeObjectURL(preview); + }; + }, [preview]); + const handleDeleteImage = () => { setPreview(null); onFileSelect(null); @@ -85,7 +96,7 @@ const UploadImage: React.FC = ({ onFileSelect }) => { return (
-

+

사진을 추가해주세요 {' '} @@ -103,6 +114,8 @@ const UploadImage: React.FC = ({ onFileSelect }) => { src={preview} alt="preview" className="w-full h-full object-cover" + loading="lazy" + decoding="async" /> {likeCount} diff --git a/src/components/common/BottomSheetForm.tsx b/src/components/common/BottomSheetForm.tsx index 9300a62..48984d5 100644 --- a/src/components/common/BottomSheetForm.tsx +++ b/src/components/common/BottomSheetForm.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import ProfileImg from '@/assets/svgs/common/profile-img.svg'; +import OptimizedImage from './OptimizedImage'; interface BottomSheetFormProps { title: string; @@ -61,7 +62,7 @@ const BottomSheetForm: React.FC = ({

{storeInfo.category && ( -
+
{storeInfo.category}
)} @@ -81,13 +82,13 @@ const BottomSheetForm: React.FC = ({ {reviewInfo && (
- + {reviewInfo.userName}
- + {reviewInfo.content}
@@ -97,13 +98,13 @@ const BottomSheetForm: React.FC = ({ {commentInfo && (
- + {commentInfo.nickname}
- + {commentInfo.content.length > 30 ? commentInfo.content.slice(0, 30) + '...' : commentInfo.content} @@ -115,13 +116,13 @@ const BottomSheetForm: React.FC = ({ {postInfo && (
- + {postInfo.nickname}
- + {postInfo.content.length > 30 ? postInfo.content.slice(0, 30) + '...' : postInfo.content} @@ -130,7 +131,7 @@ const BottomSheetForm: React.FC = ({ )} {/*공통*/} -
+

{question}

@@ -151,9 +152,9 @@ const BottomSheetForm: React.FC = ({ /> {/* 커스텀 원 */} -
+
{selected === option && ( -
+
)}
{option} @@ -173,7 +174,7 @@ const BottomSheetForm: React.FC = ({

보내주신 의견은 운영팀이 검토 후 반영됩니다

-
+
diff --git a/src/components/common/ConfirmModal.tsx b/src/components/common/ConfirmModal.tsx index dfa9191..61fa89d 100644 --- a/src/components/common/ConfirmModal.tsx +++ b/src/components/common/ConfirmModal.tsx @@ -32,7 +32,7 @@ const ConfirmModal: React.FC = ({ onClick={onDelete} heightClass="h-[44px]" widthClass="w-1/2" - bgColorClass="bg-[#919191]" + bgColorClass="bg-main-gray" textColorClass="text-white" />
diff --git a/src/components/common/ConfirmToast.tsx b/src/components/common/ConfirmToast.tsx index df31dd9..db85e5b 100644 --- a/src/components/common/ConfirmToast.tsx +++ b/src/components/common/ConfirmToast.tsx @@ -1,5 +1,6 @@ import React from 'react'; import ConfirmIcon from '@/assets/svgs/common/confirm-icon.svg'; +import OptimizedImage from './OptimizedImage'; interface ConfirmToastProps { text: string | string[]; @@ -7,8 +8,8 @@ interface ConfirmToastProps { const ConfirmToast: React.FC = ({ text }) => { return ( -
- +
+
{Array.isArray(text) ? ( text.map((line, idx) =>

{line}

) diff --git a/src/components/common/DeleteModal.tsx b/src/components/common/DeleteModal.tsx index ab29abb..6d817ea 100644 --- a/src/components/common/DeleteModal.tsx +++ b/src/components/common/DeleteModal.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { createPortal } from 'react-dom'; import TrashIcon from '@/assets/svgs/review/trash.svg'; import Button from '@/components/common/Button'; +import OptimizedImage from './OptimizedImage'; interface DeleteModalProps { title: string; @@ -20,10 +21,10 @@ const DeleteModal: React.FC = ({
- +

{title}

-

+

{description}

@@ -40,7 +41,7 @@ const DeleteModal: React.FC = ({ onClick={onDelete} heightClass="h-[44px]" widthClass="w-1/2" - bgColorClass="bg-[#919191]" + bgColorClass="bg-main-gray" textColorClass="text-white" />
diff --git a/src/components/common/Dropdown.tsx b/src/components/common/Dropdown.tsx index 8b3ab3a..1545c38 100644 --- a/src/components/common/Dropdown.tsx +++ b/src/components/common/Dropdown.tsx @@ -44,11 +44,11 @@ const Dropdown = ({ onSelect ,options}: DropdownProps) => { return (
{index === 4 && ( -
+
)}
))} diff --git a/src/components/common/Modal.tsx b/src/components/common/Modal.tsx index 29e78c6..eddb589 100644 --- a/src/components/common/Modal.tsx +++ b/src/components/common/Modal.tsx @@ -26,21 +26,21 @@ const Modal: React.FC = ({
{title}
{description && ( -
+
{description}
)}
{!hideLabelText && ( - + {label} )} diff --git a/src/components/convenience/DeleteConvenience.tsx b/src/components/convenience/DeleteConvenience.tsx index ea17c98..1d4d0f0 100644 --- a/src/components/convenience/DeleteConvenience.tsx +++ b/src/components/convenience/DeleteConvenience.tsx @@ -19,19 +19,19 @@ export default function DeleteConvenienve({

해당 글을 정말 삭제하시겠어요?

-

+

삭제된 글은 복구할 수 없어요

@@ -133,7 +133,7 @@ export default function ProductListSection({ if (suppressed) return; navigate(`/convenience/post/${p.id}`); }} - className="flex justify-between items-center py-[14px] border-b-[3px] border-[#F4F6F8] cursor-pointer" + className="flex justify-between items-center py-[14px] border-b-[3px] border-bg-gray cursor-pointer" > {/* 왼쪽: 이름(애니메이션) + 시간(고정) */}
@@ -153,7 +153,7 @@ export default function ProductListSection({
- + {p.relativeCreatedAt}
@@ -168,10 +168,10 @@ export default function ProductListSection({ animate="center" exit="exit" custom={dir} - className={`inline-block text-[12px] leading-[18px] font-semibold tracking-[0.01em] bg-[#F4F6F8] border-[1px] rounded-[8px] px-3 py-1 will-change-transform ${ + className={`inline-block text-[12px] leading-[18px] font-semibold tracking-[0.01em] bg-bg-gray border-[1px] rounded-[8px] px-3 py-1 will-change-transform ${ p.isAvailable - ? 'text-[#029F64] border-[#029F64]' - : 'text-[#FF6452] border-[#FF6452]' + ? 'text-sub-color border-main-color' + : 'text-warning border-warning' }`} > {p.isAvailable ? '결제가능' : '결제불가'} diff --git a/src/components/convenience/SelectableButton.tsx b/src/components/convenience/SelectableButton.tsx index e6d4df2..5703267 100644 --- a/src/components/convenience/SelectableButton.tsx +++ b/src/components/convenience/SelectableButton.tsx @@ -23,7 +23,7 @@ export default function SelectableButton({ ${ isSelected ? `bg-[${selectedBgColor}] border-[${selectedBorderColor}] text-black` - : 'bg-white hover:bg-[var(--BG,#F3F5ED)] border-[#C3C3C3] text-[#616161]' + : 'bg-white hover:bg-[var(--BG,#F3F5ED)] border-sub-gray text-text-gray' } ${className} `} diff --git a/src/components/home/CarouselBanner.tsx b/src/components/home/CarouselBanner.tsx index 8f56bde..452d147 100644 --- a/src/components/home/CarouselBanner.tsx +++ b/src/components/home/CarouselBanner.tsx @@ -20,7 +20,7 @@ export default function CarouselBanner({ onSlideChange }: CarouselBannerProps) { content: (
-

아동급식카드

+

아동급식카드

사용 가맹점을 검색하고,

리뷰를 남겨보세요!

@@ -31,10 +31,10 @@ export default function CarouselBanner({ onSlideChange }: CarouselBannerProps) { { content: (
-
+
현재는{' '} - + 인천, 부천, 수원, 고양, 용인, 성남 만 제공되며,{' '} @@ -43,7 +43,7 @@ export default function CarouselBanner({ onSlideChange }: CarouselBannerProps) {
{noResult ? (
-

+

아직은 공유된 제품 정보가 없어요

-

+

여러분의 결제 경험을 공유해주세요

@@ -106,10 +106,10 @@ function ConvenienceStoreSection() { > {item.name}
{item.isAvailable ? '결제가능' : '결제불가'} @@ -117,7 +117,7 @@ function ConvenienceStoreSection() { {/* {item.isAvailable ? '결제가능' : '결제불가'} diff --git a/src/components/home/HomeTopBar.tsx b/src/components/home/HomeTopBar.tsx index c02d739..9114f69 100644 --- a/src/components/home/HomeTopBar.tsx +++ b/src/components/home/HomeTopBar.tsx @@ -45,7 +45,7 @@ export default function HomeTopBar({ address = '' }: HomeTopBarProps) {
- + {address}
@@ -58,7 +58,7 @@ export default function HomeTopBar({ address = '' }: HomeTopBarProps) { ) : ( diff --git a/src/components/home/Top8StoreSection.tsx b/src/components/home/Top8StoreSection.tsx index b9ed4c4..f65c590 100644 --- a/src/components/home/Top8StoreSection.tsx +++ b/src/components/home/Top8StoreSection.tsx @@ -19,7 +19,7 @@ function Top8StoreSection() { }; return ( -
+

오늘 끼니는 여기 어때요?

diff --git a/src/components/home/TopStoreItem.tsx b/src/components/home/TopStoreItem.tsx index 790d8b2..e0dcbf6 100644 --- a/src/components/home/TopStoreItem.tsx +++ b/src/components/home/TopStoreItem.tsx @@ -71,7 +71,7 @@ const TopStoreItem = ({ store }: TopStoreItemProps) => { {/* 주소: 이름이 2줄이면 1줄, 1줄이면 2줄 */}
@@ -79,7 +79,7 @@ const TopStoreItem = ({ store }: TopStoreItemProps) => {
{/* 조회수 */} -
+
조회 {store.viewCount}
diff --git a/src/components/notification/EmptyNotification.tsx b/src/components/notification/EmptyNotification.tsx index db7d2b8..d75bc10 100644 --- a/src/components/notification/EmptyNotification.tsx +++ b/src/components/notification/EmptyNotification.tsx @@ -2,7 +2,7 @@ import EmptyIcon from '@/assets/svgs/logo/curious-congG.svg'; export default function EmptyNotification() { return ( -
+
알림 없음

{createdAt}

- + {senderNickname} -

+

님이 {content}

diff --git a/src/pages/auth/MyNeighborhoodPage.tsx b/src/pages/auth/MyNeighborhoodPage.tsx index 65de358..e98ae0c 100644 --- a/src/pages/auth/MyNeighborhoodPage.tsx +++ b/src/pages/auth/MyNeighborhoodPage.tsx @@ -120,14 +120,14 @@ function MyNeighborhoodPage() { />

자주 가는 지역을

-

즐겨찾는 지역으로 등록할 수 있어요

+

즐겨찾는 지역으로 등록할 수 있어요

-
+
자주 가는 지역을 설정하면 매번 GPS를 허용하지 않아도
빠르게 확인할 수 있어요.
-
- 현재는 인천,부천,수원,고양,용인,성남시만 제공되며, +
+ 현재는 인천,부천,수원,고양,용인,성남시만 제공되며,
다른 지역은 요청이 많은 순으로 추가될 예정이에요.
@@ -135,17 +135,17 @@ function MyNeighborhoodPage() {
-
+
{dongList && dongList.length > 0 ? ( -
    +
      {dongList.map((dong:any) => (
    • ) : city && district && !selectedDong ? ( -

      +

      *해당하는 지역이 없어요

      ) : null} diff --git a/src/pages/auth/NicknamePage.tsx b/src/pages/auth/NicknamePage.tsx index a9a0965..7e5972f 100644 --- a/src/pages/auth/NicknamePage.tsx +++ b/src/pages/auth/NicknamePage.tsx @@ -90,7 +90,7 @@ export default function NicknamePage() {

      반가워요!

      -

      +

      사용할 닉네임을 정해주세요

      @@ -131,7 +131,7 @@ export default function NicknamePage() { {/* 하단 고정 버튼 */}
      -

      +

      끼니콩에 가입함으로써
      끼니콩의{' '} @@ -142,7 +142,7 @@ export default function NicknamePage() { '_blank', ) } - className="text-[#919191] font-pretendard text-[12px] font-bold leading-[150%] tracking-[0.012px] underline decoration-solid underline-offset-2 decoration-auto" + className="text-main-gray font-pretendard text-[12px] font-bold leading-[150%] tracking-[0.012px] underline decoration-solid underline-offset-2 decoration-auto" > 이용 약관 diff --git a/src/pages/auth/NicknamePageXXX.tsx b/src/pages/auth/NicknamePageXXX.tsx index 1d4e772..e9e8ff2 100644 --- a/src/pages/auth/NicknamePageXXX.tsx +++ b/src/pages/auth/NicknamePageXXX.tsx @@ -9,11 +9,11 @@ function NicknamePageXXX() {

      반가워요!

      -

      +

      사용할 닉네임을 정해주세요

      -

      +

      설정한 닉네임은 마이페이지에서 수정할 수 있어요

      diff --git a/src/pages/community/CommunityPostDetailPage.tsx b/src/pages/community/CommunityPostDetailPage.tsx index 4602a9f..489cc70 100644 --- a/src/pages/community/CommunityPostDetailPage.tsx +++ b/src/pages/community/CommunityPostDetailPage.tsx @@ -17,6 +17,7 @@ import useCommentActions from '@/hooks/useCommentActions'; import DeleteModal from '@/components/common/DeleteModal'; import ConfirmToast from '@/components/common/ConfirmToast'; import { createPortal } from 'react-dom'; +import OptimizedImage from '@/components/common/OptimizedImage'; interface Comment { commentId: number; @@ -209,7 +210,7 @@ const CommunityPostDetailPage = () => { showBackButton={true} onBack={() => navigate('/community')} /> -
      +
      @@ -233,7 +234,7 @@ const CommunityPostDetailPage = () => { {post.nickname ?? '익명'} - + {post.isModified && '수정됨 ·'}{post.createdAt} · 조회 {post.viewCount}
      @@ -252,7 +253,7 @@ const CommunityPostDetailPage = () => { {Array.isArray(post.imageUrls) && post.imageUrls.length > 0 && (
      {post.imageUrls.map((url, idx) => ( - {`게시글 {
      -
      +
      @@ -159,7 +159,7 @@ export default function CommunityWritePage() { 사진을 추가해주세요 - (선택/최대 3장) + (선택/최대 3장)
      @@ -168,8 +168,8 @@ export default function CommunityWritePage() {
      @@ -156,10 +156,10 @@ export default function AddConveniencePage() {
      @@ -182,10 +182,10 @@ export default function ConvenienceDetailPage() { {/* 상품명 , 상태, 카테고리 */}
      {post.isAvailable ? '결제가능' : '결제불가'} @@ -194,10 +194,10 @@ export default function ConvenienceDetailPage() { {post.name}
      -
      +
      {fromServerCategory(post.category)}
      -
      +
      {fromServerBrand(post.brand)}
      @@ -209,13 +209,13 @@ export default function ConvenienceDetailPage() {
      -

      +

      {post.description}

      {/* 구분선 */} -
      +
      {/* 피드백 버튼 */} 제품명 -
      +
      setKeyword(e.target.value)} placeholder="ex) 서울우유 500ML" - className="flex-1 outline-none text-[#616161] text-body-md-title - placeholder:text-[#919191] placeholder:text-body-md-description" + className="flex-1 outline-none text-text-gray text-body-md-title + placeholder:text-main-gray placeholder:text-body-md-description" />
      -
      +

      탈퇴 처리 안내

      • 탈퇴한 아이디는 본인과 타인 모두{' '} - 재사용 및 복구가 불가 + 재사용 및 복구가 불가 하오니 신중하게 선택하시기 바랍니다.
      • 탈퇴 후{' '} - 회원정보는 모두 삭제 + 회원정보는 모두 삭제 됩니다.
      -
      +

      개인정보 삭제 안내

      -

      +

      탈퇴 후에도{' '} - 등록한 게시물은 그대로{' '} + 등록한 게시물은 그대로{' '} 남아 있습니다. [편의점 정보 공유 게시판]과 [커뮤니티]에 올린 게시글 및 댓글은 탈퇴 시 자동 삭제되지 않고 그대로 남아 있으며, 작성자 정보는 비식별화 처리됩니다. 삭제를 원하는 게시글이 있다면 반드시{' '} - + 탈퇴 전 비공개 처리하거나 삭제 하시기 바랍니다. @@ -83,7 +83,7 @@ const AccountDeletePage = () => { 위 사항을 모두 확인하였으며, 이에 동의합니다. diff --git a/src/pages/mypage/FeedbackPage.tsx b/src/pages/mypage/FeedbackPage.tsx index 684b767..9d86563 100644 --- a/src/pages/mypage/FeedbackPage.tsx +++ b/src/pages/mypage/FeedbackPage.tsx @@ -45,7 +45,7 @@ const FeedbackPage = () => { />

      -
      +

      끼니콩을 이용하면서 diff --git a/src/pages/mypage/MyLikesPage.tsx b/src/pages/mypage/MyLikesPage.tsx index b162a85..ae39490 100644 --- a/src/pages/mypage/MyLikesPage.tsx +++ b/src/pages/mypage/MyLikesPage.tsx @@ -73,12 +73,12 @@ const MyLikesPage = () => { /> {loading && totalCount === 0 ? null : totalCount === 0 ? ( -

      +
      ) : (
      -
      +
      좋아요 한 글 {totalCount}개
      diff --git a/src/pages/mypage/MyPostsPage.tsx b/src/pages/mypage/MyPostsPage.tsx index f087462..b85bbcc 100644 --- a/src/pages/mypage/MyPostsPage.tsx +++ b/src/pages/mypage/MyPostsPage.tsx @@ -83,11 +83,11 @@ const MyPostsPage = () => { onChange={handleChipChange} className={ tab === '게시글' && chip === '커뮤니티' && isCommunityEmpty - ? 'bg-[#F4F6F8]' + ? 'bg-bg-gray' : tab === '게시글' && chip === '편의점 게시판' && isConvenienceEmpty - ? 'bg-[#F4F6F8]' + ? 'bg-bg-gray' : '' } /> diff --git a/src/pages/mypage/MyReviewsPage.tsx b/src/pages/mypage/MyReviewsPage.tsx index fc85586..cd16682 100644 --- a/src/pages/mypage/MyReviewsPage.tsx +++ b/src/pages/mypage/MyReviewsPage.tsx @@ -89,12 +89,12 @@ const MyReviewsPage = () => { onBack={() => navigate('/mypage')} /> {totalCount === 0 ? ( -
      +
      ) : (
      -
      +
      내가 쓴 리뷰 수 {totalCount}개
      diff --git a/src/pages/mypage/MyScrapPage.tsx b/src/pages/mypage/MyScrapPage.tsx index c31df67..92f6d5e 100644 --- a/src/pages/mypage/MyScrapPage.tsx +++ b/src/pages/mypage/MyScrapPage.tsx @@ -61,7 +61,7 @@ const MyScrapPage = () => { /> {loading ? null : scrapStores.length === 0 ? ( -
      +
      {
      ) : (
      -
      +
      내가 찜한 가게 수 {scrapStores.length}개
      {

      수정할 닉네임을 적어주세요

      -

      +

      닉네임 수정은 1회 가능해요

      diff --git a/src/pages/store-review/StoreReviewPage.tsx b/src/pages/store-review/StoreReviewPage.tsx index 5aecb82..d6e54f0 100644 --- a/src/pages/store-review/StoreReviewPage.tsx +++ b/src/pages/store-review/StoreReviewPage.tsx @@ -108,8 +108,8 @@ const StoreReviewPage = () => { disabled={!isSubmitEnabled} className={`text-white font-semibold text-title-sb-button px-[16px] py-[12px] rounded-[8px] mt-[24px] ${ isSubmitEnabled - ? 'bg-[#65CE58]' - : 'bg-[#E6E6E6] cursor-not-allowed' + ? 'bg-main-color' + : 'bg-disabled cursor-not-allowed' }`} > 공유하기 diff --git a/tailwind.config.ts b/tailwind.config.ts index 129c583..d63a621 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -15,7 +15,17 @@ const config: Config = { pretendard: ['Pretendard', 'sans-serif'], }, colors: { + + black: '#212121', 'main-color': '#65CE58', + 'sub-color': '#029F64', + 'disabled': '#E6E6E6', + 'warning': '#FF6452', + 'main-gray': '#919191', + 'sub-gray': '#C3C3C3', + 'bg-gray': '#F4F6F8', + 'text-gray': '#616161', + 'bg': 'rgba(243, 245, 237, 1)', }, boxShadow: { custom: '2px 4px 10px 0px rgba(0, 0, 0, 0.20)', diff --git a/vite.config.ts b/vite.config.ts index 83eb9fe..0e97368 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,10 +2,36 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import svgr from 'vite-plugin-svgr'; import path from 'path'; +import { visualizer } from 'rollup-plugin-visualizer'; export default defineConfig({ plugins: [react(), svgr()], resolve: { alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }], }, + build: { + target: 'es2020', + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true, + }, + }, + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], + router: ['react-router-dom'], + swiper: ['swiper/react', 'swiper/modules'], + }, + }, + }, + }, + optimizeDeps: { + include: ['react', 'react-dom', 'react-router-dom'], + }, + esbuild: { + drop: ['console', 'debugger'], + }, });