diff --git a/backend/src/main/java/mouda/backend/moim/controller/MoimController.java b/backend/src/main/java/mouda/backend/moim/controller/MoimController.java index 6ff48247f..315e6cc86 100644 --- a/backend/src/main/java/mouda/backend/moim/controller/MoimController.java +++ b/backend/src/main/java/mouda/backend/moim/controller/MoimController.java @@ -9,9 +9,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; import mouda.backend.common.RestResponse; import mouda.backend.moim.domain.Moim; @@ -24,52 +21,37 @@ @RestController @RequestMapping("/v1/moim") @RequiredArgsConstructor -public class MoimController { +public class MoimController implements MoimSwagger { private final MoimService moimService; - @Operation(summary = "모임 생성", description = "모임을 생성한다.") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "모임 생성 성공!"), - }) + @Override @PostMapping public ResponseEntity> createMoim(@RequestBody MoimCreateRequest moimCreateRequest) { Moim moim = moimService.createMoim(moimCreateRequest); return ResponseEntity.ok().body(new RestResponse<>(moim.getId())); } - @Operation(summary = "모임 전체 조회", description = "모든 모임을 조회한다.") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "모임 조회 성공!"), - }) + @Override @GetMapping public ResponseEntity> findAllMoim() { return ResponseEntity.ok().body(new RestResponse<>(moimService.findAllMoim())); } - @Operation(summary = "모임 상세 조회", description = "모임 상세 조회한다.") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "모임 상세 조회 성공!"), - }) + @Override @GetMapping("/{moimId}") public ResponseEntity> findMoimDetails(@PathVariable long moimId) { return ResponseEntity.ok().body(new RestResponse<>(moimService.findMoimDetails(moimId))); } - @Operation(summary = "모임 참여", description = "모임에 참여한다.") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "모임 참여 성공!") - }) + @Override @PostMapping("/join") public ResponseEntity> joinMoim(@RequestBody MoimJoinRequest moimJoinRequest) { moimService.joinMoim(moimJoinRequest); return ResponseEntity.ok().build(); } - @Operation(summary = "모임 삭제", description = "해당하는 id의 모임을 삭제한다.") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "모임 삭제 성공!"), - }) + @Override @DeleteMapping("/{moimId}") public void deleteMoim(@PathVariable long moimId) { moimService.deleteMoim(moimId); diff --git a/backend/src/main/java/mouda/backend/moim/controller/MoimSwagger.java b/backend/src/main/java/mouda/backend/moim/controller/MoimSwagger.java new file mode 100644 index 000000000..48994dab9 --- /dev/null +++ b/backend/src/main/java/mouda/backend/moim/controller/MoimSwagger.java @@ -0,0 +1,47 @@ +package mouda.backend.moim.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import mouda.backend.common.RestResponse; +import mouda.backend.moim.dto.request.MoimCreateRequest; +import mouda.backend.moim.dto.request.MoimJoinRequest; +import mouda.backend.moim.dto.response.MoimDetailsFindResponse; +import mouda.backend.moim.dto.response.MoimFindAllResponses; + +public interface MoimSwagger { + + @Operation(summary = "모임 생성", description = "모임을 생성한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "모임 생성 성공!"), + }) + ResponseEntity> createMoim(@RequestBody MoimCreateRequest moimCreateRequest); + + @Operation(summary = "모임 전체 조회", description = "모든 모임을 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "모임 조회 성공!"), + }) + ResponseEntity> findAllMoim(); + + @Operation(summary = "모임 상세 조회", description = "모임 상세 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "모임 상세 조회 성공!"), + }) + ResponseEntity> findMoimDetails(@PathVariable long moimId); + + @Operation(summary = "모임 참여", description = "모임에 참여한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "모임 참여 성공!") + }) + ResponseEntity> joinMoim(@RequestBody MoimJoinRequest moimJoinRequest); + + @Operation(summary = "모임 삭제", description = "해당하는 id의 모임을 삭제한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "모임 삭제 성공!"), + }) + void deleteMoim(@PathVariable long moimId); +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cbc8b78b9..ec951efdd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -32,6 +32,7 @@ "@storybook/react-webpack5": "^8.1.11", "@storybook/test": "^8.1.11", "@stylelint/postcss-css-in-js": "^0.38.0", + "@svgr/webpack": "^8.1.0", "@tanstack/react-query": "^5.51.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", @@ -1466,6 +1467,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.7.tgz", + "integrity": "sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-react-display-name": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", @@ -3941,6 +3957,327 @@ "postcss-syntax": ">=0.36.2" } }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, "node_modules/@swc/core": { "version": "1.5.7", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.5.7.tgz", @@ -4364,6 +4701,15 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -6116,6 +6462,18 @@ "tslib": "^2.0.3" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001642", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", @@ -6814,6 +7172,39 @@ "node": ">=4" } }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -13379,6 +13770,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -14217,12 +14618,123 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, "node_modules/svg-tags": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", "dev": true }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/svgo/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/swc-loader": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9343e232d..49bf31570 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,6 +33,7 @@ "@storybook/react-webpack5": "^8.1.11", "@storybook/test": "^8.1.11", "@stylelint/postcss-css-in-js": "^0.38.0", + "@svgr/webpack": "^8.1.0", "@tanstack/react-query": "^5.51.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0e38b3078..203cb410b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,37 +1,13 @@ import { Global } from '@emotion/react'; -import reset from './reset'; - -import { - QueryClient, - QueryClientProvider, - MutationCache, -} from '@tanstack/react-query'; -import { RouterProvider, createBrowserRouter } from 'react-router-dom'; - -import ENDPOINTS from './constants/endpoints'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { RouterProvider } from 'react-router-dom'; +import createQueryClient from './queryClient'; +import reset from './common/reset.style'; +import router from './router'; import { useMemo } from 'react'; -import MoimListPage from './pages/MoimList'; -import MoimRegisterPage from './pages/MoimRegister'; - -const router = createBrowserRouter([ - { - path: ENDPOINTS.main, - element: , - }, - { - path: ENDPOINTS.addMoim, - element: , - }, -]); export default function App() { - const queryClient = useMemo(() => { - return new QueryClient({ - mutationCache: new MutationCache({ - onError: (e: Error) => console.log(e), - }), - }); - }, []); + const queryClient = useMemo(createQueryClient, []); return ( <> diff --git a/frontend/src/apis/endPoints.ts b/frontend/src/apis/endPoints.ts new file mode 100644 index 000000000..5f65778f0 --- /dev/null +++ b/frontend/src/apis/endPoints.ts @@ -0,0 +1,11 @@ +import ENV from '@_apis/env'; + +const getEndpoint = (string: string) => { + return `${ENV.baseUrl}/${string}`; +}; + +const ENDPOINTS = { + moim: getEndpoint('v1/moim'), + moims: getEndpoint('v1/moim'), +}; +export default ENDPOINTS; diff --git a/frontend/src/apis/gets.ts b/frontend/src/apis/gets.ts index c005f4472..4b1d5af25 100644 --- a/frontend/src/apis/gets.ts +++ b/frontend/src/apis/gets.ts @@ -1,12 +1,9 @@ -import { GetMoim, MoimInfo } from '../types/requests'; - -import ENV from './env'; +import ENDPOINTS from '@_apis/endPoints'; +import { GetMoim } from '@_apis/responseTypes'; +import { MoimInfo } from '@_types/index'; export const getMoims = async (): Promise => { - const url = `${ENV.baseUrl}/${'v1/moim'}`; - - const headers = new Headers(); - headers.append('Content-Type', 'application/json'); + const url = ENDPOINTS.moims; const options = { method: 'GET', @@ -18,8 +15,10 @@ export const getMoims = async (): Promise => { const response = await fetch(url, options); const statusHead = Math.floor(response.status / 100); - if (statusHead === 4 || statusHead === 5) + if (statusHead === 4 || statusHead === 5) { throw new Error('모임을 받아오지 못했습니다.'); + } + const json = (await response.json()) as GetMoim; return json.data.moims; }; diff --git a/frontend/src/apis/posts.ts b/frontend/src/apis/posts.ts index a01ec5eeb..581a039a7 100644 --- a/frontend/src/apis/posts.ts +++ b/frontend/src/apis/posts.ts @@ -1,9 +1,9 @@ -import { MoimInfo, PostMoim } from '../types/requests'; - -import ENV from './env'; +import ENDPOINTS from '@_apis/endPoints'; +import { MoimInfo } from '@_types/index'; +import { PostMoim } from '@_apis/responseTypes'; export const postMoim = async (moim: MoimInfo): Promise => { - const url = `${ENV.baseUrl}/${'v1/moim'}`; + const url = ENDPOINTS.moim; const options = { method: 'POST', diff --git a/frontend/src/apis/responseTypes.ts b/frontend/src/apis/responseTypes.ts new file mode 100644 index 000000000..fad4ce3b3 --- /dev/null +++ b/frontend/src/apis/responseTypes.ts @@ -0,0 +1,9 @@ +import { MoimInfo } from '../types'; + +export interface GetMoim { + data: { moims: MoimInfo[] }; +} + +export interface PostMoim { + id: number; +} diff --git a/frontend/src/button.styles.ts b/frontend/src/button.styles.ts deleted file mode 100644 index 7d1523d70..000000000 --- a/frontend/src/button.styles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { css } from '@emotion/react'; - -export const container = css` - display: flex; - - padding: 32px; - - font-size: 24px; - - background-color: #000; - border-radius: 4px; -`; diff --git a/frontend/src/layouts/FormLayout/FormHeader/BackArrowLogo.tsx b/frontend/src/common/assets/back.svg similarity index 82% rename from frontend/src/layouts/FormLayout/FormHeader/BackArrowLogo.tsx rename to frontend/src/common/assets/back.svg index b73065a20..fd518f694 100644 --- a/frontend/src/layouts/FormLayout/FormHeader/BackArrowLogo.tsx +++ b/frontend/src/common/assets/back.svg @@ -1,5 +1,3 @@ -export default function BackArrowLogo() { - return ( - - ); -} + \ No newline at end of file diff --git a/frontend/src/common/reset.style.ts b/frontend/src/common/reset.style.ts index d8c92f0b2..f4ca53a74 100644 --- a/frontend/src/common/reset.style.ts +++ b/frontend/src/common/reset.style.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; + const reset = css` html, body, @@ -84,9 +85,10 @@ const reset = css` margin: 0; padding: 0; border: 0; - font-size: 100%; + font-size: 62.5%; font: inherit; vertical-align: baseline; + box-sizing: border-box; } /* HTML5 display-role reset for older browsers */ article, diff --git a/frontend/src/components/Button/Button.style.ts b/frontend/src/components/Button/Button.style.ts index 779aba40c..db4ab6617 100644 --- a/frontend/src/components/Button/Button.style.ts +++ b/frontend/src/components/Button/Button.style.ts @@ -1,7 +1,6 @@ import { css } from '@emotion/react'; const font = css` - border: none; color: #fff; font-family: Pretendard; font-size: 1rem; @@ -10,31 +9,37 @@ const font = css` line-height: normal; letter-spacing: -0.02rem; `; -export const shapes = { - default: css``, - circle: css` - ${font}; - flex-shrink: 0; - border-radius: 50%; - background: #ffffff; - &:active { - background-color: #e9ecef; - } - `, - bar: css` - ${font} - display: flex; - width: 100%; - height: 4rem; - padding: 1rem 3.6875rem; - justify-content: center; - align-items: center; - gap: 0.625rem; - flex-shrink: 0; - border-radius: 1.875rem; - background: #477bff; - &:active { - background-color: #005bb5; - } - `, +export const shapes = (shape: 'circle' | 'bar', disabled: boolean) => { + if (shape === 'circle') { + return css` + ${font}; + flex-shrink: 0; + border: none; + box-shadow: 0px 0px 3px #444; + border-radius: 50%; + background: ${disabled ? '#868e96' : '#ffffff'}; + &:active { + background-color: #868e96; + } + `; + } + if (shape === 'bar') { + return css` + ${font} + border: none; + display: flex; + width: 100%; + height: 4rem; + padding: 1rem 3.6875rem; + justify-content: center; + align-items: center; + gap: 0.625rem; + flex-shrink: 0; + border-radius: 1.875rem; + background: ${disabled ? '#868e96' : '#477bff'}; + &:active { + background-color: ${disabled ? '#868e96' : '#005bb5'}; + } + `; + } }; diff --git a/frontend/src/components/Button/Button.tsx b/frontend/src/components/Button/Button.tsx index 6453e7511..af638d8fb 100644 --- a/frontend/src/components/Button/Button.tsx +++ b/frontend/src/components/Button/Button.tsx @@ -1,10 +1,17 @@ -import type ButtonProps from './Button.type'; -import { shapes } from './Button.style'; +import { ReactNode } from 'react'; +import { shapes } from '@_components/Button/Button.style'; + +interface ButtonProps { + shape: 'circle' | 'bar'; + onClick: () => void; + disabled: boolean; + children: ReactNode; +} export default function Button(props: ButtonProps) { - const { shape, onClick, children } = props; + const { shape, onClick, disabled, children } = props; return ( - ); diff --git a/frontend/src/components/Button/Button.type.ts b/frontend/src/components/Button/Button.type.ts deleted file mode 100644 index 80a6f1080..000000000 --- a/frontend/src/components/Button/Button.type.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ReactNode } from 'react'; - -export default interface ButtonProps { - shape: 'circle' | 'bar'; - onClick: () => void; - children: ReactNode; -} diff --git a/frontend/src/components/Card/MoimCard.style.ts b/frontend/src/components/Card/MoimCard.style.ts index 51e754afc..8d829f867 100644 --- a/frontend/src/components/Card/MoimCard.style.ts +++ b/frontend/src/components/Card/MoimCard.style.ts @@ -1,5 +1,5 @@ +import { common } from '@_common/common.style'; import { css } from '@emotion/react'; -import { common } from '../../common/common.style'; export const cardBox = css` display: flex; diff --git a/frontend/src/components/Card/MoimCard.tsx b/frontend/src/components/Card/MoimCard.tsx index 9ea00cb0b..e22b14ec8 100644 --- a/frontend/src/components/Card/MoimCard.tsx +++ b/frontend/src/components/Card/MoimCard.tsx @@ -1,18 +1,28 @@ -import * as S from './MoimCard.style'; -import MoimCardProps from './MoimCard.type'; +import * as S from '@_components/Card/MoimCard.style'; + +import { + formatHhmmToKorean, + formatYyyymmddToKorean, +} from '../../utils/formatters'; + +import { MoimInfo } from '../../types'; + +interface MoimCardProps { + moimInfo: MoimInfo; +} export default function MoimCard(props: MoimCardProps) { const { - data: { title, date, time, place, maxPeople }, + moimInfo: { title, date, time, place, maxPeople }, } = props; + return (

{title}

날짜 및 시간 - {date} - {time} + {`${formatYyyymmddToKorean(date)} ${formatHhmmToKorean(time)}`}
@@ -21,7 +31,7 @@ export default function MoimCard(props: MoimCardProps) {
최대인원수 - {maxPeople} + {maxPeople}명
); diff --git a/frontend/src/components/Card/MoimCard.type.ts b/frontend/src/components/Card/MoimCard.type.ts deleted file mode 100644 index 95a5b9d38..000000000 --- a/frontend/src/components/Card/MoimCard.type.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { MoimInfo } from '../../types/requests'; - -export default interface MoimCardProps { - data: MoimInfo; -} diff --git a/frontend/src/components/Input/MoimInput.style.ts b/frontend/src/components/Input/MoimInput.style.ts index 43ec46794..0b31cb6ad 100644 --- a/frontend/src/components/Input/MoimInput.style.ts +++ b/frontend/src/components/Input/MoimInput.style.ts @@ -1,5 +1,5 @@ +import { common } from '@_common/common.style'; import { css } from '@emotion/react'; -import { common } from '../../common/common.style'; export const required = css` color: #f00; diff --git a/frontend/src/components/Input/MoimInput.tsx b/frontend/src/components/Input/MoimInput.tsx index d5b66ff06..0b9799285 100644 --- a/frontend/src/components/Input/MoimInput.tsx +++ b/frontend/src/components/Input/MoimInput.tsx @@ -1,12 +1,13 @@ -import MoimInputProps from './MoimInput.type'; -import * as S from './MoimInput.style'; +import * as S from '@_components/Input/MoimInput.style'; -export default function MoimInput(props: MoimInputProps) { - const { - name, - data: { title, type, placeholder, required }, - onChange, - } = props; +import { HTMLProps } from 'react'; + +export interface LabeledInputProps extends HTMLProps { + title: string; +} + +export default function LabeledInput(props: LabeledInputProps) { + const { name, title, type, placeholder, required, onChange } = props; return ( ); } diff --git a/frontend/src/components/Input/MoimInput.type.ts b/frontend/src/components/Input/MoimInput.type.ts deleted file mode 100644 index 41b9468fd..000000000 --- a/frontend/src/components/Input/MoimInput.type.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ChangeEvent } from 'react'; -import { InputInfoType } from '../../constants'; - -export default interface MoimInputProps { - name: string; - data: InputInfoType; - onChange: (e: ChangeEvent) => void; -} diff --git a/frontend/src/components/MoimCardList/MoimCardList.tsx b/frontend/src/components/MoimCardList/MoimCardList.tsx index 09d602d66..0a2246fff 100644 --- a/frontend/src/components/MoimCardList/MoimCardList.tsx +++ b/frontend/src/components/MoimCardList/MoimCardList.tsx @@ -1,13 +1,20 @@ -import MoimCard from '../Card/MoimCard'; -import * as S from './MoimCardList.style'; -import MoimCardListProps from './MoimCardList.type'; +import * as S from '@_components/MoimCardList/MoimCardList.style'; + +import MoimCard from '@_components/Card/MoimCard'; +import { MoimInfo } from '@_types/index'; + +interface MoimCardListProps { + moimInfos: MoimInfo[]; +} export default function MoimCardList(props: MoimCardListProps) { - const { data } = props; + const { moimInfos } = props; return (
- {data.map((moimInfo, index) => { - return ; + {moimInfos.map((moimInfo, index) => { + return ( + + ); })}
); diff --git a/frontend/src/components/MoimCardList/MoimCardList.type.ts b/frontend/src/components/MoimCardList/MoimCardList.type.ts deleted file mode 100644 index 8533587bc..000000000 --- a/frontend/src/components/MoimCardList/MoimCardList.type.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { MoimInfo } from '../../types/requests'; - -export default interface MoimCardListProps { - data: MoimInfo[]; -} diff --git a/frontend/src/constants/endpoints.ts b/frontend/src/constants/endpoints.ts deleted file mode 100644 index 39a0d593c..000000000 --- a/frontend/src/constants/endpoints.ts +++ /dev/null @@ -1,6 +0,0 @@ -const ENDPOINTS = { - main: '/', - addMoim: '/add-moim', -}; - -export default ENDPOINTS; diff --git a/frontend/src/constants/poclies.ts b/frontend/src/constants/poclies.ts new file mode 100644 index 000000000..75291008f --- /dev/null +++ b/frontend/src/constants/poclies.ts @@ -0,0 +1,18 @@ +const POLICES = { + minimumTitleLength: 1, + maximumTitleLength: 20, + + minimumPlaceLength: 1, + maximumPlaceLength: 100, + + minimumMaxPeople: 1, + maximumMaxPeople: 99, + + minimumAuthorNicknameLength: 1, + maximumAuthorNicknameLength: 10, + + yyyymmddDashRegex: /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/, + hhmmRegex: /^\d{2}:\d{2}$/, +}; + +export default POLICES; diff --git a/frontend/src/queryKeys.ts b/frontend/src/constants/queryKeys.ts similarity index 100% rename from frontend/src/queryKeys.ts rename to frontend/src/constants/queryKeys.ts diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts new file mode 100644 index 000000000..1a13fa56e --- /dev/null +++ b/frontend/src/constants/routes.ts @@ -0,0 +1,6 @@ +const ROUTES = { + main: '/', + addMoim: '/add-moim', +}; + +export default ROUTES; diff --git a/frontend/src/mutaions/useAddMoim.ts b/frontend/src/hooks/mutaions/useAddMoim.ts similarity index 79% rename from frontend/src/mutaions/useAddMoim.ts rename to frontend/src/hooks/mutaions/useAddMoim.ts index fc8323393..aebce267d 100644 --- a/frontend/src/mutaions/useAddMoim.ts +++ b/frontend/src/hooks/mutaions/useAddMoim.ts @@ -1,7 +1,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import QUERY_KEYS from '../queryKeys'; -import { postMoim } from '../apis/posts'; +import QUERY_KEYS from '@_constants/queryKeys'; +import { postMoim } from '@_apis/posts'; export default function useAddMoim(onSuccess: () => void) { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/queries/useMoims.ts b/frontend/src/hooks/queries/useMoims.ts new file mode 100644 index 000000000..0fe8af30d --- /dev/null +++ b/frontend/src/hooks/queries/useMoims.ts @@ -0,0 +1,24 @@ +import QUERY_KEYS from '@_constants/queryKeys'; +import { getMoims } from '@_apis/gets'; +import { useMemo } from 'react'; +import { useQuery } from '@tanstack/react-query'; + +export default function useMoims() { + const { data: moims, isLoading } = useQuery({ + queryKey: [QUERY_KEYS.moims], + queryFn: getMoims, + }); + + // TODO:서버에서 검증하면 없애야 함 + const filteredMoims = useMemo( + () => + moims?.filter((moim) => { + const nowDate = new Date(); + const nowDateYyyymmdd = `${nowDate.getFullYear()}-${(nowDate.getMonth() + 1).toString().padStart(2, '00')}-${nowDate.getDate().toString().padStart(2, '00')}`; + return moim.date >= nowDateYyyymmdd; + }), + [moims], + ); + + return { moims: filteredMoims, isLoading }; +} diff --git a/frontend/src/layouts/FormLayout/FormBottomButtonWrapper/FormBottomButtonWrapper.tsx b/frontend/src/layouts/FormLayout/FormBottomWrapper/FormBottomButtonWrapper.tsx similarity index 50% rename from frontend/src/layouts/FormLayout/FormBottomButtonWrapper/FormBottomButtonWrapper.tsx rename to frontend/src/layouts/FormLayout/FormBottomWrapper/FormBottomButtonWrapper.tsx index 592c6914e..e667f10aa 100644 --- a/frontend/src/layouts/FormLayout/FormBottomButtonWrapper/FormBottomButtonWrapper.tsx +++ b/frontend/src/layouts/FormLayout/FormBottomWrapper/FormBottomButtonWrapper.tsx @@ -1,7 +1,8 @@ +import * as S from './FormBottomWrapper.style'; + import { PropsWithChildren } from 'react'; -import * as S from './FormBottomButtonWrapper.style'; -export default function FormBottomButtonWrapper(props: PropsWithChildren) { +export default function FormBottomWrapper(props: PropsWithChildren) { const { children } = props; return
{children}
; diff --git a/frontend/src/layouts/FormLayout/FormBottomButtonWrapper/FormBottomButtonWrapper.style.ts b/frontend/src/layouts/FormLayout/FormBottomWrapper/FormBottomWrapper.style.ts similarity index 100% rename from frontend/src/layouts/FormLayout/FormBottomButtonWrapper/FormBottomButtonWrapper.style.ts rename to frontend/src/layouts/FormLayout/FormBottomWrapper/FormBottomWrapper.style.ts diff --git a/frontend/src/layouts/FormLayout/FormHeader/FormHeader.tsx b/frontend/src/layouts/FormLayout/FormHeader/FormHeader.tsx index c50d205b1..50bbc676b 100644 --- a/frontend/src/layouts/FormLayout/FormHeader/FormHeader.tsx +++ b/frontend/src/layouts/FormLayout/FormHeader/FormHeader.tsx @@ -1,7 +1,8 @@ -import { ReactNode } from 'react'; -import BackArrowLogo from './BackArrowLogo'; import * as S from './FormHeader.style'; +import BackLogo from '../../../common/assets/back.svg'; +import { ReactNode } from 'react'; + interface FormHeaderProps { onBackArrowClick: () => void; children: ReactNode; @@ -14,7 +15,7 @@ export default function FormHeader(props: FormHeaderProps) { // TODO: '모임등록하기'가 정가운데에 오지 않음
- + {children} diff --git a/frontend/src/layouts/FormLayout/FormLayout.tsx b/frontend/src/layouts/FormLayout/FormLayout.tsx index 1ccaeb827..53ef99e48 100644 --- a/frontend/src/layouts/FormLayout/FormLayout.tsx +++ b/frontend/src/layouts/FormLayout/FormLayout.tsx @@ -1,8 +1,9 @@ -import { PropsWithChildren } from 'react'; import * as S from './FormLayout.style'; + +import FormBottomWrapper from './FormBottomWrapper/FormBottomButtonWrapper'; import FormHeader from './FormHeader/FormHeader'; import FormMain from './FormMain/FormMain'; -import FormBottomButtonWrapper from './FormBottomButtonWrapper/FormBottomButtonWrapper'; +import { PropsWithChildren } from 'react'; function FormLayout(props: PropsWithChildren) { const { children } = props; @@ -12,6 +13,6 @@ function FormLayout(props: PropsWithChildren) { FormLayout.Header = FormHeader; FormLayout.MainForm = FormMain; -FormLayout.BottomButtonWrapper = FormBottomButtonWrapper; +FormLayout.BottomButtonWrapper = FormBottomWrapper; export default FormLayout; diff --git a/frontend/src/layouts/HomeLayout.tsx/HomeLayout.tsx b/frontend/src/layouts/HomeLayout.tsx/HomeLayout.tsx index 0949622f9..6075674d0 100644 --- a/frontend/src/layouts/HomeLayout.tsx/HomeLayout.tsx +++ b/frontend/src/layouts/HomeLayout.tsx/HomeLayout.tsx @@ -1,8 +1,9 @@ -import { PropsWithChildren } from 'react'; import * as S from './MoimList.style'; + +import HomeFixedButtonWrapper from './HomeFixedButtonWrapper/HomeFixedButtonWrapper'; import HomeHeader from './HomeHeader/HomeHeader'; import HomeMain from './HomeMain/HomeMain'; -import HomeFixedButtonWrapper from './HomeFixedButtonWrapper/HomeFixedButtonWrapper'; +import { PropsWithChildren } from 'react'; function HomeLayout(props: PropsWithChildren) { const { children } = props; diff --git a/frontend/src/layouts/HomeLayout.tsx/HomeMain/HomeMain.style.ts b/frontend/src/layouts/HomeLayout.tsx/HomeMain/HomeMain.style.ts index dbd0ff6de..2e3821d21 100644 --- a/frontend/src/layouts/HomeLayout.tsx/HomeMain/HomeMain.style.ts +++ b/frontend/src/layouts/HomeLayout.tsx/HomeMain/HomeMain.style.ts @@ -12,9 +12,10 @@ export const navStyle = css` gap: 12px; `; +// TODO: 수야 때리기 && 미사용 네비게이션 하단 바 없애기 export const navItemStyle = (isTurnedOn: boolean) => { return css` - color: ${isTurnedOn ? 'rgba(71, 123, 255, 1)' : '#ededed'}; + color: ${isTurnedOn ? 'rgba(71, 123, 255, 1)' : '#868e96'}; font-weight: 600; font-size: 18px; line-height: 22.88px; diff --git a/frontend/src/layouts/HomeLayout.tsx/mockData.ts b/frontend/src/layouts/HomeLayout.tsx/mockData.ts deleted file mode 100644 index eb4b1a1eb..000000000 --- a/frontend/src/layouts/HomeLayout.tsx/mockData.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { MoimInfo } from '../../type'; - -export const meetings: MoimInfo[] = [ - { - id: 1, - title: '축구 하실 사람?', - date: '7월 15일 오후 2시', - time: '오후 2시', - place: '서울 마포구 독막로 96 1층', - maxPeople: 4, - }, - { - id: 2, - title: '축구 하실 사람?', - date: '7월 15일 오후 2시', - time: '오후 2시', - place: '서울 마포구 독막로 96 1층', - maxPeople: 4, - }, - { - id: 3, - title: '축구 하실 사람?', - date: '7월 15일 오후 2시', - time: '오후 2시', - - place: '서울 마포구 독막로 96 1층', - maxPeople: 4, - }, - { - id: 4, - title: '축구 하실 사람?', - date: '7월 15일 오후 2시', - time: '오후 2시', - - place: '서울 마포구 독막로 96 1층', - maxPeople: 4, - }, - { - id: 5, - title: '축구 하실 사람?', - date: '7월 15일 오후 2시', - time: '오후 2시', - - place: '서울 마포구 독막로 96 1층', - maxPeople: 4, - }, - { - id: 6, - title: '축구 하실 사람?', - date: '7월 15일 오후 2시', - time: '오후 2시', - - place: '서울 마포구 독막로 96 1층', - maxPeople: 4, - }, - { - id: 7, - title: '축구 하실 사람?', - date: '7월 15일 오후 2시', - time: '오후 2시', - - place: '서울 마포구 독막로 96 1층', - maxPeople: 4, - }, -]; diff --git a/frontend/src/pages/MainPage/MainPage.tsx b/frontend/src/pages/MainPage/MainPage.tsx new file mode 100644 index 000000000..9de6eec09 --- /dev/null +++ b/frontend/src/pages/MainPage/MainPage.tsx @@ -0,0 +1,31 @@ +import Button from '@_components/Button/Button'; +import HomeLayout from '@_layouts/HomeLayout.tsx/HomeLayout'; +import MoimCardList from '@_components/MoimCardList/MoimCardList'; +import Plus from '@_common/assets/tabler_plus.svg'; +import ROUTES from '@_constants/routes'; +import useMoims from '@_hooks/queries/useMoims'; +import { useNavigate } from 'react-router-dom'; + +export default function MainPage() { + const navigate = useNavigate(); + const { moims } = useMoims(); + + return ( + + 우아한테크코스 + + {moims && } + + + + + + + ); +} diff --git a/frontend/src/pages/MoimCreationPage/MoimCreatePage.hook.ts b/frontend/src/pages/MoimCreationPage/MoimCreatePage.hook.ts new file mode 100644 index 000000000..299091e04 --- /dev/null +++ b/frontend/src/pages/MoimCreationPage/MoimCreatePage.hook.ts @@ -0,0 +1,46 @@ +import { ChangeEvent, useState } from 'react'; +import { + validateAuthorNickname, + validateDate, + validateMaxPeople, + validatePlace, + validateTime, + validateTitle, +} from './MoimCreatePage.util'; + +import { MoimInfo } from '../../types'; + +const useMoimInfoInput = () => { + const [inputData, setInputData] = useState({ + title: '', + date: '', + time: '', + place: '', + maxPeople: 0, + authorNickname: '', + description: '', + }); + + const handleChange = (e: ChangeEvent) => { + setInputData({ + ...inputData, + [e.target.name]: e.target.value, + }); + }; + + const isValidMoimInfoInput = + validateTitle(inputData.title) && + validateDate(inputData.date) && + validateTime(inputData.time) && + validatePlace(inputData.place) && + validateMaxPeople(inputData.maxPeople) && + validateAuthorNickname(inputData.authorNickname); + + return { + inputData, + handleChange, + isValidMoimInfoInput, + }; +}; + +export default useMoimInfoInput; diff --git a/frontend/src/pages/MoimCreationPage/MoimCreatePage.util.ts b/frontend/src/pages/MoimCreationPage/MoimCreatePage.util.ts new file mode 100644 index 000000000..f1f078ba0 --- /dev/null +++ b/frontend/src/pages/MoimCreationPage/MoimCreatePage.util.ts @@ -0,0 +1,27 @@ +// validation.js + +import POLICIES from '@_constants/poclies'; + +export const validateTitle = (title: string) => + POLICIES.minimumTitleLength <= title.length && + title.length <= POLICIES.maximumTitleLength; + +export const validateDate = (date: string) => { + const nowDate = new Date(); + const nowDateYyyymmdd = `${nowDate.getFullYear()}-${(nowDate.getMonth() + 1).toString().padStart(2, '00')}-${nowDate.getDate().toString().padStart(2, '00')}`; + return date >= nowDateYyyymmdd && POLICIES.yyyymmddDashRegex.test(date); +}; + +export const validateTime = (time: string) => POLICIES.hhmmRegex.test(time); + +export const validatePlace = (place: string) => + POLICIES.minimumPlaceLength <= place.length && + place.length <= POLICIES.maximumPlaceLength; + +export const validateMaxPeople = (maxPeople: number) => + POLICIES.minimumMaxPeople <= maxPeople && + maxPeople <= POLICIES.maximumMaxPeople; + +export const validateAuthorNickname = (authorNickname: string) => + POLICIES.minimumAuthorNicknameLength <= authorNickname.length && + authorNickname.length <= POLICIES.maximumAuthorNicknameLength; diff --git a/frontend/src/constants/index.ts b/frontend/src/pages/MoimCreationPage/MoimCreationPage.constant.ts similarity index 67% rename from frontend/src/constants/index.ts rename to frontend/src/pages/MoimCreationPage/MoimCreationPage.constant.ts index b477a6934..346826bd2 100644 --- a/frontend/src/constants/index.ts +++ b/frontend/src/pages/MoimCreationPage/MoimCreationPage.constant.ts @@ -1,52 +1,55 @@ -type InputType = 'text' | 'textarea' | 'time' | 'date' | 'number'; -export interface InputInfoType { - title: string; - type: InputType; - placeholder: string; - required: boolean; -} +import { LabeledInputProps } from '@_components/Input/MoimInput'; -export const Input_Info_List: Record = { - title: { +const MOIM_INPUT_INFOS: LabeledInputProps[] = [ + { + name: 'title', title: '제목', type: 'text', placeholder: '없음', required: true, }, - date: { + { + name: 'date', title: '날짜', type: 'date', placeholder: '없음', required: true, }, - time: { + { + name: 'time', title: '시간', type: 'time', placeholder: '없음', required: true, }, - place: { + { + name: 'place', title: '장소', type: 'text', placeholder: '없음', required: true, }, - maxPeople: { + { + name: 'maxPeople', title: '최대인원수', type: 'number', placeholder: '0명', required: true, }, - authorNickname: { + { + name: 'authorNickname', title: '작성자 닉네임', type: 'text', placeholder: '알았다 안나야~', required: true, }, - description: { + { + name: 'description', title: '상세 내용 작성', type: 'textarea', placeholder: '어떤 모임인지 작성해주세요!', required: false, }, -}; +] as const; + +export default MOIM_INPUT_INFOS; diff --git a/frontend/src/pages/MoimCreationPage/MoimCreationPage.tsx b/frontend/src/pages/MoimCreationPage/MoimCreationPage.tsx new file mode 100644 index 000000000..81f5582e0 --- /dev/null +++ b/frontend/src/pages/MoimCreationPage/MoimCreationPage.tsx @@ -0,0 +1,50 @@ +import Button from '@_components/Button/Button'; +import FormLayout from '@_layouts/FormLayout/FormLayout'; +import LabeledInput from '@_components/Input/MoimInput'; +import MOIM_INPUT_INFOS from './MoimCreationPage.constant'; +import ROUTES from '@_constants/routes'; +import useAddMoim from '@_hooks/mutaions/useAddMoim'; +import useMoimInfoInput from './MoimCreatePage.hook'; +import { useNavigate } from 'react-router-dom'; +import { useState } from 'react'; + +export default function MoimCreationPage() { + const navigate = useNavigate(); + const { mutate } = useAddMoim(() => navigate(ROUTES.main)); + const [isSubmitted, setIsSubmitted] = useState(false); + + const { inputData, handleChange, isValidMoimInfoInput } = useMoimInfoInput(); + + const handleRegisterButtonClick = async () => { + if (!isValidMoimInfoInput) { + return; + } + if (isSubmitted) return; + setIsSubmitted(true); + mutate(inputData); + }; + + return ( + + navigate(ROUTES.main)}> + 모임등록하기 + + + + {MOIM_INPUT_INFOS.map((info) => ( + + ))} + + + + + + + ); +} diff --git a/frontend/src/pages/MoimList.tsx b/frontend/src/pages/MoimList.tsx deleted file mode 100644 index ea68b3eb8..000000000 --- a/frontend/src/pages/MoimList.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import HomeLayout from '../layouts/HomeLayout.tsx/HomeLayout'; -import MoimCardList from '../components/MoimCardList/MoimCardList'; -import Button from '../components/Button/Button'; -import Plus from '../common/assets/tabler_plus.svg'; -import { useNavigate } from 'react-router-dom'; -import ENDPOINTS from '../constants/endpoints'; -import useMoims from '../queries/useMoims'; - -export default function MoimListPage() { - const navigate = useNavigate(); - const { moims } = useMoims(); - return ( - - 우아한테크코스 - - {moims && } - - - - - - - ); -} diff --git a/frontend/src/pages/MoimRegister.tsx b/frontend/src/pages/MoimRegister.tsx deleted file mode 100644 index d9fa3a63f..000000000 --- a/frontend/src/pages/MoimRegister.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useNavigate } from 'react-router-dom'; -import Button from '../components/Button/Button'; -import MoimInput from '../components/Input/MoimInput'; -import { Input_Info_List } from '../constants'; -import FormLayout from '../layouts/FormLayout/FormLayout'; -import ENDPOINTS from '../constants/endpoints'; -import { ChangeEvent, useState } from 'react'; -import { MoimInfo } from '../types/requests'; -import useAddMoim from '../mutaions/useAddMoim'; - -export default function MoimRegisterPage() { - const navigate = useNavigate(); - const { mutate } = useAddMoim(() => navigate(ENDPOINTS.main)); - - const [inputData, setInputData] = useState({ - title: '', - date: '', - time: '', - place: '', - maxPeople: 0, - authorNickname: '', - description: '', - }); - - const handleChange = (e: ChangeEvent) => { - setInputData({ - ...inputData, - [e.target.name]: e.target.value, - }); - }; - - const handleRegisterButtonClick = async () => { - if ( - inputData.title === '' || - inputData.date === '' || - inputData.time === '' || - inputData.place === '' || - inputData.maxPeople <= 0 || - inputData.authorNickname === '' - ) { - return; - } - - mutate(inputData); - }; - - return ( - - navigate(ENDPOINTS.main)}> - 모임등록하기 - - - - {Object.entries(Input_Info_List).map(([key, info]) => ( - - ))} - - - - - - - ); -} diff --git a/frontend/src/pages/TmpAddMoim.tsx b/frontend/src/pages/TmpAddMoim.tsx deleted file mode 100644 index c417d85c7..000000000 --- a/frontend/src/pages/TmpAddMoim.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import ENDPOINTS from '../constants/endpoints'; -import { useNavigate } from 'react-router-dom'; - -export default function TmpAddMoim() { - const navigate = useNavigate(); - return ( - <> - - - ); -} diff --git a/frontend/src/pages/TmpMain.tsx b/frontend/src/pages/TmpMain.tsx deleted file mode 100644 index e467b48c9..000000000 --- a/frontend/src/pages/TmpMain.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import ENDPOINTS from '../constants/endpoints'; -import { useNavigate } from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; - -export default function TmpMain() { - const navigate = useNavigate(); - const { data } = useQuery({ - queryKey: ['aaa'], - queryFn: async () => { - const response = await fetch( - 'https://jsonplaceholder.typicode.com/posts/1', - ); - return await response.json(); - }, - }); - - console.log(data); - return ( - <> - - - ); -} diff --git a/frontend/src/queries/useMoims.ts b/frontend/src/queries/useMoims.ts deleted file mode 100644 index 8c9398994..000000000 --- a/frontend/src/queries/useMoims.ts +++ /dev/null @@ -1,12 +0,0 @@ -import QUERY_KEYS from '../queryKeys'; -import { getMoims } from '../apis/gets'; -import { useQuery } from '@tanstack/react-query'; - -export default function useMoims() { - const { data: moims, isLoading } = useQuery({ - queryKey: [QUERY_KEYS.moims], - queryFn: getMoims, - }); - - return { moims, isLoading }; -} diff --git a/frontend/src/queryClient.ts b/frontend/src/queryClient.ts new file mode 100644 index 000000000..2e5dae860 --- /dev/null +++ b/frontend/src/queryClient.ts @@ -0,0 +1,17 @@ +import { QueryCache, QueryClient } from '@tanstack/react-query'; + +const createQueryClient = () => { + return new QueryClient({ + defaultOptions: { + queries: { + networkMode: 'always', + }, + }, + queryCache: new QueryCache({ + onError: (error) => { + alert(error); + }, + }), + }); +}; +export default createQueryClient; diff --git a/frontend/src/reset.ts b/frontend/src/reset.ts deleted file mode 100644 index f4ca53a74..000000000 --- a/frontend/src/reset.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { css } from '@emotion/react'; - -const reset = css` - html, - body, - div, - span, - applet, - object, - iframe, - h1, - h2, - h3, - h4, - h5, - h6, - p, - blockquote, - pre, - a, - abbr, - acronym, - address, - big, - cite, - code, - del, - dfn, - em, - img, - ins, - kbd, - q, - s, - samp, - small, - strike, - strong, - sub, - sup, - tt, - var, - b, - u, - i, - center, - dl, - dt, - dd, - ol, - ul, - li, - fieldset, - form, - label, - legend, - table, - caption, - tbody, - tfoot, - thead, - tr, - th, - td, - article, - aside, - canvas, - details, - embed, - figure, - figcaption, - footer, - header, - hgroup, - menu, - nav, - output, - ruby, - section, - summary, - time, - mark, - audio, - video { - margin: 0; - padding: 0; - border: 0; - font-size: 62.5%; - font: inherit; - vertical-align: baseline; - box-sizing: border-box; - } - /* HTML5 display-role reset for older browsers */ - article, - aside, - details, - figcaption, - figure, - footer, - header, - hgroup, - menu, - nav, - section { - display: block; - } - body { - line-height: 1; - } - ol, - ul { - list-style: none; - } - blockquote, - q { - quotes: none; - } - blockquote:before, - blockquote:after, - q:before, - q:after { - content: ''; - content: none; - } - table { - border-collapse: collapse; - border-spacing: 0; - } -`; -export default reset; diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx new file mode 100644 index 000000000..5a2d7014e --- /dev/null +++ b/frontend/src/router.tsx @@ -0,0 +1,17 @@ +import MainPage from '@_pages/MainPage/MainPage'; +import MoimCreationPage from '@_pages/MoimCreationPage/MoimCreationPage'; +import ROUTES from '@_constants/routes'; +import { createBrowserRouter } from 'react-router-dom'; + +const router = createBrowserRouter([ + { + path: ROUTES.main, + element: , + }, + { + path: ROUTES.addMoim, + element: , + }, +]); + +export default router; diff --git a/frontend/src/type/index.ts b/frontend/src/types/index.d.ts similarity index 77% rename from frontend/src/type/index.ts rename to frontend/src/types/index.d.ts index aa6edbe45..b267d3f6a 100644 --- a/frontend/src/type/index.ts +++ b/frontend/src/types/index.d.ts @@ -1,10 +1,9 @@ export interface MoimInfo { - id: number; title: string; date: string; time: string; place: string; maxPeople: number; - authorNickname?: string; + authorNickname: string; description?: string; } diff --git a/frontend/src/types/requests.d.ts b/frontend/src/types/requests.d.ts deleted file mode 100644 index 3064b15b7..000000000 --- a/frontend/src/types/requests.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface MoimInfo { - title: string; - date: string; - time: string; - place: string; - maxPeople: number; - authorNickname: string; - description?: string; -} - -export interface GetMoim { - data: { moims: MoimInfo[] }; -} - -export interface PostMoim { - id: number; -} diff --git a/frontend/src/utils/formatters.ts b/frontend/src/utils/formatters.ts new file mode 100644 index 000000000..ab370161b --- /dev/null +++ b/frontend/src/utils/formatters.ts @@ -0,0 +1,38 @@ +export const formatYyyymmddToKorean = ( + yyyymmdd: string, + seperator: string = '-', +) => { + const yyyymmddArray = yyyymmdd.split(seperator).map(Number); + if (yyyymmddArray.length !== 3) { + throw new Error('올바른 날짜 형식이 아닙니다'); + } + if (yyyymmddArray.some((el) => Number.isNaN(el))) { + throw new Error('올바른 날짜 형식이 아닙니다'); + } + + const [year, month, date] = yyyymmddArray; + const nowYear = new Date().getFullYear(); + const result = `${month}월 ${date}일`; + if (year !== nowYear) { + return `${year}년 ${result}`; + } + return result; +}; + +export const formatHhmmToKorean = (hhmm: string, seperator: string = ':') => { + const hhmmArray = hhmm.split(seperator).map(Number); + if (hhmmArray.length < 2) { + throw new Error('올바른 시간 형식이 아닙니다'); + } + if (hhmmArray.some((el) => Number.isNaN(el))) { + throw new Error('올바른 시간 형식이 아닙니다'); + } + + const [hour, minute] = hhmmArray; + + if (minute === 0) { + return `${hour}시`; + } + + return `${hour}시 ${minute}분`; +}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 1811fd14e..88ad1d197 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -17,18 +17,22 @@ "noUnusedLocals": true, "noUnusedParameters": true, - "jsxImportSource": "@emotion/react" + "jsxImportSource": "@emotion/react", - // @components 같이 절대경로 설정 - // "baseUrl": "./", - // "paths": { - // "@components/*": [ - // "src/components/*" - // ], - // "@utils/*": [ - // "src/utils/*" - // ], + "baseUrl": "./", + "paths": { + "@_apis/*": ["src/apis/*"], + "@_constants/*": ["src/constants/*"], + "@_common/*": ["src/common/*"], + "@_components/*": ["src/components/*"], + "@_hooks/*": ["src/hooks/*"], + "@_layouts/*": ["src/layouts/*"], + "@_pages/*": ["src/pages/*"], + "@_types/*": ["src/types/*"], + "@_utils/*": ["src/utils/*"] + } }, + "exclude": ["node_modules"], "include": ["./src/**/*.tsx", "./src/**/*.ts"] } diff --git a/frontend/webpack.common.js b/frontend/webpack.common.js index 624ae0634..8562b11d9 100644 --- a/frontend/webpack.common.js +++ b/frontend/webpack.common.js @@ -17,11 +17,26 @@ module.exports = { ], resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js'], + alias: { + '@_apis': path.resolve(__dirname, 'src/apis'), + '@_constants': path.resolve(__dirname, 'src/constants'), + '@_common': path.resolve(__dirname, 'src/common'), + '@_components': path.resolve(__dirname, 'src/components'), + '@_hooks': path.resolve(__dirname, 'src/hooks'), + '@_layouts': path.resolve(__dirname, 'src/layouts'), + '@_pages': path.resolve(__dirname, 'src/pages'), + '@_types': path.resolve(__dirname, 'src/types'), + '@_utils': path.resolve(__dirname, 'src/utils'), + }, }, module: { rules: [ { - test: /\.(png|jpe?g|gif|svg|webp)$/i, + test: /\.svg$/i, + use: ['@svgr/webpack'], + }, + { + test: /\.(png|jpe?g|gif|webp)$/i, type: 'asset/resource', }, ], diff --git a/frontend/webpack.prod.js b/frontend/webpack.prod.js index 0f315ac6b..b01235705 100644 --- a/frontend/webpack.prod.js +++ b/frontend/webpack.prod.js @@ -9,10 +9,7 @@ module.exports = merge(common, { { test: /\.(ts|tsx)$/, exclude: /node_modules/, - use: [ - 'babel-loader', - 'ts-loader', - ], + use: ['babel-loader', 'ts-loader'], }, ], },