frontend: add community
Change-Id: I929c21d82ddab39d8b210b229ff7559320c1d853
diff --git a/next.config.ts b/next.config.ts
index e9ffa30..0b3cd6b 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -2,6 +2,9 @@
const nextConfig: NextConfig = {
/* config options here */
+ env: {
+ NEXT_PUBLIC_NGINX_URL: "http://localhost:65/",
+ },
};
export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
index 353dc4b..6e3de0c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,20 +8,37 @@
"name": "frontend",
"version": "0.1.0",
"dependencies": {
+ "@icon-park/react": "^1.4.2",
+ "@react-icons/all-files": "^4.1.0",
+ "axios": "^1.9.0",
+ "lodash": "^4.17.21",
"next": "15.2.4",
+ "primeicons": "^7.0.0",
+ "primereact": "^10.9.5",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "react-router-dom": "^7.6.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
+ "@types/lodash": "^4.17.17",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.2.4",
+ "sass": "^1.89.0",
"typescript": "^5"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.1.tgz",
+ "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@emnapi/core": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.4.0.tgz",
@@ -241,6 +258,19 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@icon-park/react": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmmirror.com/@icon-park/react/-/react-1.4.2.tgz",
+ "integrity": "sha512-+MtQLjNiRuia3fC/NfpSCTIy5KH5b+NkMB9zYd7p3R4aAIK61AjK0OSraaICJdkKooU9jpzk8m0fY4g9A3JqhQ==",
+ "engines": {
+ "node": ">= 8.0.0",
+ "npm": ">= 5.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9",
+ "react-dom": ">=16.9"
+ }
+ },
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
@@ -773,6 +803,323 @@
"node": ">=12.4.0"
}
},
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz",
+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-freebsd-x64": "2.5.1",
+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-win32-ia32": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+ "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+ "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+ "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+ "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+ "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+ "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+ "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+ "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+ "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+ "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+ "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+ "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher/node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/@react-icons/all-files": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/@react-icons/all-files/-/all-files-4.1.0.tgz",
+ "integrity": "sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -826,6 +1173,12 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.17",
+ "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.17.tgz",
+ "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==",
+ "dev": true
+ },
"node_modules/@types/node": {
"version": "20.17.29",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.17.29.tgz",
@@ -839,7 +1192,6 @@
"version": "19.0.12",
"resolved": "https://registry.npmmirror.com/@types/react/-/react-19.0.12.tgz",
"integrity": "sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==",
- "dev": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -853,6 +1205,14 @@
"@types/react": "^19.0.0"
}
},
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmmirror.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.29.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz",
@@ -1507,6 +1867,11 @@
"node": ">= 0.4"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -1531,6 +1896,16 @@
"node": ">=4"
}
},
+ "node_modules/axios": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmmirror.com/axios/-/axios-1.9.0.tgz",
+ "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -1601,7 +1976,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
@@ -1670,6 +2044,21 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "devOptional": true,
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz",
@@ -1716,12 +2105,31 @@
"simple-swizzle": "^0.2.2"
}
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -1739,8 +2147,7 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -1856,6 +2263,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.3.tgz",
@@ -1877,11 +2292,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
@@ -1966,7 +2389,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -1975,7 +2397,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -2011,7 +2432,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
"dependencies": {
"es-errors": "^1.3.0"
},
@@ -2023,7 +2443,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
@@ -2593,6 +3012,25 @@
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz",
@@ -2608,11 +3046,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -2650,7 +3101,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
@@ -2674,7 +3124,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
@@ -2756,7 +3205,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -2822,7 +3270,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -2834,7 +3281,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
"dependencies": {
"has-symbols": "^1.0.3"
},
@@ -2849,7 +3295,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -2866,6 +3311,12 @@
"node": ">= 4"
}
},
+ "node_modules/immutable": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.2.tgz",
+ "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==",
+ "devOptional": true
+ },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -3306,8 +3757,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.0",
@@ -3421,6 +3871,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3431,7 +3886,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@@ -3443,7 +3897,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
"engines": {
"node": ">= 0.4"
}
@@ -3470,6 +3923,25 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
@@ -3573,11 +4045,17 @@
}
}
},
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "dev": true,
+ "optional": true
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3850,17 +4328,48 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/primeicons": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/primeicons/-/primeicons-7.0.0.tgz",
+ "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="
+ },
+ "node_modules/primereact": {
+ "version": "10.9.5",
+ "resolved": "https://registry.npmmirror.com/primereact/-/primereact-10.9.5.tgz",
+ "integrity": "sha512-4O6gm0LrKF7Ml8zQmb8mGiWS/ugJ94KBOAS/CAxWFQh9qyNgfNw/qcpCeomPIkjWd98jrM2XDiEbgq+W0395Hw==",
+ "dependencies": {
+ "@types/react-transition-group": "^4.4.1",
+ "react-transition-group": "^4.4.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
@@ -3912,8 +4421,71 @@
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/react-router": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.6.0.tgz",
+ "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.6.0.tgz",
+ "integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
+ "dependencies": {
+ "react-router": "7.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "devOptional": true,
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
@@ -4080,6 +4652,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/sass": {
+ "version": "1.89.0",
+ "resolved": "https://registry.npmmirror.com/sass/-/sass-1.89.0.tgz",
+ "integrity": "sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==",
+ "devOptional": true,
+ "dependencies": {
+ "chokidar": "^4.0.0",
+ "immutable": "^5.0.2",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher": "^2.4.1"
+ }
+ },
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.26.0.tgz",
@@ -4097,6 +4689,11 @@
"node": ">=10"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
diff --git a/package.json b/package.json
index 8cd2132..c61cf2d 100644
--- a/package.json
+++ b/package.json
@@ -9,17 +9,26 @@
"lint": "next lint"
},
"dependencies": {
+ "@icon-park/react": "^1.4.2",
+ "@react-icons/all-files": "^4.1.0",
+ "axios": "^1.9.0",
+ "lodash": "^4.17.21",
+ "next": "15.2.4",
+ "primeicons": "^7.0.0",
+ "primereact": "^10.9.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "next": "15.2.4"
+ "react-router-dom": "^7.6.0"
},
"devDependencies": {
- "typescript": "^5",
+ "@eslint/eslintrc": "^3",
+ "@types/lodash": "^4.17.17",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.2.4",
- "@eslint/eslintrc": "^3"
+ "sass": "^1.89.0",
+ "typescript": "^5"
}
}
diff --git a/public/images/1.svg b/public/images/1.svg
new file mode 100644
index 0000000..d2175eb
--- /dev/null
+++ b/public/images/1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="81.478515625" height="65" viewBox="0 0 81.478515625 65" fill="none">
+<path fill="#FFE000" d="M2.43 20.14L11.26 56.93C11.6916 58.7285 13.3004 60 15.15 60L54.85 60C56.6996 60 58.3084 58.7285 58.74 56.93L67.47 20.55C68.1804 17.5899 64.54 15.5752 62.41 17.75L52.38 28C51.4187 28.9815 49.7745 28.7188 49.16 27.49L38.99 7.15C37.5159 4.20193 33.3141 4.20193 31.84 7.15L21.64 27.54C21.0333 28.7533 19.419 29.0293 18.45 28.08L7.45 17.3C5.29872 15.1923 1.72724 17.2115 2.43 20.14">
+</path>
+<path d="M35.0005 60L54.8505 60C56.7001 60 58.3089 58.7286 58.7405 56.93L67.4705 20.5501C68.1808 17.5899 64.5405 15.5752 62.4105 17.7501L52.3805 28.0001C51.4192 28.9816 49.7749 28.7189 49.1605 27.4901L38.9905 7.15006C38.1815 5.5321 36.5509 4.80211 35.0005 4.96009L35.0005 60Z" fill-rule="evenodd" fill="#FFD600" >
+</path>
+<g >
+<path fill="#FFFFFF" d="M28.72 52.12L42.192 52.12L42.192 49.08L37.616 49.08L37.616 28.536L34.832 28.536C33.456 29.4 31.888 29.976 29.68 30.36L29.68 32.696L33.904 32.696L33.904 49.08L28.72 49.08L28.72 52.12Z">
+</path>
+</g>
+</svg>
diff --git a/public/images/2.svg b/public/images/2.svg
new file mode 100644
index 0000000..908b2be
--- /dev/null
+++ b/public/images/2.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="81.478515625" height="67.58984375" viewBox="0 0 81.478515625 67.58984375" fill="none">
+<path fill="#BFC8D2" d="M2.43 20.14L11.26 56.93C11.6916 58.7285 13.3004 60 15.15 60L54.85 60C56.6996 60 58.3084 58.7285 58.74 56.93L67.47 20.55C68.1803 17.5899 64.54 15.5752 62.41 17.75L52.38 28C51.4187 28.9815 49.7744 28.7188 49.16 27.49L38.99 7.15C37.5159 4.20193 33.3141 4.20193 31.84 7.15L21.64 27.54C21.0333 28.7533 19.419 29.0293 18.45 28.08L7.45 17.3C5.29872 15.1923 1.72724 17.2115 2.43 20.14">
+</path>
+<path d="M35 60.0004L54.85 60.0004C56.6996 60.0004 58.3084 58.729 58.74 56.9304L67.47 20.5505C68.1803 17.5903 64.54 15.5756 62.41 17.7505L52.38 28.0005C51.4187 28.982 49.7744 28.7193 49.16 27.4904L38.99 7.15046C38.181 5.5325 36.5504 4.80251 35 4.96049L35 60.0004Z" fill-rule="evenodd" fill="#ABB5C1" >
+</path>
+<g >
+<path fill="#FFFFFF" d="M27.908 53.4149L43.14 53.4149L43.14 50.2469L37.22 50.2469C36.068 50.2469 34.596 50.3749 33.38 50.5029C38.372 45.7349 42.02 41.0309 42.02 36.4869C42.02 32.2309 39.236 29.4149 34.916 29.4149C31.812 29.4149 29.732 30.7269 27.716 32.9349L29.796 34.9829C31.076 33.5109 32.612 32.3909 34.436 32.3909C37.092 32.3909 38.404 34.1189 38.404 36.6789C38.404 40.5509 34.852 45.1269 27.908 51.2709L27.908 53.4149Z">
+</path>
+</g>
+</svg>
diff --git a/public/images/3.svg b/public/images/3.svg
new file mode 100644
index 0000000..0694592
--- /dev/null
+++ b/public/images/3.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="81.478515625" height="65.6748046875" viewBox="0 0 81.478515625 65.6748046875" fill="none">
+<path fill="#DEBA9C" d="M2.43 20.14L11.26 56.93C11.6916 58.7285 13.3004 60 15.15 60L54.85 60C56.6996 60 58.3084 58.7285 58.74 56.93L67.47 20.55C68.1803 17.5899 64.54 15.5752 62.41 17.75L52.38 28C51.4187 28.9815 49.7744 28.7188 49.16 27.49L38.99 7.15C37.5159 4.20193 33.3141 4.20193 31.84 7.15L21.64 27.54C21.0333 28.7533 19.419 29.0293 18.45 28.08L7.45 17.3C5.29872 15.1924 1.72724 17.2115 2.43 20.14">
+</path>
+<path d="M34.9996 60L54.8496 60C56.6992 60 58.308 58.7285 58.7396 56.93L67.4696 20.55C68.1799 17.5898 64.5396 15.5751 62.4096 17.75L52.3796 28C51.4183 28.9815 49.774 28.7188 49.1596 27.49L38.9896 7.14996C38.1806 5.532 36.55 4.80201 34.9996 4.95999L34.9996 60Z" fill-rule="evenodd" fill="#CDA787" >
+</path>
+<g >
+<path fill="#FFFFFF" d="M35.076 52.9054C39.396 52.9054 42.948 50.3774 42.948 46.1214C42.948 42.9534 40.804 40.9054 38.116 40.2014L38.116 40.0734C40.612 39.1454 42.18 37.2574 42.18 34.5374C42.18 30.6654 39.172 28.4574 34.948 28.4574C32.228 28.4574 30.084 29.6414 28.196 31.3054L30.116 33.6094C31.492 32.2974 32.996 31.4334 34.82 31.4334C37.06 31.4334 38.436 32.7134 38.436 34.7934C38.436 37.1614 36.9 38.8894 32.26 38.8894L32.26 41.6414C37.572 41.6414 39.204 43.3374 39.204 45.9294C39.204 48.3934 37.412 49.8334 34.756 49.8334C32.324 49.8334 30.596 48.6494 29.188 47.2734L27.396 49.6414C28.996 51.4014 31.364 52.9054 35.076 52.9054Z">
+</path>
+</g>
+</svg>
diff --git "a/public/images/\345\234\260\345\233\276.png" "b/public/images/\345\234\260\345\233\276.png"
new file mode 100644
index 0000000..1a87e41
--- /dev/null
+++ "b/public/images/\345\234\260\345\233\276.png"
Binary files differ
diff --git "a/public/images/\346\225\264\345\220\210\345\214\205.png" "b/public/images/\346\225\264\345\220\210\345\214\205.png"
new file mode 100644
index 0000000..55a79eb
--- /dev/null
+++ "b/public/images/\346\225\264\345\220\210\345\214\205.png"
Binary files differ
diff --git "a/public/images/\346\235\220\350\264\250\345\214\205.png" "b/public/images/\346\235\220\350\264\250\345\214\205.png"
new file mode 100644
index 0000000..59295da
--- /dev/null
+++ "b/public/images/\346\235\220\350\264\250\345\214\205.png"
Binary files differ
diff --git "a/public/images/\346\250\241\347\273\204.png" "b/public/images/\346\250\241\347\273\204.png"
new file mode 100644
index 0000000..74051f7
--- /dev/null
+++ "b/public/images/\346\250\241\347\273\204.png"
Binary files differ
diff --git "a/src/app/community/community-detail/\133communityId\135/page.tsx" "b/src/app/community/community-detail/\133communityId\135/page.tsx"
new file mode 100644
index 0000000..40200f7
--- /dev/null
+++ "b/src/app/community/community-detail/\133communityId\135/page.tsx"
@@ -0,0 +1,289 @@
+'use client';
+
+import React, { useEffect, useState, useRef } from "react";
+import { InputText } from 'primereact/inputtext';
+import { Button } from 'primereact/button';
+import { Card } from 'primereact/card';
+import { Image } from 'primereact/image';
+import { Dropdown } from 'primereact/dropdown';
+// 页面跳转
+import { useParams } from 'next/navigation'
+import { useRouter } from 'next/navigation';
+// 分页
+import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
+// 评分图标
+import { Fire } from '@icon-park/react';
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 发布帖子
+import { Dialog } from 'primereact/dialog';
+import { FileUpload } from 'primereact/fileupload';
+import { InputTextarea } from 'primereact/inputtextarea';
+// 接口传输
+import axios from 'axios';
+// 防抖函数
+import { debounce } from 'lodash';
+// 样式
+import './resource-community.scss';
+
+// 帖子列表数据
+interface Thread {
+ threadId: number;
+ userId: number;
+ threadPicture: string;
+ title: string;
+ createdAt: string;
+ communityId: string;
+ likes: number;
+}
+interface ThreadList {
+ records: Thread[];
+}
+// 社区信息
+interface CommunityInfo {
+ communityId: string;
+ communityName: string;
+ communityPicture: string;
+ description: string;
+ hot: number;
+ type: string;
+ threadNumber: number;
+}
+
+
+// 社区详情页面
+export default function CommunityDetailPage() {
+ // 获取URL参数,页面跳转
+ const params = useParams<{ communityId: string }>()
+ const communityId = decodeURIComponent(params.communityId); // 防止中文路径乱码
+ const router = useRouter();
+ // 社区数据
+ const [communityInfo, setCommunityInfo] = useState<CommunityInfo | null>(null);
+ // 帖子列表数据
+ const [threads, setThreads] = useState<Thread[]>([]);
+ const [totalThreads, setTotalThreads] = useState<number>(0);
+ // 搜索框
+ const [searchValue, setSearchValue] = useState("");
+ const debouncedSearch = useRef(
+ debounce((value: string) => {
+ setSearchValue(value);
+ }, 600)
+ ).current;
+ // 消息提醒
+ const toast = useRef<Toast>(null);
+ // 筛选器选项数据
+ const [selectedOption, setSelectedOption] = useState({ name: '热度最高' });
+ const options = [
+ { name: '来自好友' },
+ { name: '热度最高' }
+ ];
+ // 分页
+ const [first, setFirst] = useState(0);
+ const [rows, setRows] = useState(6);
+ const onPageChange = (event: PaginatorPageChangeEvent) => {
+ setFirst(event.first);
+ setRows(event.rows);
+ };
+ // 获取社区信息
+ useEffect(() => {
+ const fetchThreadInfo = async () => {
+ try {
+ const { data } = await axios.get(`http://127.0.0.1:4523/m1/6387307-6083949-default/community/info?communityId=${communityId}`);
+ setCommunityInfo(data);
+ setTotalThreads(data.threadNumber);
+ } catch (err) {
+ console.error(err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取社区信息失败' });
+ }
+ };
+ fetchThreadInfo();
+ }, [communityId]);
+
+ // 获取帖子列表
+ useEffect(() => {
+ fetchThreads();
+ }, [communityId, first, rows, selectedOption, searchValue]);
+
+ const fetchThreads = async () => {
+ try {
+ const page = first / rows + 1;
+ console.log("当前页" + page + "size" + rows + "搜索内容" + searchValue);
+ const option = selectedOption.name // 添加排序参数
+ const response = await axios.get<ThreadList>(
+ `http://127.0.0.1:4523/m1/6387307-6083949-default/community/threads`, {
+ params: { communityId, page, rows, option, searchValue }
+ }
+ );
+ console.log('获取帖子列表:', response.data.records);
+ setThreads(response.data.records);
+ } catch (err) {
+ console.error('获取帖子失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取帖子失败' });
+ }
+ };
+
+ // 发布帖子弹窗
+ const [visible, setVisible] = useState(false);
+ const [formData, setFormData] = useState({
+ title: '',
+ content: '',
+ threadPicture: ''
+ });
+ // 图片上传消息通知
+ const onUpload = () => {
+ toast.current?.show({ severity: 'info', summary: 'Success', detail: 'File Uploaded' });
+ };
+
+ // 发帖接口
+ const handleSubmit = async () => {
+ try {
+ const currentDate = new Date().toISOString();
+ const postData = {
+ userId: 22301145, // 记得用户登录状态获取
+ threadPicture: formData.threadPicture,
+ title: formData.title,
+ content: formData.content,
+ createdAt: currentDate,
+ communityId: communityId // 从URL参数获取的社区ID
+ };
+ // 发送POST请求
+ const response = await axios.post('http://127.0.0.1:4523/m1/6387307-6083949-default/thread', postData);
+
+ if (response.status === 200) {
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '帖子发布成功' });
+ // 发帖成功
+ setVisible(false);
+ // 重置表单
+ setFormData({
+ title: '',
+ content: '',
+ threadPicture: ''
+ });
+ // 可以刷新帖子列表
+ fetchThreads();
+ }
+ } catch (error) {
+ console.error('发帖失败:', error);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '帖子发布失败' });
+ }
+ };
+ return (
+ <div className="resource-community">
+ <Toast ref={toast}></Toast>
+ {/* 社区标题和介绍 */}
+ <div className="community-header">
+ <div className="title-section">
+ <h1>{communityInfo?.communityName}</h1>
+ <p className="subtitle">{communityInfo?.description}</p>
+ <div className="community-states">
+ <div className="state-item">
+ <Fire theme="outline" size="16" fill="#FF8D1A" />
+ <span>热度: {communityInfo?.hot}</span>
+ </div>
+ <div className="state-item">
+ <i className="pi pi-book" />
+ <span>帖子数: {totalThreads}</span>
+ </div>
+ </div>
+ </div>
+ <div className="input">
+ <Button label="返回列表" link onClick={() => router.push(`/community/resource-community-list/${communityInfo?.type}`)} />
+ <div className="action-section">
+ <div className="communities-searchBar">
+ <i className="pi pi-search" />
+ <InputText type="search" className="search-helper" placeholder="搜索你感兴趣的帖子" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} />
+ </div>
+ <Dropdown
+ value={selectedOption}
+ onChange={(e) => setSelectedOption(e.value)}
+ options={options}
+ optionLabel="name"
+ className="select-dropdown"
+ />
+ </div>
+ </div>
+ </div>
+
+ {/* 帖子列表 */}
+ <div className="thread-list">
+ <div className="resource-grid">
+ {threads.map((thread) => (
+ <Card key={thread.threadId} className="resource-card" onClick={() => router.push(`/community/thread-detail/${thread.threadId}`)}>
+ <Image
+ src={process.env.NEXT_PUBLIC_NGINX_URL + thread.threadPicture}
+ alt={thread.title}
+ width="368"
+ height="200"
+ />
+ <div className="card-content">
+ <h3>{thread.title}</h3>
+ <div className="view-count">
+ <i className="pi pi-face-smile" />
+ <span>{thread.likes}</span>
+ </div>
+ </div>
+ </Card>
+ ))}
+ </div>
+ {totalThreads > 6 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalThreads} rowsPerPageOptions={[6, 12]} onPageChange={onPageChange} />)}
+ </div>
+
+ {/* 添加按钮 */}
+ <Button className="add-resource-button" icon="pi pi-plus" rounded aria-label="add-thread" onClick={() => setVisible(true)} />
+
+ {/* 发布帖子弹窗 */}
+ <Dialog
+ header="发布新帖子"
+ visible={visible}
+ onHide={() => setVisible(false)}
+ className="publish-dialog"
+ modal
+ footer={
+ <div className="dialog-footer">
+ <Button label="发布" icon="pi pi-check" onClick={handleSubmit} autoFocus />
+ <Button label="取消" icon="pi pi-times" onClick={() => setVisible(false)} className="p-button-text" />
+ </div>
+ }
+ >
+ <div className="publish-form">
+ <div className="form-field">
+ <label htmlFor="title">标题</label>
+ <InputText
+ id="title"
+ value={formData.title}
+ onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
+ placeholder="请输入帖子标题"
+ className="w-full"
+ />
+ </div>
+
+ <div className="form-field">
+ <label htmlFor="content">内容</label>
+ <InputTextarea
+ id="content"
+ value={formData.content}
+ onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
+ rows={5}
+ placeholder="请输入帖子内容"
+ className="w-full"
+ />
+ </div>
+
+ <div className="form-field">
+ <label>封面图片</label>
+ <FileUpload
+ mode="basic"
+ name="thread-image"
+ url="/file" // 与后端交互的URL
+ accept="image/*"
+ maxFileSize={10000000000}
+ chooseLabel="选择图片"
+ className="w-full"
+ onUpload={onUpload}
+ />
+ </div>
+ </div>
+ </Dialog>
+ </div>
+ );
+}
\ No newline at end of file
diff --git "a/src/app/community/community-detail/\133communityId\135/resource-community.scss" "b/src/app/community/community-detail/\133communityId\135/resource-community.scss"
new file mode 100644
index 0000000..a40ebaa
--- /dev/null
+++ "b/src/app/community/community-detail/\133communityId\135/resource-community.scss"
@@ -0,0 +1,243 @@
+@import '../../../globals.scss';
+
+.resource-community {
+ padding: 2rem;
+ position: relative;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 2rem;
+
+ .community-header {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin-bottom: 2rem;
+
+ .title-section {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ align-items: flex-start;
+
+ h1 {
+ font-size: 3rem;
+ margin: 0;
+ margin-top: 32px;
+ }
+
+ .subtitle {
+ margin: 0;
+ color: #718096;
+ font-size: 1rem;
+ }
+
+ .stars {
+ font-size: 1.2rem;
+ }
+
+ .community-states {
+ display: flex;
+ gap: 2rem;
+ align-items: center;
+
+ .state-item {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: #666;
+
+ span {
+ font-size: 0.9rem;
+ }
+ }
+ }
+ }
+
+ .input {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: flex-end;
+
+ .p-button {
+ width: 100px;
+
+ padding: 0;
+ margin-top: 10px;
+ }
+ }
+ .p-dropdown-item p-focus{
+ background-color: rgba(182, 238, 235, 0.4) !important;
+ }
+ .p-dropdown-item p-highlight p-focus {
+ background-color: rgba(182, 238, 235, 0.4) !important;
+ }
+ .action-section {
+ display: flex;
+ align-items: center;
+
+ .communities-searchBar {
+ max-width: 100%;
+ position: relative;
+
+ .pi-search {
+ position: absolute;
+ left: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 1;
+ }
+
+ .search-helper {
+ width: 100%;
+ height: 3rem;
+ padding-left: 2.5rem;
+ border-radius: 10px 0px 0px 10px;
+ font-size: 1.1rem;
+ border: 1px solid #ddd;
+ }
+ }
+
+ .select-dropdown {
+ width: 100px;
+ height: 48px;
+ border-radius: 0px 10px 10px 0px;
+
+ .p-dropdown-items {
+ max-height: 20px;
+ }
+ }
+ }
+ }
+
+ .thread-list {
+ display: flex;
+ gap: 1rem;
+ flex-direction: column;
+
+ .resource-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 1rem;
+
+ .resource-card {
+ transition: transform 0.2s ease;
+ cursor: pointer;
+ box-shadow: none !important;
+
+ .p-image {
+ img {
+ border-radius: 0.5rem 0.5rem 0 0;
+ object-fit: cover;
+ }
+ }
+
+ .p-card-body {
+ padding: 0;
+ }
+
+ .p-card-content {
+ padding: 0;
+ }
+
+ &:hover {
+ transform: translateY(-4px);
+ }
+
+ .card-content {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ margin-left: 16px;
+ margin-right: 16px;
+ margin-bottom: 16px;
+
+ h3 {
+ margin: 1rem 0;
+ font-size: 1rem;
+ line-height: 1.5;
+ color: #2d3748;
+ }
+
+ .view-count {
+ position: absolute;
+ bottom: 0rem;
+ right: 0rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: #718096;
+ font-size: 0.9rem;
+ }
+ }
+ }
+ }
+ }
+
+ .add-resource-button {
+ position: fixed;
+ bottom: 2rem;
+ right: 2rem;
+ width: 56px;
+ height: 56px;
+
+ .pi-plus {
+ font-size: 1.5rem;
+ }
+ }
+}
+
+// ========== 弹窗发布样式 ==========
+
+.publish-dialog {
+ width: 600px !important;
+ max-width: 90vw;
+
+ .p-dialog-header {
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: $heading-color;
+ padding-bottom: 0.5rem;
+ }
+
+ .p-dialog-content {
+ padding-top: 0;
+ padding-bottom: 0;
+
+ .publish-form {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ margin-top: 1rem;
+
+ .form-field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+
+ label {
+ font-weight: 600;
+ color: $heading-color;
+ }
+
+ input,
+ textarea {
+ padding: 0.75rem 1rem;
+ border-radius: 8px;
+ font-size: 1rem;
+ color: #2d3748;
+ }
+
+
+ .p-fileupload {
+ .p-button {
+ width: 100%;
+ justify-content: center;
+ border: none;
+ margin-bottom: 1rem;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/community/community.scss b/src/app/community/community.scss
new file mode 100644
index 0000000..42bfda1
--- /dev/null
+++ b/src/app/community/community.scss
@@ -0,0 +1,219 @@
+// 全局容器样式
+.community-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 2rem;
+}
+
+
+// 热门社区样式
+.hot-communities {
+ margin: 3rem 0;
+
+ h1 {
+ margin-bottom: 1.5rem;
+ text-align: center; // 页面居中
+ }
+
+ .p-card-body {
+ height: 80px;
+ padding: 0;
+ padding-left: 10px;
+ padding-bottom: 10px;
+ padding-right: 10px;
+ }
+
+ .p-card-content {
+ padding: 0;
+ }
+
+ &-carousel {
+ .p-carousel-container {
+ padding: 1rem 0;
+ }
+ }
+
+ &-card {
+ margin: 0.5rem;
+ transition: transform 0.3s ease;
+ box-shadow: none !important; // 取消阴影
+
+ &:hover {
+ transform: translateY(-5px);
+ }
+
+ p {
+ margin: 0;
+ }
+
+ .card-header {
+ position: relative;
+
+ .card-tag {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: #93C4C1;
+ color: white;
+ padding: 0.3rem 0.8rem;
+ border-radius: 0.5rem 0 0.5rem 0;
+ font-size: 2rem;
+ z-index: 1;
+ }
+
+ img {
+ border-radius: 0.5rem 0.5rem 0 0;
+ object-fit: cover;
+ }
+ }
+ }
+}
+
+h1 {
+ text-align: center; // 页面居中
+}
+
+// 全部分类样式
+.all-communities-classifications {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 1.5rem;
+ margin: 2rem 0;
+
+ .communities-classification-card {
+ position: relative;
+ overflow: hidden;
+ border-radius: 1rem;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ //鼠标悬浮效果
+ img:hover {
+ transform: translateY(-5px);
+ }
+
+ //图片样式
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 0.5rem;
+ transition: all 0.3s ease;
+ }
+ }
+}
+
+// 全部社区样式
+.all-communities {
+ width: 100%;
+ padding: 1rem;
+
+ &-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ &-card {
+ height: 140px;
+ padding: 1.5rem;
+ margin-bottom: 1rem;
+ border-radius: 0.5rem;
+ transition: transform 0.3s ease;
+ box-shadow: none !important; // 取消阴影
+
+ //填充卡片
+ &.p-card.p-component {
+ padding: 0;
+ }
+
+ .p-card-body {
+ padding: 0;
+ }
+
+ &:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ }
+
+ .p-card-content {
+ height: 140px;
+ display: flex;
+ justify-content: space-between;
+ padding: 0;
+ }
+
+ img {
+ border-radius: 0.5rem 0 0 0.5rem;
+ object-fit: cover;
+ }
+
+ .community-header {
+ display: flex;
+ flex: 1;
+ max-width: 850px;
+ padding-left: 20px;
+ padding-right: 20px;
+ margin-bottom: 20px;
+ }
+
+ .community-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ h3 {
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: #2c3e50;
+ }
+
+ .community-introduction {
+ color: #666;
+ font-size: 1rem;
+ margin-bottom: 0;
+ }
+ }
+
+ .community-states {
+ min-width: 120px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: flex-end;
+ gap: 0.5rem;
+
+ .state-item {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: #666;
+ font-size: 1rem;
+ }
+ }
+ }
+}
+
+
+// 响应式设计
+@media (max-width: 1024px) {
+ .all-communities-classifications {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (max-width: 768px) {
+ .community-container {
+ padding: 0 1rem;
+ }
+
+ .all-communities-classifications {
+ grid-template-columns: 1fr;
+ }
+
+ .hot-communities-carousel {
+ .p-carousel-items-container {
+ padding: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/community/page.tsx b/src/app/community/page.tsx
new file mode 100644
index 0000000..a47cb47
--- /dev/null
+++ b/src/app/community/page.tsx
@@ -0,0 +1,191 @@
+'use client';
+
+import { useState, useEffect, useRef } from 'react';
+import { Button } from 'primereact/button';
+import { Card } from 'primereact/card';
+import { Image } from 'primereact/image';
+//幻灯片
+import { Carousel } from 'primereact/carousel';
+//评分图标
+import { Fire } from '@icon-park/react';
+// 页面跳转
+import { useRouter } from 'next/navigation';
+import Link from 'next/link';
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 接口传输
+import axios from 'axios';
+// 样式
+import './community.scss';
+
+
+// 热门社区信息
+interface hotCommunity {
+ communityId: number;
+ communityName: string;
+ hot: number;
+ status: number; // 热门状态
+ threadNumber: number;
+ description: string;
+ communityPicture: string;
+ type: string;
+}
+interface Community {
+ communityId: number;
+ communityName: string;
+ hot: number;
+ threadNumber: number;
+ description: string;
+ communityPicture: string;
+ type: string;
+}
+
+
+// 社区主页面
+export default function CommunityPage() {
+ // 路由
+ const router = useRouter();
+ // 消息提醒
+ const toast = useRef<Toast>(null);
+ // 热门社区数据
+ const [hotCommunities, setHotCommunities] = useState<hotCommunity[]>([]);
+ // 社区数据
+ const [communities, setCommunities] = useState<Community[]>([]);
+ // 获取热门社区信息
+ useEffect(() => {
+ const fetchHotCommunity = async () => {
+ try {
+ const { data } = await axios.get(`http://127.0.0.1:4523/m1/6387307-6083949-default/community/hot`);
+ setHotCommunities(data.communityList);
+ } catch (err) {
+ console.error(err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取热门社区失败' });
+ }
+ };
+ fetchHotCommunity();
+ }, []);
+
+ // 获取社区信息
+ useEffect(() => {
+ const fetchCommunity = async () => {
+ try {
+ const { data } = await axios.get(`http://127.0.0.1:4523/m1/6387307-6083949-default/community/common`);
+ setCommunities(data.communityList);
+ console.log(data.communityList);
+ } catch (err) {
+ console.error(err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取社区失败' });
+ }
+ };
+ fetchCommunity();
+ }, []);
+ return (
+ <div className="community-container">
+
+ {/* 热门社区 */}
+ <div className="hot-communities">
+ <h1>热门社区</h1>
+ <Carousel
+ showIndicators={false}
+ showNavigators={false}
+ value={hotCommunities}
+ numVisible={3}
+ numScroll={1}
+ className="hot-communities-carousel"
+ itemTemplate={(item) => (
+ <div className="hot-communities-card">
+ <Card
+ onClick={() => router.push(`/community/community-detail/${item.communityId}`)}
+ title={item.communityName}
+ header={
+ <div className="card-header">
+ <div className="card-tag">
+ <Image
+ src={`/images/${item.status}.svg`}
+ alt="热门标签"
+ width="24"
+ height="24"
+ /></div>
+ <Image src={process.env.NEXT_PUBLIC_NGINX_URL + item.communityPicture} alt={item.communityName} height="200" className="w-full h-48 object-cover" />
+ </div>
+ }
+ >
+ <p>{item.description}</p>
+ </Card>
+ </div>
+ )}
+ />
+ </div>
+
+ {/* 全部分类 */}
+ <h1>全部分类</h1>
+ <div className="all-communities-classifications">
+ <Link href="/community/resource-community-list/材质包">
+ <Image
+ className='communities-classification-card'
+ src="/images/材质包.png"
+ alt="Image"
+ width="250"
+ />
+ </Link>
+ <Link href="/community/resource-community-list/整合包">
+ <Image
+ className='communities-classification-card'
+ src="/images/整合包.png"
+ alt="Image"
+ width="250"
+ />
+ </Link>
+ <Link href="/community/resource-community-list/模组">
+ <Image
+ className='communities-classification-card'
+ src="/images/模组.png"
+ alt="Image"
+ width="250"
+ />
+ </Link>
+ <Link href="/community/resource-community-list/地图">
+ <Image
+ className='communities-classification-card'
+ src="/images/地图.png"
+ alt="Image"
+ width="250"
+ />
+ </Link>
+ </div>
+
+ {/* 全部社区 */}
+ <div className="all-communities">
+ <div className="all-communities-header">
+ <h1>全部社区</h1>
+ <Link href="/community/resource-community-list/all">
+ <Button label="查看更多" link />
+ </Link>
+ </div>
+ <div className="all-communities-list">
+ {communities.map((community) => (
+ <Card key={community.communityId} className="all-communities-card" onClick={() => router.push(`/community/community-detail/${community.communityId}`)}>
+ <Image alt="avatar" src={process.env.NEXT_PUBLIC_NGINX_URL + community.communityPicture} className="community-avatar" width="250" height="140" />
+ <div className="community-header">
+ <div className="community-content">
+ <h3>{community.communityName}</h3>
+ <p className="community-introduction">{community.description}</p>
+ </div>
+ <div className="community-states">
+ <div className="state-item">
+ <Fire theme="outline" size="16" fill="#FF8D1A" />
+ <span>热度: {community.hot}</span>
+ </div>
+ <div className="state-item">
+ <i className="pi pi-book" />
+ <span>贴子数: {community.threadNumber}</span>
+ </div>
+ </div>
+ </div>
+ </Card>
+ ))}
+ </div>
+ </div>
+ </div>
+ );
+}
\ No newline at end of file
diff --git "a/src/app/community/resource-community-list/\133category\135/page.tsx" "b/src/app/community/resource-community-list/\133category\135/page.tsx"
new file mode 100644
index 0000000..a34fc07
--- /dev/null
+++ "b/src/app/community/resource-community-list/\133category\135/page.tsx"
@@ -0,0 +1,137 @@
+'use client';
+
+import { useState, useEffect, useRef } from 'react';
+import { InputText } from 'primereact/inputtext';
+import { Card } from 'primereact/card';
+import { Image } from 'primereact/image';
+// 评分图标
+import { Fire } from '@icon-park/react';
+// 分页
+import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
+// 页面跳转
+import { useParams } from 'next/navigation'
+import { useRouter } from 'next/navigation';
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 接口传输
+import axios from 'axios';
+// 防抖函数
+import { debounce } from 'lodash';
+// 样式
+import './resource-community-list.scss';
+
+// 单元社区信息
+interface Community {
+ communityId: number;
+ communityName: string;
+ hot: number; // 热度
+ threadNumber: number; // 帖子数量
+ description: string; // 简介
+ communityPicture: string; // 头像
+}
+
+// 资源社区列表
+interface CommunityList {
+ total: number;
+ records: Community[];
+}
+
+
+// 资源社区界面
+export default function ResourceCommunityPage() {
+ // 获取URL参数
+ const params = useParams<{ category: string }>()
+ const category = decodeURIComponent(params.category); // 防止中文路径乱码
+ const router = useRouter();
+
+ // 社区列表数据
+ const [communities, setCommunities] = useState<Community[]>([]);
+ const [totalCommunities, setTotalCommunities] = useState<number>(0);
+ // 消息提醒
+ const toast = useRef<Toast>(null);
+ //搜索框
+ const [searchValue, setSearchValue] = useState('');
+ const debouncedSearch = useRef(
+ debounce((value: string) => {
+ setSearchValue(value);
+ }, 600)
+ ).current;
+
+ //分页
+ const [first, setFirst] = useState(0);
+ const [rows, setRows] = useState(5);
+ const onPageChange = (event: PaginatorPageChangeEvent) => {
+ setFirst(event.first);
+ setRows(event.rows);
+ };
+
+
+ // 获取社区列表
+ useEffect(() => {
+ fetchCommunities();
+ }, [category, first, rows, searchValue]);
+
+ const fetchCommunities = async () => {
+ try {
+ const page = first / rows + 1;
+ const type = category === 'all' ? '全部' : category;
+ console.log("当前页" + page + "size" + rows + "type" + type + "searchValue" + searchValue);
+ const response = await axios.get<CommunityList>(
+ `http://127.0.0.1:4523/m1/6387307-6083949-default/community`, {
+ params: { type, page, rows, searchValue }
+ }
+ );
+ console.log('获取社区列表:', response.data.records);
+ setTotalCommunities(response.data.total);
+ setCommunities(response.data.records);
+ } catch (err) {
+ console.error('获取社区失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取社区失败' });
+ }
+ };
+
+
+ return (
+ <div className="resource-community-list-container">
+ <Toast ref={toast}></Toast>
+ <div className="resource-communities-header">
+ <h1 className="title">
+ {category === 'all' ? '全部社区' : `${category}社区`}
+ </h1>
+ </div>
+
+ {/* 社区搜索栏 */}
+ <div className="communities-searchBar">
+ <i className="pi pi-search" />
+ <InputText type="search" className="search-helper" placeholder="搜索你感兴趣的社区" onChange={(e) => { const target = e.target as HTMLInputElement; debouncedSearch(target.value); }} />
+ </div>
+
+
+ {/* 社区列表 */}
+ <div className="resource-communities-list">
+ {communities.map((community) => (
+ <Card key={community.communityId} className="resource-communities-list-card" onClick={() => router.push(`/community/community-detail/${community.communityId}`)}>
+ <Image alt="avatar" src={process.env.NEXT_PUBLIC_NGINX_URL + community.communityPicture} className="community-avatar" width="250" height="140" />
+ <div className="community-header">
+ <div className="community-content">
+ <h3>{community.communityName}</h3>
+ <p className="community-introduction">{community.description}</p>
+ </div>
+ <div className="community-states">
+ <div className="state-item">
+ <Fire theme="outline" size="16" fill="#FF8D1A" />
+ <span>热度: {community.hot}</span>
+ </div>
+ <div className="state-item">
+ <i className="pi pi-book" />
+ <span>帖子数: {community.threadNumber}</span>
+ </div>
+ </div>
+ </div>
+ </Card>
+ ))}
+ {totalCommunities > 5 && <Paginator className="Paginator" first={first} rows={rows} totalRecords={totalCommunities} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />}
+ </div>
+ </div>
+ );
+}
\ No newline at end of file
diff --git "a/src/app/community/resource-community-list/\133category\135/resource-community-list.scss" "b/src/app/community/resource-community-list/\133category\135/resource-community-list.scss"
new file mode 100644
index 0000000..c8a0018
--- /dev/null
+++ "b/src/app/community/resource-community-list/\133category\135/resource-community-list.scss"
@@ -0,0 +1,104 @@
+// 全局容器样式
+.resource-community-list-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 2rem;
+}
+
+.resource-communities-header {
+ h1 {
+ text-align: center; // 页面居中
+ }
+}
+
+// 全部社区样式
+.resource-communities-list {
+ width: 100%;
+ padding: 1rem;
+
+ &-card {
+ height: 140px;
+ padding: 1.5rem;
+ margin-bottom: 1rem;
+ border-radius: 0.5rem;
+ transition: transform 0.3s ease;
+ box-shadow: none !important; // 取消阴影
+
+ //填充卡片
+ &.p-card.p-component {
+ padding: 0;
+ }
+
+ .p-card-body {
+ padding: 0;
+ }
+
+ &:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ }
+
+ .p-card-content {
+ height: 140px;
+ display: flex;
+ justify-content: space-between;
+ padding: 0;
+ }
+
+ img {
+ border-radius: 0.5rem 0 0 0.5rem;
+ object-fit: cover;
+ }
+
+ .community-header {
+ display: flex;
+ flex: 1;
+ max-width: 800px;
+ padding-left: 20px;
+ padding-right: 20px;
+ margin-bottom: 20px;
+ }
+
+
+ .community-avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 8px;
+ object-fit: cover;
+ }
+
+ .community-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ h3 {
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: #2c3e50;
+ }
+
+ .community-introduction {
+ color: #666;
+ font-size: 1rem;
+ margin-bottom: 0;
+ }
+ }
+
+ .community-states {
+ min-width: 120px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: flex-end;
+ gap: 0.5rem;
+
+ .state-item {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: #666;
+ font-size: 1rem;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git "a/src/app/community/thread-detail/\133threadId\135/page.tsx" "b/src/app/community/thread-detail/\133threadId\135/page.tsx"
new file mode 100644
index 0000000..2189402
--- /dev/null
+++ "b/src/app/community/thread-detail/\133threadId\135/page.tsx"
@@ -0,0 +1,396 @@
+'use client';
+
+import React, { useEffect, useState, useRef } from 'react';
+import { Image } from 'primereact/image';
+import { Avatar } from 'primereact/avatar';
+import { Button } from 'primereact/button';
+import { InputText } from "primereact/inputtext";
+// 页面跳转
+import { useParams } from 'next/navigation'
+import { useRouter } from 'next/navigation';
+// 接口传输
+import axios from 'axios';
+// 回复评论
+import { OverlayPanel } from 'primereact/overlaypanel';
+import { Sidebar } from 'primereact/sidebar';
+// 分页
+import { Paginator, type PaginatorPageChangeEvent } from 'primereact/paginator';
+// 消息提醒
+import { Toast } from 'primereact/toast';
+// 样式
+import './thread.scss';
+
+
+// 评论信息
+interface Comment {
+ commentId: number;
+ userId: number | null;
+ replyId: number;
+ content: string;
+ createdAt: string;
+}
+// 评论列表
+interface CommentList {
+ records: Comment[]; // 当前页评论数组
+}
+// 帖子信息
+interface ThreadInfo {
+ threadId: number;
+ userId: number;
+ threadPicture: string;
+ title: string;
+ content: string;
+ likes: number;
+ isLike: boolean;
+ createdAt: string;
+ commentNumber: number;
+ communityId: number;
+}
+// 用户信息
+interface UserInfo {
+ userId: number;
+ username: string;
+ avatar: string;
+ signature: string;
+}
+// 新评论接口
+interface NewComment {
+ userId: number;
+ threadId: number;
+ resourceId: number;
+ replyId: number;
+ content: string;
+ createdAt: string;
+}
+
+
+//帖子详情界面
+export default function ThreadDetailPage() {
+ // 获取URL参数,页面跳转
+ const params = useParams<{ threadId: string }>()
+ const threadId = decodeURIComponent(params.threadId); // 防止中文路径乱码
+ const router = useRouter();
+ // 消息提醒
+ const toast = useRef<Toast>(null);
+ // 帖子信息
+ const [threadInfo, setThreadInfo] = useState<ThreadInfo | null>(null);
+ // 发帖人信息
+ const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
+ // 评论人信息
+ const [commentUserInfos, setCommentUserInfos] = useState<Map<number, UserInfo>>(new Map());
+ //评论
+ const [comments, setComments] = useState<Comment[]>([]);
+ const [commentValue, setCommentValue] = useState<string>('');
+ const [totalComments, setTotalComments] = useState<number>(0);
+ // 回复
+ const [replyValue, setReplyValue] = useState<string>('');
+ const [visibleReply, setVisibleReply] = useState<boolean>(false);// 回复评论可视
+ // 评论选择框
+ const ops = useRef<OverlayPanel[]>([]);
+
+ // 分页
+ const [first, setFirst] = useState<number>(0);
+ const [rows, setRows] = useState<number>(5);
+ const onPageChange = (event: PaginatorPageChangeEvent) => {
+ setFirst(event.first);
+ setRows(event.rows);
+ };
+
+ // 获取帖子信息
+ useEffect(() => {
+ fetchThreadInfo();
+ }, [threadId, threadInfo]);
+
+ const fetchThreadInfo = async () => {
+ try {
+ const { data } = await axios.get(`http://127.0.0.1:4523/m1/6387307-6083949-default/thread?threadId=${threadId}`);
+ setThreadInfo(data);
+ setTotalComments(data.commentNumber);
+ } catch (err) {
+ console.error(err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取帖子信息失败' });
+ }
+ };
+
+ // 获取发帖人
+ useEffect(() => {
+ if (!threadInfo) return;
+ // 发帖人
+ axios.get(`http://127.0.0.1:4523/m1/6387307-6083949-default/user/info?userId=${threadInfo.userId}`)
+ .then(res => setUserInfo(res.data))
+ .catch(console.error);
+ }, [threadInfo]);
+
+ // 处理点赞
+ const handleLike = async () => {
+ if (!threadInfo) return;
+ if (!threadInfo.isLike) {
+ try {
+ const response = await axios.post(
+ `http://127.0.0.1:4523/m1/6387307-6083949-default/thread/like`, {
+ params: { threadId, userId: 22301145 }
+ }
+ );
+ fetchThreadInfo(); // 刷新帖子信息
+ if (response.status === 200) {
+ console.log('点赞成功:', response.data);
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '点赞成功' });
+ }
+ } catch (error) {
+ console.error('点赞失败:', error);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '点赞失败' });
+ }
+ } else {
+ try {
+ const response = await axios.delete(
+ `http://127.0.0.1:4523/m1/6387307-6083949-default/thread/like`, {
+ params: { threadId, userId: 22301145 }
+ }
+ );
+ fetchThreadInfo(); // 刷新帖子信息
+ if (response.status === 200) {
+ console.log('取消点赞成功:', response.data);
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '取消点赞成功' });
+ }
+ } catch (error) {
+ console.error('取消点赞失败:', error);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '取消点赞失败' });
+ }
+ }
+
+ };
+
+
+ // 当 threadId 或分页参数变化时重新拉评论
+ useEffect(() => {
+ if (!threadId) return;
+
+ fetchComments();
+ }, [threadId, first, rows]);
+
+
+ //通过评论ID获取评论人信息
+ const getReplyUserName = (replyId: number) => {
+ if (replyId == null || replyId == 0) return '';
+ const replyComment = comments.find(comment => comment.commentId === replyId);
+ if (!replyComment?.userId) return '匿名用户';
+ return "回复 " + commentUserInfos.get(replyComment.userId)?.username || '匿名用户';
+ };
+
+ // 获取评论列表
+ const fetchComments = async () => {
+ try {
+ const page = first / rows + 1;
+ console.log("当前页" + page + "size" + rows);
+ const response = await axios.get<CommentList>(
+ `http://127.0.0.1:4523/m1/6387307-6083949-default/comments`, {
+ params: { threadId, page, rows }
+ }
+ );
+ console.log('获取评论列表:', response.data.records);
+ setComments(response.data.records);
+ // 拉取评论对应用户信息
+ response.data.records.forEach(comment => {
+ if (comment.userId != null && !commentUserInfos.has(comment.userId)) {
+ axios.get<UserInfo>(
+ `http://127.0.0.1:4523/m1/6387307-6083949-default/user/info`,
+ { params: { userId: comment.userId } }
+ ).then(res => {
+ setCommentUserInfos(prev => new Map(prev).set(comment.userId!, res.data));
+ });
+ }
+ });
+ } catch (err) {
+ console.error('获取评论失败', err);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '获取评论失败' });
+ }
+ };
+
+ // 回复评论接口
+ const publishReply = async (commentId: number) => {
+ if (!replyValue.trim() || !threadInfo) return;
+ console.log('发布评论:', commentId);
+ try {
+ const newComment: NewComment = {
+ userId: 22301145,
+ threadId: threadInfo.threadId,
+ resourceId: 0,
+ replyId: commentId,
+ content: commentValue,
+ createdAt: new Date().toISOString().slice(0, 19).replace('T', ' ')
+ };
+
+ const response = await axios.post('http://127.0.0.1:4523/m1/6387307-6083949-default/comment', newComment);
+
+ if (response.status === 200) {
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '回复成功' });
+ // 更新评论列表
+ fetchComments();
+ setVisibleReply(false)
+ // 清空输入框
+ setReplyValue('');
+ }
+ } catch (error) {
+ console.error('发布评论失败:', error);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '回复失败' });
+ }
+ };
+
+ // 发布评论接口
+ const publishComment = async () => {
+ if (!commentValue.trim() || !threadInfo) return;
+
+ try {
+ const newComment: NewComment = {
+ userId: 22301145,
+ threadId: threadInfo.threadId,
+ resourceId: 0,
+ replyId: 0, // 直接评论,不是回复
+ content: commentValue,
+ createdAt: new Date().toISOString().slice(0, 19).replace('T', ' ')
+ };
+
+ const response = await axios.post('http://127.0.0.1:4523/m1/6387307-6083949-default/comment', newComment);
+
+ if (response.status === 200) {
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '评论成功' });
+ // 更新评论列表
+ fetchComments();
+ // 清空输入框
+ setCommentValue('');
+ }
+ } catch (error) {
+ console.error('发布评论失败:', error);
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '发布评论失败' });
+ }
+ };
+
+ // 删除评论接口
+ const deleteComment = async (commentId: number) => {
+ if (!threadInfo) return;
+
+ try {
+ // 调用 DELETE 接口,URL 中最后一段是要删除的 commentId
+ const response = await axios.delete(
+ `http://127.0.0.1:4523/m1/6387307-6083949-default/comment?commentId=${commentId}`
+ );
+
+ if (response.status === 200) {
+ fetchComments();
+ toast.current?.show({ severity: 'success', summary: 'Success', detail: '删除评论成功' });
+ } else {
+ toast.current?.show({ severity: 'error', summary: 'error', detail: '删除评论失败' });
+ console.error('删除评论失败,状态码:', response.status);
+ }
+ } catch (error) {
+ console.error('删除评论接口报错:', error);
+ }
+ };
+
+ const ReplyHeader = (
+ <div className="flex align-items-center gap-1">
+ <h3>回复评论</h3>
+ </div>
+ );
+ if (!threadInfo || !userInfo) return <div>Loading...</div>;
+ return (
+ <div className="thread-detail">
+ <Toast ref={toast}></Toast>
+ {/* 帖子头部 */}
+ <div className="thread-header">
+ <div className="user-info">
+ <Avatar image={process.env.NEXT_PUBLIC_NGINX_URL + "users/" + userInfo.avatar} size="large" shape="circle" />
+ <div className="user-meta">
+ <h3>{userInfo.username}</h3>
+ <span>{userInfo.signature}</span>
+ </div>
+ </div>
+ <span className="post-time">{threadInfo.createdAt}</span>
+ </div>
+
+ {/* 帖子内容 */}
+ <div className="thread-content">
+ <h1>{threadInfo.title}</h1>
+ <div className="content-body">
+ <Image
+ src={process.env.NEXT_PUBLIC_NGINX_URL + threadInfo.threadPicture}
+ alt={threadInfo.title}
+ width="800"
+ height="400"
+ className="thread-image"
+ />
+ <p>{threadInfo.content}</p>
+ </div>
+ <div className="thread-actions">
+ <Button
+ icon={"pi pi-face-smile"}
+ onClick={handleLike}
+ className={`like-button ${threadInfo.isLike ? 'liked' : ''}`}
+ label={threadInfo.likes.toString()}
+ />
+ </div>
+ </div>
+ {/* 评论列表 */}
+ <div className="comments-section">
+ <div className="comments-header">
+ <h2>评论 ({totalComments})</h2>
+ <Button label="返回社区" link onClick={() => router.push(`/community/community-detail/${threadInfo.communityId}`)} />
+ </div>
+ <div className="comments-input">
+ <Avatar image={process.env.NEXT_PUBLIC_NGINX_URL + "users/" + userInfo.avatar} size="large" shape="circle" />
+ <InputText value={commentValue} placeholder="发布你的评论" onChange={(e) => setCommentValue(e.target.value)} />
+ <Button label="发布评论" onClick={publishComment} disabled={!commentValue.trim()} />
+ </div>
+ <div className="comments-list">
+ {comments.map((comment, index) => (
+ <div key={comment.commentId} className="comment-item">
+ <div className="comment-user">
+ <Avatar
+ image={comment.userId ? process.env.NEXT_PUBLIC_NGINX_URL + "users/" + commentUserInfos.get(comment.userId)?.avatar : '/default-avatar.png'}
+ size="normal"
+ shape="circle"
+ />
+ <div className="comment-meta">
+ <span className="username">
+ {comment.userId ? commentUserInfos.get(comment.userId)?.username : '匿名用户'}
+ </span>
+ <div className="comment-time">
+ <span className="floor">#{index + 1}楼</span>
+ <span className="time">{comment.createdAt}</span>
+ </div>
+ </div>
+ <i className='pi pi-ellipsis-v' onClick={(e) => ops.current[index].toggle(e)} />
+ </div>
+ <div className="comment-content">
+ {<span className="reply-to">{getReplyUserName(comment.replyId)}</span>}
+ <p>{comment.content}</p>
+ </div>
+ <OverlayPanel // 回调 ref:把实例放到 ops.current 对应的位置
+ ref={el => {
+ if (el) ops.current[index] = el;
+ }}>
+ <Button label="回复" text size="small" onClick={() => setVisibleReply(true)} />
+ {comment.userId === 22301145 &&
+ <Button
+ label="删除"
+ text
+ size="small"
+ onClick={() => { console.log('Deleting comment:', comment.commentId, 'by user:', comment.userId); deleteComment(comment.commentId) }}
+ />
+ }
+ </OverlayPanel>
+ <Sidebar className='reply' header={ReplyHeader} visible={visibleReply} position="bottom" onHide={() => setVisibleReply(false)}>
+ <div className="reply-input">
+ <Avatar image={process.env.NEXT_PUBLIC_NGINX_URL + "users/" + userInfo.avatar} size="large" shape="circle" />
+ <InputText value={replyValue} placeholder="发布你的评论" onChange={(e) => setReplyValue(e.target.value)} />
+ <Button label="发布评论" onClick={() => publishReply(comment.commentId)} disabled={!replyValue.trim()} />
+ </div>
+ </Sidebar>
+ </div>
+ ))}
+ {totalComments > 5 && (<Paginator className="Paginator" first={first} rows={rows} totalRecords={totalComments} rowsPerPageOptions={[5, 10]} onPageChange={onPageChange} />)}
+ </div>
+ </div>
+ </div>
+ );
+}
\ No newline at end of file
diff --git "a/src/app/community/thread-detail/\133threadId\135/thread.scss" "b/src/app/community/thread-detail/\133threadId\135/thread.scss"
new file mode 100644
index 0000000..b2524f0
--- /dev/null
+++ "b/src/app/community/thread-detail/\133threadId\135/thread.scss"
@@ -0,0 +1,201 @@
+.thread-detail {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+
+ // 帖子头部
+ .thread-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+
+ .user-info {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+
+ .user-meta {
+ h3 {
+ margin: 0;
+ font-size: 1.25rem;
+ color: #2d3748;
+ }
+
+ span {
+ color: #718096;
+ font-size: 0.875rem;
+ }
+ }
+ }
+
+ .post-time {
+ color: #718096;
+ font-size: 0.875rem;
+ }
+ }
+
+ // 帖子内容
+ .thread-content {
+ h1 {
+ font-size: 2rem;
+ color: #1a202c;
+ margin-bottom: 1.5rem;
+ }
+
+ .content-body {
+ .thread-image {
+ width: 100%;
+ height: auto;
+ border-radius: 0.5rem;
+ margin-bottom: 1.5rem;
+ }
+
+ .thread-image {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ p {
+ font-size: 1rem;
+ line-height: 1.75;
+ color: #4a5568;
+ margin-bottom: 2rem;
+ }
+ }
+
+ .thread-actions {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 2rem;
+
+ .like-button {
+ &.liked {
+ background: #f49c79;
+ }
+ }
+ }
+ }
+
+ // 评论区域
+ .comments-section {
+ .comments-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ h2 {
+ font-size: 1.5rem;
+ color: #2d3748;
+ margin-bottom: 1.5rem;
+ }
+ }
+
+ // 评论输入区
+ .comments-input {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 1rem;
+ border-radius: 0.5rem;
+
+ .p-inputtext {
+ flex: 1; // 输入框占据剩余空间
+ height: 3rem;
+ }
+
+ .p-button {
+ height: 3rem;
+ }
+ }
+
+ // 评论列表
+ .comments-list {
+ display: flex;
+ flex-direction: column;
+ margin-top: 1rem;
+ gap: 0.5rem;
+
+ .comment-item {
+ padding: 1.5rem;
+ border-radius: 0.5rem;
+
+ .comment-user {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1rem;
+
+ .comment-meta {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ justify-content: space-between;
+ gap: 0.5rem;
+
+ .comment-time {
+ justify-content: space-between;
+ gap: 0.75rem
+ }
+
+ .username {
+ margin-left: 0.5rem;
+ font-weight: 600;
+ color: #2d3748;
+ }
+
+ .floor {
+ color: #718096;
+ margin-right: 0.75rem;
+ font-size: 0.875rem;
+ }
+
+ .time {
+ color: #a0aec0;
+ font-size: 0.875rem;
+ }
+ }
+ }
+
+ .comment-content {
+ padding-left: 3.5rem;
+
+ .reply-to {
+ display: inline-block;
+ color: #93C4C1;
+ font-size: 0.875rem;
+ margin-bottom: 0.5rem;
+ }
+
+ p {
+ color: #4a5568;
+ margin: 0;
+ line-height: 1.5;
+ }
+ }
+ }
+ }
+ }
+}
+
+.p-sidebar-header,
+.p-sidebar-custom-header {
+ padding: 10px !important;
+}
+
+.p-overlaypanel-content {
+ padding: 0 !important;
+}
+
+.reply {
+
+ .reply-input {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-self: center;
+ gap: 3rem;
+ padding: 1rem;
+ }
+}
\ No newline at end of file
diff --git a/src/app/globals.css b/src/app/globals.css
deleted file mode 100644
index e3734be..0000000
--- a/src/app/globals.css
+++ /dev/null
@@ -1,42 +0,0 @@
-:root {
- --background: #ffffff;
- --foreground: #171717;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
-}
-
-html,
-body {
- max-width: 100vw;
- overflow-x: hidden;
-}
-
-body {
- color: var(--foreground);
- background: var(--background);
- font-family: Arial, Helvetica, sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-* {
- box-sizing: border-box;
- padding: 0;
- margin: 0;
-}
-
-a {
- color: inherit;
- text-decoration: none;
-}
-
-@media (prefers-color-scheme: dark) {
- html {
- color-scheme: dark;
- }
-}
diff --git a/src/app/globals.scss b/src/app/globals.scss
new file mode 100644
index 0000000..77fe0da
--- /dev/null
+++ b/src/app/globals.scss
@@ -0,0 +1,170 @@
+$function-button-color: #93C4C1;
+$nomal-button-color: #526665;
+$heading-color: #2c3e50;
+$subheading-color: #93C4C1;
+$icon-color: #93C4C1;
+$background-color: #F0F4F7;
+
+.container {
+ display: flex;
+ flex-direction: column;
+ background-color: $background-color;
+ min-height: 100vh;
+}
+
+.toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem 2rem;
+ background-color: #FFF;
+ position: sticky;
+ top: 0;
+ z-index: 1000;
+
+}
+
+// ##第一部分:logo-name
+.logo-name {
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+
+ .logo {
+ width: 40px;
+ height: 40px;
+ }
+
+ .name {
+ font-size: 3rem;
+ font-weight: bold;
+ color: #333;
+ }
+}
+
+// ##第二部分:tools
+.tools {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+
+}
+
+.tools {
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+ .no-underline {
+ text-decoration: none;
+ }
+ .tool-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.25rem;
+
+ i {
+ font-size: 1.25rem;
+ color: $icon-color;
+ }
+
+ span {
+ font-size: 0.75rem;
+ color: #333;
+ }
+ }
+
+ .p-avatar {
+ margin-left: 1rem;
+ }
+}
+
+.logo-name {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+
+ .logo {
+ width: 40px;
+ height: 40px;
+ }
+
+ .name {
+ font-size: 1.25rem;
+ font-weight: bold;
+ }
+}
+
+// 全局容器样式
+.main-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 2rem;
+}
+
+//TabView组件样式
+.p-tabview {
+ background-color: #F0F4F7;
+}
+
+.p-tabview-panels {
+ background-color: #F0F4F7;
+}
+
+.p-tabview-nav {
+ background-color: #F0F4F7;
+}
+
+.p-tabview-nav-link {
+ background-color: #F0F4F7;
+}
+
+//分页样式
+.Paginator {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: #F0F4F7;
+
+ // 高亮状态的分页按钮
+ .p-highlight {
+ background-color: rgba(182, 238, 235, 0.4) !important;
+ color: #768786 !important;
+ border-color: #768786 !important;
+ }
+}
+
+// 大标题样式
+h1,
+.heading,
+.title {
+ color: $heading-color;
+ font-size: 2rem;
+ font-weight: bold;
+}
+
+
+// 搜索栏样式
+.communities-searchBar {
+ max-width: 600px;
+ margin: 2rem auto;
+ position: relative;
+
+ .pi-search {
+ position: absolute;
+ color: $icon-color;
+ left: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 1;
+ }
+
+ .search-helper {
+ width: 100%;
+ height: 3rem;
+ padding-left: 2.5rem;
+ border-radius: 1.5rem;
+ font-size: 1.1rem;
+ border: 1px solid #ddd;
+ }
+}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 42fc323..68e7de7 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,31 +1,73 @@
-import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
-import "./globals.css";
+// app/layout.tsx
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
+import type { Metadata } from 'next';
+import { Avatar } from 'primereact/avatar';
+// 页面跳转
+import Link from 'next/link';
+// PrimeReact 依赖
+import { PrimeReactProvider } from 'primereact/api';
+import 'primeicons/primeicons.css';
+import 'primereact/resources/themes/lara-light-teal/theme.css'; // 主题
+// 全局样式
+import './globals.scss';
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
-
+//设置标题
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: 'MCPT',
+ description: 'MCPT resource Platform',
};
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode;
-}>) {
+// 页首工具栏
+export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
- <html lang="en">
- <body className={`${geistSans.variable} ${geistMono.variable}`}>
- {children}
+ <html lang="zh">
+ <body>
+ <PrimeReactProvider>
+ <div className="container">
+ <header className="toolbar">
+ <div className="logo-name">
+ <Link href="/" className="no-underline">
+ <img src="/logo.png" alt="Logo" className="logo" />
+ </Link>
+
+ <span className="name">MCPT</span>
+ </div>
+ <div className="tools">
+ <Link href="/classification" className="no-underline">
+ <div className="tool-item">
+ <i className="pi pi-search" />
+ <span>搜索</span>
+ </div>
+ </Link>
+
+ <Link href="/community" className="no-underline">
+ <div className="tool-item">
+ <i className="pi pi-users" />
+ <span>社区</span>
+ </div>
+ </Link>
+
+ <Link href="/classification" className="no-underline">
+ <div className="tool-item">
+ <i className="pi pi-tags" />
+ <span>分类</span>
+ </div>
+ </Link>
+
+ <Link href="/notification" className="no-underline">
+ <div className="tool-item">
+ <i className="pi pi-bell" />
+ <span>通知</span>
+ </div>
+ </Link>
+ <Link href="/user" className="no-underline">
+ <Avatar image="/images/avatar/asiyajavayant.png" size="large" shape="circle" />
+ </Link>
+ </div>
+ </header>
+ <main className="mainContent">{children}</main>
+ </div>
+ </PrimeReactProvider>
</body>
</html>
);