Update client side. fix previous errors

main
Ernest Litvinenko 2025-02-07 09:07:20 +03:00
parent ac24e8bbd8
commit 056c41000d
19 changed files with 639 additions and 124 deletions

4
graphql.config.yml Normal file
View File

@ -0,0 +1,4 @@
schema:
- http://localhost:8000/api/v1/graphql:
headers:
Authorization: Bearer eyJ1c2VyX2lkIjogMSwgInNhbHQiOiAiMTkxNDZhIiwgImV4cGlyZXMiOiAxNzIxMjMyMzM3Ljk4MTUyN30HZ3bA-tzG2JebZhmLQJRmODYQj6_aM1ZEQ23uDJreKg==

View File

@ -1,4 +1,6 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = {}; const nextConfig = {
reactStrictMode: false
};
export default nextConfig; export default nextConfig;

245
package-lock.json generated
View File

@ -10,7 +10,11 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.3.7", "@ant-design/icons": "^5.3.7",
"@ant-design/nextjs-registry": "^1.0.0", "@ant-design/nextjs-registry": "^1.0.0",
"@apollo/client": "^3.10.8",
"@apollo/experimental-nextjs-app-support": "^0.11.2",
"antd": "^5.19.2", "antd": "^5.19.2",
"graphql": "^16.9.0",
"js-cookie": "^3.0.5",
"next": "14.2.5", "next": "14.2.5",
"react": "^18", "react": "^18",
"react-dom": "^18" "react-dom": "^18"
@ -111,6 +115,73 @@
"react": ">=16.9.0" "react": ">=16.9.0"
} }
}, },
"node_modules/@apollo/client": {
"version": "3.10.8",
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.10.8.tgz",
"integrity": "sha512-UaaFEitRrPRWV836wY2L7bd3HRCfbMie1jlYMcmazFAK23MVhz/Uq7VG1nwbotPb5xzFsw5RF4Wnp2G3dWPM3g==",
"dependencies": {
"@graphql-typed-document-node/core": "^3.1.1",
"@wry/caches": "^1.0.0",
"@wry/equality": "^0.5.6",
"@wry/trie": "^0.5.0",
"graphql-tag": "^2.12.6",
"hoist-non-react-statics": "^3.3.2",
"optimism": "^0.18.0",
"prop-types": "^15.7.2",
"rehackt": "^0.1.0",
"response-iterator": "^0.2.6",
"symbol-observable": "^4.0.0",
"ts-invariant": "^0.10.3",
"tslib": "^2.3.0",
"zen-observable-ts": "^1.2.5"
},
"peerDependencies": {
"graphql": "^15.0.0 || ^16.0.0",
"graphql-ws": "^5.5.5",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
"subscriptions-transport-ws": "^0.9.0 || ^0.11.0"
},
"peerDependenciesMeta": {
"graphql-ws": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
},
"subscriptions-transport-ws": {
"optional": true
}
}
},
"node_modules/@apollo/client-react-streaming": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/@apollo/client-react-streaming/-/client-react-streaming-0.11.2.tgz",
"integrity": "sha512-rRA/dIA09/Y6+jtGGBnXHQfPOv6BYYVZwQP8OzQtWrWbSgDEI6uAhqULssU5f0ZhQJVzKDuslqGE9QAX0gdfRQ==",
"dependencies": {
"ts-invariant": "^0.10.3"
},
"peerDependencies": {
"@apollo/client": "^3.10.4",
"react": "^18"
}
},
"node_modules/@apollo/experimental-nextjs-app-support": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/@apollo/experimental-nextjs-app-support/-/experimental-nextjs-app-support-0.11.2.tgz",
"integrity": "sha512-HRQ8/Ux/tM2pezrhZeoHsJs55+nJvJZRV1B21QwEVtWhslQXjT5gqs5nKw86KURF0xR7gX18Nyy659NzJ09Pmw==",
"dependencies": {
"@apollo/client-react-streaming": "0.11.2"
},
"peerDependencies": {
"@apollo/client": "^3.10.4",
"next": "^13.4.1 || ^14.0.0 || 15.0.0-rc.0",
"react": "^18"
}
},
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.24.8", "version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz",
@ -196,6 +267,14 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@graphql-typed-document-node/core": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
"integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
"peerDependencies": {
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.14", "version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@ -816,6 +895,50 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true "dev": true
}, },
"node_modules/@wry/caches": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.1.tgz",
"integrity": "sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@wry/context": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.4.tgz",
"integrity": "sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@wry/equality": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.7.tgz",
"integrity": "sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@wry/trie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.5.0.tgz",
"integrity": "sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.12.1", "version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@ -2632,6 +2755,28 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true "dev": true
}, },
"node_modules/graphql": {
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz",
"integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==",
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
"node_modules/graphql-tag": {
"version": "2.12.6",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
"integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==",
"dependencies": {
"tslib": "^2.1.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
}
},
"node_modules/has-bigints": { "node_modules/has-bigints": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
@ -2713,6 +2858,14 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@ -3216,6 +3369,14 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -3560,7 +3721,6 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -3701,6 +3861,28 @@
"wrappy": "1" "wrappy": "1"
} }
}, },
"node_modules/optimism": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.0.tgz",
"integrity": "sha512-tGn8+REwLRNFnb9WmcY5IfpOqeX2kpaYJ1s6Ae3mn12AeydLkR3j+jSCmVQFoXqU8D41PAJ1RG1rCRNWmNZVmQ==",
"dependencies": {
"@wry/caches": "^1.0.0",
"@wry/context": "^0.7.0",
"@wry/trie": "^0.4.3",
"tslib": "^2.3.0"
}
},
"node_modules/optimism/node_modules/@wry/trie": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.4.3.tgz",
"integrity": "sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@ -4024,7 +4206,6 @@
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@ -4663,8 +4844,7 @@
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
"dev": true
}, },
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
@ -4731,6 +4911,23 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/rehackt": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.1.0.tgz",
"integrity": "sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==",
"peerDependencies": {
"@types/react": "*",
"react": "*"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/resize-observer-polyfill": { "node_modules/resize-observer-polyfill": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
@ -4771,6 +4968,14 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
} }
}, },
"node_modules/response-iterator": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/response-iterator/-/response-iterator-0.2.6.tgz",
"integrity": "sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/reusify": { "node_modules/reusify": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@ -5308,6 +5513,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/symbol-observable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
"integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.4", "version": "3.4.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
@ -5424,6 +5637,17 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true "dev": true
}, },
"node_modules/ts-invariant": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz",
"integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==",
"dependencies": {
"tslib": "^2.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tsconfig-paths": { "node_modules/tsconfig-paths": {
"version": "3.15.0", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -5808,6 +6032,19 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zen-observable": {
"version": "0.8.15",
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
},
"node_modules/zen-observable-ts": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz",
"integrity": "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==",
"dependencies": {
"zen-observable": "0.8.15"
}
} }
} }
} }

View File

@ -11,7 +11,12 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.3.7", "@ant-design/icons": "^5.3.7",
"@ant-design/nextjs-registry": "^1.0.0", "@ant-design/nextjs-registry": "^1.0.0",
"@apollo/client": "^3.10.8",
"@apollo/experimental-nextjs-app-support": "^0.11.2",
"antd": "^5.19.2", "antd": "^5.19.2",
"axios": "^1.7.9",
"graphql": "^16.9.0",
"js-cookie": "^3.0.5",
"next": "14.2.5", "next": "14.2.5",
"react": "^18", "react": "^18",
"react-dom": "^18" "react-dom": "^18"

8
src/app/auth/loading.js Normal file
View File

@ -0,0 +1,8 @@
import {cookies} from "next/headers";
import {redirect} from "next/navigation";
export default function Loading() {
if (cookies().get("session_token")) {
return redirect('/my')
}
}

View File

@ -1,16 +1,46 @@
"use client" "use client"
import {Form, Button, Input} from "antd"; import {Form, Button, Input, message} from "antd";
import {useRouter} from "next/navigation";
const msgLoginSuccess = () => message.success("Вы успешно вошли в систему")
const msgLoginError = () => message.error("Неверный логин или пароль")
const AuthPage = () => { const AuthPage = () => {
const router = useRouter()
const onFinish = (values) => {
fetch("http://localhost:8000/api/v1/auth", {
headers: {
"Content-Type": "application/json"
},
credentials: "include",
method: "POST",
body: JSON.stringify(values)
}).then((response) => {
if (response.ok) {
msgLoginSuccess()
router.push("/my")
} else {
msgLoginError()
}
})
};
return ( return (
<> <>
<Form> <Form
<Form.Item label={"Логин"}> onFinish={onFinish}
<Input /> onSubmitCapture={(e) => {
e.preventDefault()
}}
>
<Form.Item name={"login"} label={"Логин"}
rules={[{required: true, message: "Введите e-mail или логин"}]}>
<Input/>
</Form.Item> </Form.Item>
<Form.Item label={"Пароль"}> <Form.Item name={"password"} label={"Пароль"} rules={[{required: true, message: "Введите пароль"}]}>
<Input.Password></Input.Password> <Input.Password></Input.Password>
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>

View File

@ -2,29 +2,6 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
/*:root {*/
/* --foreground-rgb: 0, 0, 0;*/
/* --background-start-rgb: 214, 219, 220;*/
/* --background-end-rgb: 255, 255, 255;*/
/*}*/
/*@media (prefers-color-scheme: dark) {*/
/* :root {*/
/* --foreground-rgb: 255, 255, 255;*/
/* --background-start-rgb: 0, 0, 0;*/
/* --background-end-rgb: 0, 0, 0;*/
/* }*/
/*}*/
/*body {*/
/* color: rgb(var(--foreground-rgb));*/
/* background: linear-gradient(*/
/* to bottom,*/
/* transparent,*/
/* rgb(var(--background-end-rgb))*/
/* )*/
/* rgb(var(--background-start-rgb));*/
/*}*/
@layer utilities { @layer utilities {
.text-balance { .text-balance {

View File

@ -3,6 +3,7 @@ import "./globals.css";
import {Col, ConfigProvider, Layout, Row, Typography} from "antd"; import {Col, ConfigProvider, Layout, Row, Typography} from "antd";
import Logo from "@/components/header/logo"; import Logo from "@/components/header/logo";
import {AntdRegistry} from "@ant-design/nextjs-registry"; import {AntdRegistry} from "@ant-design/nextjs-registry";
import Link from "next/link";
const inter = Inter({subsets: ["latin"]}); const inter = Inter({subsets: ["latin"]});
@ -17,27 +18,33 @@ export default function RootLayout({children}) {
<html lang="en"> <html lang="en">
<ConfigProvider <ConfigProvider
theme={{ theme={{
token: {
colorPrimary: "#E5332A",
},
components: { components: {
// Button: {
// colorPrimary: "#E5332A",
// colorText: "#E5332A",
// colorPrimaryHover: "#ea6761",
// },
Typography: { Typography: {
titleMarginBottom: 0 titleMarginBottom: 0
}, },
} }
}}> }}>
<AntdRegistry> <AntdRegistry>
<body className={inter.className}> <body className={inter.className}>
<header className={"mb-14"}> <header className={"mb-14"}>
<Row className={"items-center py-5"}> <Row className={"items-center py-5"}>
<Col span={6} offset={3}> <Col span={6} offset={3}>
<Logo/> <Link href={"/"}><Logo/></Link>
</Col> </Col>
<Col> <Col>
<span className={"font-bold text-2xl"}>Система Обновлений MPUpdate</span> <span className={"font-bold text-2xl"}>Система Обновлений MPUpdate</span>
</Col> </Col>
</Row> </Row>
</header> </header>
{children} {children}
</body> </body>
</AntdRegistry> </AntdRegistry>
</ConfigProvider> </ConfigProvider>

View File

@ -0,0 +1,40 @@
"use client"
import {Col, Row} from "antd";
import UpdationTable from "@/components/my/application/updationTable";
import Title from "@/components/my/application/title"
import AddUpdate from "@/components/my/application/addUpdateModal";
import {useEffect, useState} from "react";
import AppService from "@/services/appService";
const ApplicationPage = ({params: { appId }}) => {
const [isLoading, setIsLoading] = useState(false)
const [appData, setAppData] = useState({
})
useEffect(() => {
setIsLoading(true)
AppService.listApplications().then((res) => {
console.log(res.data[appId])
setAppData(res.data[appId])
setIsLoading(false)
})
}, [appId]);
return (
<>
<Row className={"items-center mb-4"}>
<Col span={12}>
<Title>{appData.appName}</Title>
</Col>
<AddUpdate appId={appData.id}/>
</Row>
<UpdationTable dataSource={appData.versions} />
</>
)
}
export default ApplicationPage

14
src/app/my/layout.js Normal file
View File

@ -0,0 +1,14 @@
import {Col, Row} from "antd";
export const metadata = {
title: "Список приложений | Система обновлений MPUpdate",
};
export default function AppWrapper({children}) {
return (
<Row className={"justify-center items-center"}>
<Col span={16}>{children}</Col>
</Row>
)
}

View File

@ -1,13 +1,27 @@
"use client" "use client"
import {Button, Col, Form, Input, Modal, Row, Table, Typography} from "antd"; import {Button, Col, Row, Table, Typography} from "antd";
import {useState} from "react"; import {useEffect, useState} from "react";
import CreateAppModal from "@/components/my/createAppModal"; import CreateAppModal from "@/components/my/createAppModal";
import {useRouter} from "next/navigation";
import AppService from "@/services/appService";
const MyPage = () => { const MyPage = () => {
const [apps, setApps] = useState([])
const [isLoading, setIsLoading] = useState(true)
const router = useRouter()
const [isCreateAppModalOpened, setIsCreateAppModalOpened] = useState(false) const [isCreateAppModalOpened, setIsCreateAppModalOpened] = useState(false)
useEffect(() => {
setIsLoading(true)
AppService.listApplications().then((res) => {
setApps(Object.values(res.data))
setIsLoading(false)
})
}, []);
const handleOkIsCreateAppModalOpened = () => { const handleOkIsCreateAppModalOpened = () => {
setIsCreateAppModalOpened(false) setIsCreateAppModalOpened(false)
} }
@ -18,37 +32,33 @@ const MyPage = () => {
return ( return (
<> <>
<Row className={"justify-center items-center"}> <Row className={"mb-4"}>
<Col span={16}> <Col span={12}>
<Row className={"mb-4 items-center justify-between"}> <Typography.Title className={"pb-0 mb-0"}>Список приложений</Typography.Title>
<Col>
<Typography.Title className={"pb-0 mb-0"}>Список приложений</Typography.Title>
</Col>
<Col>
<Button type={"primary"} onClick={() => setIsCreateAppModalOpened(true)}>Добавить приложение</Button>
</Col>
</Row>
<Table>
<Table.Column title={"Приложение"} dataIndex={"appName"}/>
<Table.Column title={"Описание"} dataIndex={"name"} key={"name"}/>
<Table.Column title={"Последняя версия"} dataIndex={"email"} key={"email"}/>
<Table.Column render={(d) => {
return <Button onClick={() => {
console.log(d);
}}>Изменить</Button>
}
}></Table.Column>
</Table>
</Col> </Col>
<Col offset={6} span={6}>
<Button type={"primary"} className={"w-full"} onClick={() => setIsCreateAppModalOpened(true)}>Добавить
приложение</Button>
</Col>
</Row> </Row>
<Table dataSource={apps} loading={isLoading}>
<Table.Column title={"Приложение"} dataIndex={"appName"}/>
<Table.Column title={"Описание"} dataIndex={"description"}/>
<Table.Column title={"Последняя версия"} render={(d) => <span>{d.versions[0].id}</span>}/>
<Table.Column render={(d) => {
return <Button onClick={() => {
router.push(`/my/application/${d.id}`)
}}>Изменить</Button>
}
}></Table.Column>
</Table>
<CreateAppModal isOpen={isCreateAppModalOpened} handleCancel={handleCancelCreateAppModal}/> <CreateAppModal isOpen={isCreateAppModalOpened} handleCancel={handleCancelCreateAppModal}/>
</> </>
) )
} }
export default MyPage; export default MyPage;

View File

@ -1,9 +1,5 @@
import Image from "next/image"; import {redirect} from "next/navigation";
export default function Home() { export default function Home() {
return ( return redirect("/my")
<main>
</main>
);
} }

View File

@ -0,0 +1,88 @@
"use client"
import {Button, Col, Form, Input, Modal, Progress, Row, Typography, Upload} from "antd";
import {useState} from "react";
import AppService from "@/services/appService";
export function AddUpdateModalBtn(setIsModalVisible) {
return (
<Button onClick={() => setIsModalVisible(true)}>Добавить обновление</Button>
)
}
export function AddUpdateModal({isVisible, onOk, onCancel}) {
const [description, setDescription] = useState("")
const [versionId, setVersionId] = useState("")
const [link, setLink] = useState("")
const [progressUpload, setProgressUpload] = useState(0)
const sendFileHandler = (file) => {
AppService.uploadApp(file, {onProgress: (p) => setProgressUpload(p)}).then(d => {
setLink(d.link)
})
}
return (
<Modal open={isVisible} onOk={() => {}} onCancel={onCancel} okText={"Добавить обновление"} footer={() => <></>}>
<Row className={"mt-10 w-full"}>
<Col span={24}>
<Form>
<Row className={"items-center"}>
<Col span={11} className={"mr-4"}>
<Form.Item label={"Описание"} layout={"horizontal"} name={"description"} required={true}>
<Input.TextArea placeholder={"Введите описание для обновления"} value={description} onChange={e => setDescription(e.target.value)}/>
</Form.Item>
</Col>
<Col span={11}>
<Form.Item label={"Версия"} required={true} name={"version"}>
<Input placeholder={"0.0.1"} value={versionId} onChange={e => setVersionId(e.target.value)}/>
</Form.Item>
</Col>
</Row>
<Row>
<Col span={24}>
<Form.Item name={"link"}>
<Progress percent={progressUpload}/>
<input type="file" onChange={e => sendFileHandler(e.target.files[0])}/>
</Form.Item>
</Col>
</Row>
</Form>
</Col>
</Row>
<Button type={"primary"} onClick={() => onOk({id: versionId, description, link})}>Добавить обновление</Button>
</Modal>
)
}
export default function AddUpdate({appId}) {
const [isModalVisible, setIsModalVisible] = useState(false);
const onOkCb = ({id, link, description}) => {
if (id )
AppService.addVersion({id, appId, link, description}).then(d => {
setIsModalVisible(false)
})
}
const onCancelCb = () => {
setIsModalVisible(false)
}
return (
<>
<Col span={6} offset={6}>
<Button onClick={() => setIsModalVisible(true)} type={"primary"} className={"w-full"}>Добавить
обновление</Button>
</Col>
<AddUpdateModal isVisible={isModalVisible} onOk={onOkCb} onCancel={onCancelCb}/>
</>
)
}

View File

@ -0,0 +1,6 @@
"use client"
import {Typography} from "antd";
export default function Title({children}) {
return <Typography.Title>{children}</Typography.Title>
}

View File

@ -0,0 +1,17 @@
"use client"
import {Button, Table} from "antd";
export default function UpdationTable({dataSource}) {
return (
<div>
<Table dataSource={dataSource}>
<Table.Column title={"Версия"} dataIndex={"id"}/>
<Table.Column title={"Описание"} dataIndex={"description"}/>
<Table.Column title={"Код Версии"} dataIndex={"versionCode"}/>
<Table.Column title={"Ссылка"} dataIndex={"link"} render={(e) => <a href={e}><Button type={"link"}>Скачать</Button></a> }/>
</Table>
</div>
)
}

View File

@ -1,48 +1,42 @@
import {Button, Col, Divider, Form, Input, message, Modal, Row, Space, Typography} from "antd"; import {Button, Col, Divider, Form, Input, message, Modal, Progress, Row, Space, Typography} from "antd";
import Upload from "@/components/my/upload";
import {useState} from "react"; import {useState} from "react";
import {useRouter} from "next/navigation";
import AppService from "@/services/appService";
const uploadProps = {
name: 'file',
multiple: false,
action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload',
beforeUpload: (file) => {
const isAPK = file.type === 'application/vnd.android.package-archive';
if (!isAPK) {
message.error(`${file.name} не является APK файлом`);
}
return isAPK || Upload.LIST_IGNORE;
},
onChange(info) {
const { status } = info.file;
if (status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`);
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
onDrop(e) {
console.log('Dropped files', e.dataTransfer.files);
},
};
const CreateAppModal = ({isOpen, handleCancel}) => { const CreateAppModal = ({isOpen, handleCancel}) => {
const router = useRouter()
const [isTitleFocused, setIsTitleFocused] = useState(false) const [isTitleFocused, setIsTitleFocused] = useState(false)
const [appName, setAppName] = useState("Название приложения") const [appName, setAppName] = useState("Название приложения")
const [appVersion, setAppVersion] = useState("")
const [appDescription, setAppDescription] = useState("")
const [link, setLink] = useState()
const [appId, setAppId] = useState()
const [progressUpload, setProgressUpload] = useState(0)
const sendFileHandler = (file) => {
AppService.uploadApp(file, {onProgress: (p) => setProgressUpload(p)}).then(d => {
setLink(d.link)
})
}
return ( return (
<Modal <Modal
open={isOpen} open={isOpen}
onOk={handleCancel} onOk={() => AppService.createApp({
id: appId,
appName,
version: appVersion,
description: appDescription,
link
}).then(() => {
handleCancel()
router.refresh()
})}
onCancel={handleCancel} onCancel={handleCancel}
width={"50%"} width={"50%"}
> >
@ -51,21 +45,28 @@ const CreateAppModal = ({isOpen, handleCancel}) => {
<Divider className={"!mt-0"}/> <Divider className={"!mt-0"}/>
<Form> <Form>
<Form.Item required> <Form.Item required name={"name"}>
<Typography.Title level={5} className={isTitleFocused ? "hidden": "inline"} onClick={() => setIsTitleFocused(true)}>{appName}</Typography.Title> <Typography.Title level={5} className={isTitleFocused ? "hidden": "inline"} onClick={() => setIsTitleFocused(true)}>{appName}</Typography.Title>
<Input value={appName} onChange={e => setAppName(e.target.value)} className={isTitleFocused ? "inline": "!hidden"} /> <Input value={appName} onChange={e => setAppName(e.target.value)} className={isTitleFocused ? "inline": "!hidden"} />
<Button type={"primary"} onClick={() => setIsTitleFocused(false)} className={isTitleFocused ? "inline mt-4": "!hidden"} >Сохранить</Button> <Button type={"primary"} onClick={() => setIsTitleFocused(false)} className={isTitleFocused ? "inline mt-4": "!hidden"} >Сохранить</Button>
</Form.Item> </Form.Item>
<Row className={"items-center"}> <Row className={"items-center"}>
<Col span={11} className={"mr-4"}> <Col span={11} className={"mr-4"}>
<Form.Item label={"Описание"} layout={"horizontal"}> <Form.Item label={"Идентификатор"} layout={"horizontal"} name={"id"} required={true}>
<Input.TextArea placeholder={"Введите описание для приложения"}/> <Input.TextArea placeholder={"com.example.mp_update"} value={appId} onChange={e => setAppId(e.target.value)}/>
</Form.Item>
</Col>
<Col span={11} className={"mr-4"}>
<Form.Item label={"Описание"} layout={"horizontal"} name={"description"} required={true}>
<Input.TextArea placeholder={"Введите описание для приложения"} value={appDescription} onChange={e => setAppDescription(e.target.value)}/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={11}> <Col span={11}>
<Form.Item label={"Версия"} required={true}> <Form.Item label={"Версия"} required={true} name={"version"}>
<Input placeholder={"0.0.1"}/> <Input placeholder={"0.0.1"} value={appVersion} onChange={e => setAppVersion(e.target.value)}/>
</Form.Item> </Form.Item>
</Col> </Col>
@ -73,11 +74,12 @@ const CreateAppModal = ({isOpen, handleCancel}) => {
<Row> <Row>
<Col span={24}> <Col span={24}>
<Upload {...uploadProps}/> <Form.Item name={"link"}>
<Progress percent={progressUpload}/>
<input type="file" onChange={e => sendFileHandler(e.target.files[0])}/>
</Form.Item>
</Col> </Col>
</Row> </Row>
</Form> </Form>
</Modal> </Modal>
) )

View File

@ -1,18 +1,30 @@
import {Upload} from 'antd'; import {Upload} from 'antd';
import {InboxOutlined} from "@ant-design/icons"; import {InboxOutlined} from "@ant-design/icons";
import AppService from "@/services/appService";
const UploadComponent = (props) => {
return ( const uploadHandler = () => {
<Upload.Dragger {...props}> AppService.uploadApp()
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">Загрузка .apk файла</p>
<p className="ant-upload-hint">
Выберите или перетащите файл в данную область
</p>
</Upload.Dragger>
)
} }
// const UploadComponent = (props) => {
// return (
// <Upload.Dragger {...props} action={(file) => {
// return new Promise((resolve, reject) => {
// AppService.uploadApp(file).then(d =>
// resolve(d.data.link)
// )
// })
// }}>
// <p className="ant-upload-drag-icon">
// <InboxOutlined />
// </p>
// <p className="ant-upload-text">Загрузка .apk файла</p>
// <p className="ant-upload-hint">
// Выберите или перетащите файл в данную область
// </p>
// </Upload.Dragger>
// )
// }
export default UploadComponent; export default UploadComponent;

View File

@ -0,0 +1,50 @@
import $instance, {BASE_URL} from "@/services/default";
export default class AppService {
static async listApplications() {
return await $instance.get("/updates/list-app")
}
static uploadApp(file, {onProgress}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.upload.onprogress = (event) => {
onProgress?.(Math.round((event.loaded / event.total) * 100));
};
xhr.open("POST", `${BASE_URL}/updates/upload`)
xhr.onload = () => {
xhr.status === 200 ? resolve(JSON.parse(xhr.response)) : reject(xhr.response)
}
const fileData = new FormData();
fileData.append("version", file)
xhr.send(fileData)
})
// return await $instance.postForm("/updates/upload", {
// "version": file
// }, {
// headers: {
// "Content-Type": "multipart/form-data"
// }
// })
}
static async createApp({id, appName, version, description, link}) {
return await $instance.post("/updates/create-app", {
id, appName, version, description, link
})
}
static async addVersion({id, appId, link, description}) {
return await $instance.post("/updates/create", {
id, appId, link, description
})
}
}

10
src/services/default.js Normal file
View File

@ -0,0 +1,10 @@
import axios from "axios";
export const BASE_URL = "http://localhost:8001/api"
const $instance = axios.create({
baseURL: BASE_URL
})
export default $instance;