diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..7377e8e --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,19 @@ +module.exports = { + root: true, + extends: ["next", "next/core-web-vitals", "eslint:recommended", "plugin:@typescript-eslint/recommended"], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + rules: { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-empty-object-type": "off", + "no-empty": "off", + "no-case-declarations": "off", + "react/no-unescaped-entities": "off", + "@next/next/no-img-element": "off", + "react-hooks/exhaustive-deps": "off", + "prefer-const": "off", + }, +}; diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..1554927 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,32 @@ +name: Deploy Frontend to EC2 + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup SSH key + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.EC2_SSH_KEY }} + + - name: Deploy Frontend to EC2 + run: | + ssh -o StrictHostKeyChecking=no ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }} -p ${{ secrets.EC2_PORT }} << 'EOF' + cd /home/ec2-user/easyToFind-Frontend + + echo "NEXT_PUBLIC_API_BASE_URL=${{ secrets.NEXT_PUBLIC_API_BASE_URL }}" > .env.local + git pull origin main + + pnpm install + pnpm run build + pm2 restart easytofind-frontend + EOF diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index c85fb67..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import { FlatCompat } from "@eslint/eslintrc"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const compat = new FlatCompat({ - baseDirectory: __dirname, -}); - -const eslintConfig = [ - ...compat.extends("next/core-web-vitals", "next/typescript"), -]; - -export default eslintConfig; diff --git a/package.json b/package.json index 4b53528..aeb4ff7 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,10 @@ "@nivo/pie": "^0.99.0", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.3", "@tanstack/react-query": "^5.83.0", -<<<<<<< Updated upstream "@tanstack/react-query-devtools": "^5.83.0", -======= ->>>>>>> Stashed changes "@types/react-datepicker": "^7.0.0", "apexcharts": "^5.3.0", "class-variance-authority": "^0.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf18544..2884de2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,18 +23,18 @@ importers: '@radix-ui/react-progress': specifier: ^1.1.7 version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slider': + specifier: ^1.3.5 + version: 1.3.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-slot': specifier: ^1.2.3 version: 1.2.3(@types/react@19.1.8)(react@19.1.0) '@tanstack/react-query': specifier: ^5.83.0 version: 5.83.0(react@19.1.0) -<<<<<<< Updated upstream '@tanstack/react-query-devtools': specifier: ^5.83.0 version: 5.83.0(@tanstack/react-query@5.83.0(react@19.1.0))(react@19.1.0) -======= ->>>>>>> Stashed changes '@types/react-datepicker': specifier: ^7.0.0 version: 7.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -84,9 +84,6 @@ importers: '@eslint/eslintrc': specifier: ^3 version: 3.3.1 - '@tanstack/react-query-devtools': - specifier: ^5.83.0 - version: 5.83.0(@tanstack/react-query@5.83.0(react@19.1.0))(react@19.1.0) '@types/node': specifier: ^20 version: 20.19.8 @@ -500,6 +497,25 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: @@ -518,6 +534,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-label@2.1.7': resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} peerDependencies: @@ -557,6 +582,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-slider@1.3.5': + resolution: {integrity: sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: @@ -566,6 +604,51 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@react-spring/animated@10.0.1': resolution: {integrity: sha512-BGL3hA66Y8Qm3KmRZUlfG/mFbDPYajgil2/jOP0VXf2+o2WPVmcDps/eEgdDqgf5Pv9eBbyj7LschLMuSjlW3Q==} peerDependencies: @@ -3400,6 +3483,22 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.2': {} + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 @@ -3412,6 +3511,12 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + '@radix-ui/react-direction@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -3440,6 +3545,25 @@ snapshots: '@types/react': 19.1.8 '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-slider@1.3.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) @@ -3447,6 +3571,40 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + '@react-spring/animated@10.0.1(react@19.1.0)': dependencies: '@react-spring/shared': 10.0.1(react@19.1.0) diff --git a/src/app/(pages)/login/page.tsx b/src/app/(pages)/login/page.tsx index 593e03d..d558893 100644 --- a/src/app/(pages)/login/page.tsx +++ b/src/app/(pages)/login/page.tsx @@ -21,8 +21,7 @@ export default function LoginPage() { } setLoading(true); try { - const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL; - const res = await fetch(`${API_BASE_URL}/api/auth/login`, { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/src/app/page.tsx b/src/app/page.tsx index 316fe60..30851a9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -24,9 +24,9 @@ export default function Home() { const router = useRouter(); const apiUrls = [ - 'http://localhost:3001/api/main/aum', - 'http://localhost:3001/api/main/fluc', - 'http://localhost:3001/api/main/volume' + `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/main/aum`, + `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/main/fluc`, + `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/main/volume` ]; useEffect(() => { diff --git a/src/components/blocks/ETFDetail/ETFDetailHoldings.client.tsx b/src/components/blocks/ETFDetail/ETFDetailHoldings.client.tsx index 15d2a94..76fa7ea 100644 --- a/src/components/blocks/ETFDetail/ETFDetailHoldings.client.tsx +++ b/src/components/blocks/ETFDetail/ETFDetailHoldings.client.tsx @@ -13,8 +13,6 @@ import { useQuery } from "@tanstack/react-query"; // 한국어로 포맷팅 registerLocale("ko", ko); -interface ETFHoldingsData extends ETFHoldingsWithStock {} - // 캘린더 커스텀 const customDatePickerStyles = ` .react-datepicker-wrapper { diff --git a/src/components/blocks/me/mbti/ResultComponent.client.tsx b/src/components/blocks/me/mbti/ResultComponent.client.tsx index 54e3b1e..2258f78 100644 --- a/src/components/blocks/me/mbti/ResultComponent.client.tsx +++ b/src/components/blocks/me/mbti/ResultComponent.client.tsx @@ -19,8 +19,7 @@ export default function ResultComponentClient({ riskType, theme, riskScore }: Pr //결과 불러오기 useEffect(() => { const fetchData = async () => { - const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL; - let url = `${API_BASE_URL}/api/recommendation`; + let url = `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/recommendation`; let body: any = {}; if (Array.isArray(riskScore) && riskScore.length === 4) { @@ -39,7 +38,7 @@ export default function ResultComponentClient({ riskType, theme, riskScore }: Pr } if (selectedTab === "theme") { - url = `${API_BASE_URL}/api/recommendation/theme`; + url = `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/recommendation/theme`; body.theme = theme; } @@ -59,14 +58,13 @@ export default function ResultComponentClient({ riskType, theme, riskScore }: Pr //결과 저장 useEffect(() => { const fetchData = async () => { - const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL; // riskScore 정규화 함수 const normalize = (arr: number[]) => { const sum = arr.reduce((a, b) => a + b, 0); return sum === 0 ? [0.25, 0.25, 0.25, 0.25] : arr.map(v => v / sum); }; const [stabilityWeight, liquidityWeight, growthWeight, divWeight] = normalize(riskScore); - const response = await fetch(`${API_BASE_URL}/api/me/mbti`, { + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/me/mbti`, { method: "PUT", headers: { "Content-Type": "application/json", diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 7efa57c..8515551 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -40,10 +40,9 @@ export const Header = () => { useEffect(() => {}, []); - const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL; const handleLogout = async () => { try { - const res = await fetch(`${API_BASE_URL}/api/auth/logout`, { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/auth/logout`, { method: "POST", credentials: "include", }); diff --git a/src/hooks/useGoalPlanner.ts b/src/hooks/useGoalPlanner.ts index b5af883..05d1e4f 100644 --- a/src/hooks/useGoalPlanner.ts +++ b/src/hooks/useGoalPlanner.ts @@ -5,9 +5,7 @@ import { GoalPlannerRequest, GoalPlannerResponse } from "@/types/goal"; import { useUserRiskProfile } from "./useUserRiskProfile"; // 백엔드 API URL 설정 -const API_BASE_URL = - process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:3001"; -const GOAL_PLANNER_URL = `${API_BASE_URL}/api/goal-planner`; +const GOAL_PLANNER_URL = `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/goal-planner`; export const useGoalPlanner = () => { const { @@ -83,7 +81,7 @@ export const useGoalPlanner = () => { // 5. API 엔드포인트 결정 const getApiEndpoint = () => { - return `${API_BASE_URL}/api/goal-planner/monte-carlo`; + return `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/goal-planner/monte-carlo`; }; // 6. API 호출 핸들러