Kaynağa Gözat

마이그레이션 완료

home 6 ay önce
ebeveyn
işleme
f14659d997

+ 0 - 6
package.json

@@ -6,7 +6,6 @@
   "scripts": {
     "dev": "vite",
     "build": "vite build",
-    "lint": "eslint .",
     "preview": "vite preview"
   },
   "engines": {
@@ -20,16 +19,11 @@
     "tailwindcss": "^4.1.7"
   },
   "devDependencies": {
-    "@eslint/js": "^9.25.0",
     "@types/navermaps": "^3.9.1",
     "@types/node": "^24.0.0",
     "@types/react": "^19.1.7",
     "@types/react-dom": "^19.1.2",
     "@vitejs/plugin-react": "^4.4.1",
-    "eslint": "^9.25.0",
-    "eslint-plugin-react-hooks": "^5.2.0",
-    "eslint-plugin-react-refresh": "^0.4.19",
-    "globals": "^16.0.0",
     "typescript": "^5.8.3",
     "vite": "^6.3.5"
   }

+ 14 - 49
src/App.tsx

@@ -1,55 +1,20 @@
-/// <reference types="vite/client" />
-
-import React, { Suspense, lazy } from 'react';
+/**
+ * [파일명 규칙 안내]
+ * - 이 프로젝트는 src/pages 경로 하위의 컴포넌트를 자동으로 lazy import 하며,
+ * - 각 파일은 반드시 PascalCase (대문자 시작)로 작성되어야 합니다.
+ *   예: about/Ceo.tsx, performance/History.tsx 등
+ * - 단, index.tsx는 예외적으로 허용됩니다.
+ *
+ * 해당 규칙은 lazyImport.tsx에서 강제 적용되며, 이를 지키지 않으면 라우팅 실패가 발생합니다.
+ */
+
+
+import React from 'react';
 import { Routes, Route } from 'react-router-dom';
 import Header from '@/components/Header';
-import Layout from '@/components/Layout';
-import menuItems from '@/data/menuItems';
-
-// 모든 페이지를 glob으로 가져오기
-const pages = import.meta.glob('@/pages/**/*.tsx');
-
-// lazy import 유틸
-const lazyImport = (path: string) => {
-  const importer = pages[`/src/pages/${path}.tsx`];
-  return importer
-    ? lazy(importer as () => Promise<{ default: React.ComponentType }>)
-    : lazy(() => Promise.resolve({ default: () => <div>페이지를 찾을 수 없습니다.</div> }));
-};
-
-// Suspense 래핑
-const withSuspense = (Component: React.LazyExoticComponent<React.ComponentType>) => (
-  <Suspense fallback={<div>로딩 중...</div>}>
-    <Component />
-  </Suspense>
-);
-
-// 메뉴 기반 라우팅
-const renderRoutesFromMenu = () => {
-  return menuItems.map((parent) => {
-    const parentPath = parent.href.replace(/^\//, '');
-
-    return (
-      <Route key={parent.href} path={parentPath} element={<Layout />}>
-        {parent.children.map((child) => {
-          const childPath = child.href.replace(/^\//, '');
-          const fullPath = `${parentPath}/${childPath}`;
-          const Component = lazyImport(fullPath);
-
-          return (
-            <Route
-              key={child.href}
-              path={childPath}
-              element={withSuspense(Component)}
-            />
-          );
-        })}
-      </Route>
-    );
-  });
-};
+import { lazyImport, withSuspense } from '@/utils/lazyImport';
+import { renderRoutesFromMenu } from '@/routes/generateRoutes';
 
-// 메인 및 404 페이지도 lazy import로 처리
 const Main = lazyImport('main/index');
 const NotFound = lazyImport('NotFound');
 

+ 2 - 1
src/components/Header.tsx

@@ -1,6 +1,7 @@
 import React, {useState} from 'react';
 import menuItems from '@/data/menuItems';
 import {Link} from "react-router-dom";
+import {IMAGE_PREFIX} from "@/constants";
 
 const Header = () => {
   const [activeIndex, setActiveIndex] = useState<number | null>(null);
@@ -18,7 +19,7 @@ const Header = () => {
         onClick={() => setActiveIndex(null)}
         className="absolute left-[40px] top-[26px] flex items-center gap-1 w-32"
       >
-        <img className="w-10 h-7" src="/logo.png" alt="logo"/>
+        <img className="w-10 h-7" src={`${IMAGE_PREFIX}/logo.png`} alt="logo"/>
         <div className="text-gray-600 text-xl font-bold font-['Inter']">반도산전</div>
       </Link>
 

+ 7 - 4
src/constants.ts

@@ -1,4 +1,7 @@
-export const BUSINESS_IMAGE_PREFIX = "/image/business";
-export const PARTNER_IMAGE_PREFIX = "/image/partners";
-export const PROJECT_IMAGE_PREFIX = "/image/project";
-export const LAYOUT_IMAGE_PREFIX = "/image/layout";
+/* 이미지 관련 상수 */
+export const IMAGE_PREFIX = "/image";
+export const BUSINESS_IMAGE_PREFIX = `${IMAGE_PREFIX}/business`;
+export const PARTNER_IMAGE_PREFIX = `${IMAGE_PREFIX}/partners`;
+export const PROJECT_IMAGE_PREFIX = `${IMAGE_PREFIX}/project`;
+export const LAYOUT_IMAGE_PREFIX = `${IMAGE_PREFIX}/layout`;
+export const FOOTER_IMAGE_PREFIX = `${IMAGE_PREFIX}/footer`;

+ 0 - 0
src/pages/business/sections.tsx → src/pages/business/Sections.tsx


+ 6 - 5
src/pages/main/Footer.tsx

@@ -1,4 +1,5 @@
 import React from 'react';
+import {FOOTER_IMAGE_PREFIX} from "@/constants";
 
 const Footer = () => {
     return (
@@ -10,7 +11,7 @@ const Footer = () => {
                 <div className="flex gap-8">
                     {/* 로고 */}
                     <div className="">
-                        <img className="w-24 h-16" src="/footer/footerLogo.svg" alt="Logo" />
+                        <img className="w-24 h-16" src={`${FOOTER_IMAGE_PREFIX}/footerLogo.svg`} alt="Logo" />
                     </div>
 
                     {/* 회사 정보 2단 그리드 */}
@@ -50,10 +51,10 @@ const Footer = () => {
 
                 {/* SNS 아이콘 영역 */}
                 <div className="flex justify-end mb-10 gap-6">
-                    <img className="w-20 h-11" src="/footer/KTR마크.png" alt="icon1" />
-                    <img className="w-12 h-11" src="/footer/중소기업청.png" alt="icon2" />
-                    <img className="w-12 h-11" src="/footer/벤처기업인증마크.png" alt="icon3" />
-                    <img className="w-16 h-11" src="/footer/ci.png" alt="icon4" />
+                    <img className="w-20 h-11" src={`${FOOTER_IMAGE_PREFIX}/KTR마크.png`} alt="icon1" />
+                    <img className="w-12 h-11" src={`${FOOTER_IMAGE_PREFIX}/중소기업청.png`} alt="icon2" />
+                    <img className="w-12 h-11" src={`${FOOTER_IMAGE_PREFIX}/벤처기업인증마크.png`} alt="icon3" />
+                    <img className="w-16 h-11" src={`${FOOTER_IMAGE_PREFIX}/ci.png`} alt="icon4" />
                 </div>
             </div>
             

+ 3 - 2
src/pages/main/Hero.tsx

@@ -1,10 +1,11 @@
 import React, {useState, useEffect} from 'react';
 import DarkOverlay from "@/components/DarkOverlay.jsx";
+import {IMAGE_PREFIX} from "@/constants";
 
 const images = [
-    '/hero.jpg',
+    `${IMAGE_PREFIX}/hero.jpg`,
     // '/hero2.png',
-    '/hero.jpg',
+    `${IMAGE_PREFIX}/hero.jpg`,
 ];
 
 const Hero = () => {

+ 2 - 1
src/pages/main/Partners.tsx

@@ -1,12 +1,13 @@
 import React from 'react';
 import DarkOverlay from "@/components/DarkOverlay.jsx";
 import partnerList from '@/data/partners';
+import {IMAGE_PREFIX} from "@/constants";
 
 const Partners = () => {
     return (
         <div
             className="relative w-full pt-10 pb-20 mb-[4vw] bg-cover bg-center"
-            style={{backgroundImage: 'url(/partners-bg.jpg)'}}
+            style={{backgroundImage: `url(${IMAGE_PREFIX}/partners-bg.jpg)`}}
         >
             <DarkOverlay/>
             <div className="max-w-7xl mx-auto relative flex-col justify-items-center">

+ 37 - 0
src/routes/generateRoutes.tsx

@@ -0,0 +1,37 @@
+import React from 'react';
+import { Route } from 'react-router-dom';
+import Layout from '@/components/Layout';
+import menuItems from '@/data/menuItems';
+import { lazyImport, withSuspense } from '@/utils/lazyImport';
+
+/**
+ * [라우팅 규칙]
+ * - menuItems의 경로(href)는 모두 소문자여야 하며,
+ * - 실제 컴포넌트 파일명은 PascalCase (대문자 시작)를 따라야 합니다.
+ * - 예: menuItems에 "about/ceo" → 실제 파일: src/pages/about/Ceo.tsx
+ *
+ * 자동 라우팅이므로 규칙을 따르지 않으면 컴포넌트를 불러올 수 없습니다.
+ */
+
+export const renderRoutesFromMenu = () => {
+  return menuItems.map((parent) => {
+    const parentPath = parent.href.replace(/^\//, '');
+
+    return (
+      <Route key={parent.href} path={parentPath} element={<Layout />}>
+        {parent.children.map((child) => {
+          const childPath = child.href.replace(/^\//, '');
+          const fullPath = `${parentPath}/${childPath}`;
+          const Component = lazyImport(fullPath);
+          return (
+            <Route
+              key={child.href}
+              path={childPath}
+              element={withSuspense(Component)}
+            />
+          );
+        })}
+      </Route>
+    );
+  });
+};

+ 63 - 0
src/utils/lazyImport.tsx

@@ -0,0 +1,63 @@
+/**
+ * [개발 규칙]
+ * - src/pages 하위의 모든 파일은 반드시 .tsx 확장자를 사용해야 합니다.
+ * - 컴포넌트 파일명은 반드시 대문자로 시작하는 PascalCase 형식이어야 합니다.
+ *   예) About/Ceo.tsx, main/Partners.tsx 등
+ * - 단, index.tsx 파일은 예외적으로 허용됩니다 (폴더 진입점 용도).
+ *
+ * 해당 규칙을 어기면 lazyImport 함수에서 컴포넌트를 찾지 못하게 됩니다.
+ */
+
+/// <reference types="vite/client" />
+import React, { lazy, Suspense } from 'react';
+
+// Vite glob으로 모든 페이지 import
+const pages = import.meta.glob('@/pages/**/*.tsx');
+
+/**
+ * URL 경로의 마지막 세그먼트만 PascalCase(대문자 시작)로 변환
+ * 예: "about/ceo" → "about/Ceo"
+ */
+const toPascalCaseFilePath = (path: string): string => {
+  const parts = path.split('/');
+  const last = parts.pop();
+  if (!last) return path;
+  const fileName = last.charAt(0).toUpperCase() + last.slice(1);
+  return [...parts, fileName].join('/');
+};
+
+/**
+ * [lazyImport 함수] 지정한 경로의 컴포넌트를 동적으로 import합니다.
+ *
+ * 처리 로직:
+ * - menuItems에서 온 경로는 전부 소문자입니다. (예: 'about/ceo')
+ * - 마지막 파일명만 PascalCase로 바꿔서 실제 파일 위치와 일치시킵니다.
+ * - Vite의 import.meta.glob()으로 가져온 pages 객체에서 해당 컴포넌트를 찾습니다.
+ * - 못 찾으면 기본 fallback 컴포넌트를 반환합니다.
+ */
+export const lazyImport = (path: string) => {
+  // 'about/ceo' → 'about/Ceo' 형태로 경로 변환
+  const transformedPath = toPascalCaseFilePath(path);
+
+  // glob으로 등록된 경로에서 해당 컴포넌트 importer 찾기
+  const importer = pages[`/src/pages/${transformedPath}.tsx`];
+
+  // importer가 존재하면 lazy 로딩 적용, 없으면 fallback 페이지
+  return importer
+    ? lazy(importer as () => Promise<{ default: React.ComponentType }>)
+    : lazy(() => Promise.resolve({
+      default: () => <div>페이지를 찾을 수 없습니다.</div>
+    }));
+};
+
+/**
+ * Suspense 래핑 유틸
+ * - 컴포넌트를 받아 로딩 fallback을 함께 적용
+ */
+export const withSuspense = (
+  Component: React.LazyExoticComponent<React.ComponentType>
+) => (
+  <Suspense fallback={<div>로딩 중...</div>}>
+    <Component />
+  </Suspense>
+);