diff --git a/.env b/.env new file mode 100644 index 0000000..8978752 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_SERVER_API_URL=http://localhost:8000 \ No newline at end of file diff --git a/index.html b/index.html index 6049f2a..4d1b104 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ - + - PayCheck + 나만의 회계 비서, PayCheck
diff --git a/package.json b/package.json index 0d46058..4ceb83c 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ }, "dependencies": { "@tailwindcss/vite": "^4.1.7", + "axios": "^1.9.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-router-dom": "^7.6.0", "tailwindcss": "^4.1.7" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fac60a4..d9372c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,12 +11,18 @@ importers: '@tailwindcss/vite': specifier: ^4.1.7 version: 4.1.7(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)) + axios: + specifier: ^1.9.0 + version: 1.9.0 react: specifier: ^19.1.0 version: 19.1.0 react-dom: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) + react-router-dom: + specifier: ^7.6.0 + version: 7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) tailwindcss: specifier: ^4.1.7 version: 4.1.7 @@ -671,6 +677,12 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.9.0: + resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -689,6 +701,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -711,12 +727,20 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -736,10 +760,18 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + electron-to-chromium@1.5.155: resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==} @@ -747,6 +779,22 @@ packages: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.25.4: resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} engines: {node: '>=18'} @@ -856,15 +904,39 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -885,6 +957,10 @@ packages: resolution: {integrity: sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -895,6 +971,18 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1040,6 +1128,10 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1048,6 +1140,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1125,6 +1225,9 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1141,6 +1244,23 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-router-dom@7.6.0: + resolution: {integrity: sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.6.0: + resolution: {integrity: sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -1173,6 +1293,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1856,6 +1979,16 @@ snapshots: argparse@2.0.1: {} + asynckit@0.4.0: {} + + axios@1.9.0: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + balanced-match@1.0.2: {} brace-expansion@1.1.11: @@ -1878,6 +2011,11 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.5) + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + callsites@3.1.0: {} caniuse-lite@1.0.30001718: {} @@ -1895,10 +2033,16 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + concat-map@0.0.1: {} convert-source-map@2.0.0: {} + cookie@1.0.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1913,8 +2057,16 @@ snapshots: deep-is@0.1.4: {} + delayed-stream@1.0.0: {} + detect-libc@2.0.4: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + electron-to-chromium@1.5.155: {} enhanced-resolve@5.18.1: @@ -1922,6 +2074,21 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.2 + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esbuild@0.25.4: optionalDependencies: '@esbuild/aix-ppc64': 0.25.4 @@ -2073,11 +2240,40 @@ snapshots: flatted@3.3.3: {} + follow-redirects@1.15.9: {} + + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2092,12 +2288,24 @@ snapshots: globals@16.1.0: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + ignore@5.3.2: {} ignore@7.0.4: {} @@ -2205,6 +2413,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + math-intrinsics@1.1.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -2212,6 +2422,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -2275,6 +2491,8 @@ snapshots: prelude-ls@1.2.1: {} + proxy-from-env@1.1.0: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -2286,6 +2504,20 @@ snapshots: react-refresh@0.17.0: {} + react-router-dom@7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-router: 7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + + react-router@7.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + cookie: 1.0.2 + react: 19.1.0 + set-cookie-parser: 2.7.1 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + react@19.1.0: {} resolve-from@4.0.0: {} @@ -2328,6 +2560,8 @@ snapshots: semver@7.7.2: {} + set-cookie-parser@2.7.1: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 diff --git a/public/add.svg b/public/add.svg new file mode 100644 index 0000000..9912fc3 --- /dev/null +++ b/public/add.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/barcode.svg b/public/barcode.svg new file mode 100644 index 0000000..f59c9e2 --- /dev/null +++ b/public/barcode.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..18e25f3 --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/icon2.svg b/public/icon2.svg new file mode 100644 index 0000000..872cbd3 --- /dev/null +++ b/public/icon2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/icon3.svg b/public/icon3.svg new file mode 100644 index 0000000..a60e494 --- /dev/null +++ b/public/icon3.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..b292309 --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/navigate_left.svg b/public/navigate_left.svg new file mode 100644 index 0000000..de4b7d3 --- /dev/null +++ b/public/navigate_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/navigate_right.svg b/public/navigate_right.svg new file mode 100644 index 0000000..cb44808 --- /dev/null +++ b/public/navigate_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/receipt.svg b/public/receipt.svg new file mode 100644 index 0000000..44fc08d --- /dev/null +++ b/public/receipt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/App.tsx b/src/App.tsx index d2760b4..872e4ee 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,33 @@ -import './App.css' +import './App.css'; +import {createBrowserRouter, RouterProvider} from 'react-router-dom'; +import LandingPage from './pages/LandingPage'; +import NotFoundPage from './pages/NotFoundPage'; +import MainPage from './pages/MainPage'; +import CheckPage from './pages/CheckPage'; +import Layout from './layouts/Layout'; -function App() { +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + }, + { + path: "/main", + element: , + errorElement: , + }, + { + path: "/check", + element: , + errorElement: , + } + ]); +function App() { return ( - <> - + + ) } diff --git a/src/apis/auth.ts b/src/apis/auth.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/apis/axios.ts b/src/apis/axios.ts new file mode 100644 index 0000000..6c66cf2 --- /dev/null +++ b/src/apis/axios.ts @@ -0,0 +1,5 @@ +import axios from "axios"; + +export const axiosInstance = axios.create({ + baseURL: import.meta.env.VITE_SERVER_API_URL, +}); \ No newline at end of file diff --git a/src/index.css b/src/index.css index a461c50..310ba43 100644 --- a/src/index.css +++ b/src/index.css @@ -1 +1,2 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700;900&display=swap'); @import "tailwindcss"; \ No newline at end of file diff --git a/src/layouts/Layout.tsx b/src/layouts/Layout.tsx new file mode 100644 index 0000000..d216d3d --- /dev/null +++ b/src/layouts/Layout.tsx @@ -0,0 +1,43 @@ +import type { ReactNode } from 'react'; +import { useNavigate } from 'react-router-dom'; + +interface LayoutProps { + children: ReactNode; +} + +const Layout = ({ children }: LayoutProps) => { + const navigate = useNavigate(); + return ( +
+ + {/* 메인 컨텐츠 */} +
{children}
+ {/* 푸터 */} +
+ ChillGuys! +
+
+ ); +}; + +export default Layout; \ No newline at end of file diff --git a/src/pages/CheckPage.tsx b/src/pages/CheckPage.tsx new file mode 100644 index 0000000..87f7509 --- /dev/null +++ b/src/pages/CheckPage.tsx @@ -0,0 +1,40 @@ +const CheckPage = () => { + return ( +
+
결제내역
+
25/05/08 동국대학교생활협동조합
+
+
+
품목
+
수량
+
금액
+
참여자
+
+
+
삼겹김치철판
+
2
+
13000
+
모수진
+
+
+
+
총액
+
13000원
+
+
+
정산 결과
+
+
하승연
+
모수진
+
+
+
+
엑셀로 내보내기
+
공유하기
+
+
+
+ ) +}; + +export default CheckPage; \ No newline at end of file diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx new file mode 100644 index 0000000..1a07c43 --- /dev/null +++ b/src/pages/LandingPage.tsx @@ -0,0 +1,33 @@ +import { useNavigate } from 'react-router-dom'; + +const LandingPage = () => { + const navigate = useNavigate(); + + const handleStartClick = () => { + navigate('/main'); // MainPage 경로로 이동 + }; + + return ( +
+
+ 나만의 회계 비서, + + logo + + + +
+ barcode + ChillGuys! +
+
+
+ ); +}; + +export default LandingPage; \ No newline at end of file diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx new file mode 100644 index 0000000..92f50e2 --- /dev/null +++ b/src/pages/MainPage.tsx @@ -0,0 +1,218 @@ +import React, { useState, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { axiosInstance } from '../apis/axios'; + +const MainPage = () => { + const [participant, setParticipant] = useState(''); + const [participants, setParticipants] = useState([]); + const [settleType, setSettleType] = useState<'even' | 'item'>('even'); + const [previewFiles, setPreviewFiles] = useState([]); + const [previewUrls, setPreviewUrls] = useState([]); + const fileInputRef = useRef(null); + const navigate = useNavigate(); + const [uploading, setUploading] = useState(false); + + const handleAdd = () => { + if (participant.trim() && !participants.includes(participant.trim())) { + setParticipants([...participants, participant.trim()]); + setParticipant(''); + } + }; + + const handleRemove = (name: string) => { + setParticipants(participants.filter(p => p !== name)); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleAdd(); + } + }; + + const handleAddBtnClick = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files.length > 0) { + const files = Array.from(e.target.files); + // 새로 선택한 파일들을 기존 대기열에 추가 + setPreviewFiles(prev => [...prev, ...files]); + // 미리보기 URL 생성 및 추가 + const newUrls = files.map(file => URL.createObjectURL(file)); + setPreviewUrls(prev => [...prev, ...newUrls]); + } + }; + + const handleRemovePreview = (idx: number) => { + setPreviewFiles(prev => prev.filter((_, i) => i !== idx)); + setPreviewUrls(prev => prev.filter((_, i) => i !== idx)); + }; + + const handlePayCheck = async () => { + if (previewFiles.length === 0) { + alert('영수증 이미지를 첨부해주세요.'); + return; + } + if (participants.length === 0) { + alert('참여자를 추가해주세요.'); + return; + } + setUploading(true); + try { + const formData = new FormData(); + previewFiles.forEach(file => formData.append('image', file)); + + // 이미지 업로드 POST 요청 + await axiosInstance.post('api/receipt/upload/', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + // 참여자 명단 개별 POST 요청 + // 참여자 명단 배열을 순회하며 각 이름에 대해 개별 POST 요청 전송 + for (const participantName of participants) { + // 각 참여자 이름을 JSON 객체 형태로 감싸서 전송 + const participantBody = { + name: participantName + }; + + // '/api/participant/' 엔드포인트로 JSON 형태의 본문 전송 + await axiosInstance.post('api/participant/', participantBody, { + headers: { + 'Content-Type': 'application/json', + } + }); + } + + // 업로드 성공 시 페이지 이동 + navigate('/check'); + } catch (error: any) { // 에러 타입 명시 + console.error('업로드 및 참여자 명단 전송 실패:', error); + alert('업로드 및 참여자 명단 전송에 실패했습니다.\n' + (error.response?.data?.detail || error.message)); // 상세 에러 메시지 포함 + } finally { + setUploading(false); + } + }; + + return ( +
+
+

영수증 이미지 첨부

+
+ + + 이미지를 첨부하세요 +
+ {/* 미리보기 썸네일 */} +
+ {previewUrls.map((url, idx) => ( +
+ {`미리보기 + +
+ ))} +
+
+
+

참여자 리스트

+
+ setParticipant(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="참여자 이름 입력" + className="w-[220px] h-[50px] rounded-[12px] bg-[#F5F5F5] px-4 text-[18px] font-['Inter'] outline-none min-w-0" + /> + +
+
+ {participants.map(name => ( +
+ {name} + +
+ ))} +
+
+
+

정산 방식 선택

+
+ + +
+
+ {/* 하단 PayCheck 버튼 */} +
+ +
+
+ ); +}; + +export default MainPage; \ No newline at end of file diff --git a/src/pages/NotFoundPage.tsx b/src/pages/NotFoundPage.tsx new file mode 100644 index 0000000..2493e7b --- /dev/null +++ b/src/pages/NotFoundPage.tsx @@ -0,0 +1,5 @@ +const NotFoundPage = () => { + return
Not Found Error!
+}; + +export default NotFoundPage; \ No newline at end of file diff --git a/src/types/auth.ts b/src/types/auth.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/types/common.ts b/src/types/common.ts new file mode 100644 index 0000000..76b2b14 --- /dev/null +++ b/src/types/common.ts @@ -0,0 +1,6 @@ +export type CommonResponse = { + status:boolean; + statusCode:number; + message:string; + data: T; +} \ No newline at end of file diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..08a5978 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,9 @@ /// +interface ImportMetaEnv { + readonly VITE_SERVER_API_URL: string + // 다른 환경 변수들에 대한 타입 정의... + } + +interface ImportMeta { + readonly env: ImportMetaEnv +} \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index c9ccbd4..9c8a3c4 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -19,7 +19,6 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true },