Merge pull request 'make translate for login, register pages' (#1) from translate into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
+3
-1
@@ -1,7 +1,9 @@
|
||||
import type { NextConfig } from "next";
|
||||
import createNextIntlPlugin from 'next-intl/plugin';
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* output: 'export' */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
const withNextIntl = createNextIntlPlugin();
|
||||
export default withNextIntl(nextConfig);
|
||||
Generated
+375
-1
@@ -14,6 +14,7 @@
|
||||
"jose": "^6.1.2",
|
||||
"next": "^16.0.7",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"next-intl": "^4.5.8",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
@@ -485,6 +486,66 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz",
|
||||
"integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "2.2.7",
|
||||
"@formatjs/intl-localematcher": "0.6.2",
|
||||
"decimal.js": "^10.4.3",
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz",
|
||||
"integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/fast-memoize": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
|
||||
"integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||
"version": "2.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz",
|
||||
"integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "2.3.6",
|
||||
"@formatjs/icu-skeleton-parser": "1.8.16",
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||
"version": "1.8.16",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz",
|
||||
"integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "2.3.6",
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz",
|
||||
"integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "2"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -1592,6 +1653,178 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@schummar/icu-type-parser": {
|
||||
"version": "1.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
|
||||
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.3.tgz",
|
||||
"integrity": "sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-x64": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.3.tgz",
|
||||
"integrity": "sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.3.tgz",
|
||||
"integrity": "sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.3.tgz",
|
||||
"integrity": "sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-musl": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.3.tgz",
|
||||
"integrity": "sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.3.tgz",
|
||||
"integrity": "sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.3.tgz",
|
||||
"integrity": "sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.3.tgz",
|
||||
"integrity": "sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.3.tgz",
|
||||
"integrity": "sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-x64-msvc": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.3.tgz",
|
||||
"integrity": "sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
@@ -1601,6 +1834,15 @@
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/types": {
|
||||
"version": "0.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
|
||||
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.17",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz",
|
||||
@@ -3166,6 +3408,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
@@ -4408,6 +4656,18 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat": {
|
||||
"version": "10.7.18",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz",
|
||||
"integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "2.3.6",
|
||||
"@formatjs/fast-memoize": "2.2.7",
|
||||
"@formatjs/icu-messageformat-parser": "2.11.4",
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-array-buffer": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||
@@ -5409,6 +5669,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "16.0.7",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.0.7.tgz",
|
||||
@@ -5488,6 +5757,91 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl": {
|
||||
"version": "4.5.8",
|
||||
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.5.8.tgz",
|
||||
"integrity": "sha512-BdN6494nvt09WtmW5gbWdwRhDDHC/Sg7tBMhN7xfYds3vcRCngSDXat81gmJkblw9jYOv8zXzzFJyu5VYXnJzg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/amannn"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.5.4",
|
||||
"@swc/core": "^1.15.2",
|
||||
"negotiator": "^1.0.0",
|
||||
"next-intl-swc-plugin-extractor": "^4.5.8",
|
||||
"po-parser": "^1.0.2",
|
||||
"use-intl": "^4.5.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl-swc-plugin-extractor": {
|
||||
"version": "4.5.8",
|
||||
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.5.8.tgz",
|
||||
"integrity": "sha512-hscCKUv+5GQ0CCNbvqZ8gaxnAGToCgDTbL++jgCq8SCk/ljtZDEeQZcMk46Nm6Ynn49Q/JKF4Npo/Sq1mpbusA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@swc/core": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.3.tgz",
|
||||
"integrity": "sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.25"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.15.3",
|
||||
"@swc/core-darwin-x64": "1.15.3",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.3",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.3",
|
||||
"@swc/core-linux-arm64-musl": "1.15.3",
|
||||
"@swc/core-linux-x64-gnu": "1.15.3",
|
||||
"@swc/core-linux-x64-musl": "1.15.3",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.3",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.3",
|
||||
"@swc/core-win32-x64-msvc": "1.15.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": ">=0.5.17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/helpers": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@swc/helpers": {
|
||||
"version": "0.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
@@ -5762,6 +6116,12 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/po-parser": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/po-parser/-/po-parser-1.0.2.tgz",
|
||||
"integrity": "sha512-yTIQL8PZy7V8c0psPoJUx7fayez+Mo/53MZgX9MPuPHx+Dt+sRPNuRbI+6Oqxnddhkd68x4Nlgon/zizL1Xg+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@@ -6767,7 +7127,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -6903,6 +7263,20 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl": {
|
||||
"version": "4.5.8",
|
||||
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.5.8.tgz",
|
||||
"integrity": "sha512-rWPV2Sirw55BQbA/7ndUBtsikh8WXwBrUkZJ1mD35+emj/ogPPqgCZdv1DdrEFK42AjF1g5w8d3x8govhqPH6Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "^2.2.0",
|
||||
"@schummar/icu-type-parser": "1.21.5",
|
||||
"intl-messageformat": "^10.5.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"jose": "^6.1.2",
|
||||
"next": "^16.0.7",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"next-intl": "^4.5.8",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import Providers from '@/app/providers/getQueryServer'
|
||||
import type { Metadata } from "next";
|
||||
import { NextIntlClientProvider, hasLocale } from 'next-intl';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { routing } from '@/i18n/routing';
|
||||
import "../styles/globals.css";
|
||||
import "../styles/global-styles.scss";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return routing.locales.map((locale: any) => ({ locale }));
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "No copy",
|
||||
description: "No copy",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}>) {
|
||||
const { locale } = await params;
|
||||
if (!hasLocale(routing.locales, locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<html lang={locale}>
|
||||
<body>
|
||||
<NextIntlClientProvider>
|
||||
<Providers>
|
||||
{children}
|
||||
</Providers>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -3,45 +3,58 @@ import styles from '@/app/styles/login.module.scss'
|
||||
import LogoIcon from '@/app/ui/logo-icon';
|
||||
import Link from 'next/link';
|
||||
import LoginForm from '@/app/ui/login-form';
|
||||
import LanguageSwitcher from '@/app/components/LanguageSwitcher';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Login',
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const t = useTranslations('Login-register-form');
|
||||
|
||||
return (
|
||||
<div className={`${styles['login-container-wrapper']}`}>
|
||||
<div className={`${styles['login-container-language']}`}>
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
<div className={`${styles['login-container']}`}>
|
||||
<div className={`${styles['logo']}`}>
|
||||
<LogoIcon />
|
||||
<h1>NO COPY</h1>
|
||||
<p>Защита авторских прав</p>
|
||||
<p>
|
||||
{t('copyright-protection')}
|
||||
</p>
|
||||
</div>
|
||||
<LoginForm />
|
||||
<div className="divider">
|
||||
<span>или войти через</span>
|
||||
<span>
|
||||
{t('or-log-in-via')}
|
||||
</span>
|
||||
</div>
|
||||
<div className={`${styles['register-link']}`}>
|
||||
<p>Нет аккаунта? <Link href="register">Зарегистрироваться</Link></p>
|
||||
<p>{t('no-account')}?
|
||||
<Link href="register"> {t('register')}</Link>
|
||||
</p>
|
||||
</div>
|
||||
<div className={`${styles['features']}`}>
|
||||
<div className={`${styles['feature']}`}>
|
||||
<svg className={`${styles['feature-icon']}`} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
Защита контента скрытыми водяными знаками
|
||||
{t('protecting-watermark')}
|
||||
</div>
|
||||
<div className={`${styles['feature']}`}>
|
||||
<svg className={`${styles['feature-icon']}`} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
Мониторинг нарушений в реальном времени
|
||||
{t('monitoring-violations')}
|
||||
</div>
|
||||
<div className={`${styles['feature']}`}>
|
||||
<svg className={`${styles['feature-icon']}`} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 1L3 5v6c0 5.55 3.84 9.74 9 11 5.16-1.26 9-5.45 9-11V5l-9-4z"></path>
|
||||
</svg>
|
||||
Надежное хранение и безопасность данных
|
||||
{t('safe-storage')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,17 +1,21 @@
|
||||
import styles from "@/app/styles/page.module.scss";
|
||||
import { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Home page',
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
const t = useTranslations('HomePage');
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<main className={styles.main}>
|
||||
<div className={styles.intro}>
|
||||
Home page
|
||||
{t('title')}
|
||||
</div>
|
||||
<Link key={'dashboard'}
|
||||
href={'/pages/dashboard'}
|
||||
@@ -1,25 +1,26 @@
|
||||
import { Metadata } from 'next';
|
||||
import { Suspense } from 'react';
|
||||
import ButtonTest from '@/app/ui/button-test'
|
||||
import ProtectionOverview from '@/app/ui/dashboard/protection-overview';
|
||||
import StatsGrid from '@/app/ui/dashboard/stats-grid';
|
||||
import StorageBreakdown from '@/app/ui/dashboard/storage-breakdown';
|
||||
import RecentFilesList from '@/app/ui/dashboard/recent-files-list';
|
||||
import ChartContainer from '@/app/ui/dashboard/chart-container';
|
||||
import UserInfo from '@/app/ui/dashboard/user-info';
|
||||
import ViolationsTable from '@/app/ui/dashboard/violations-table';
|
||||
import ViolationsTypeTable from '@/app/ui/dashboard/violations-type-table';
|
||||
import ActivityChart from '@/app/ui/dashboard/activity-chart';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Dashboard',
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const t = useTranslations('Global');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<>...</>}>
|
||||
<h1 className="page-title">Статистика</h1>
|
||||
<h1 className="page-title">
|
||||
{t("statistics")}
|
||||
</h1>
|
||||
<ProtectionOverview />
|
||||
<StatsGrid />
|
||||
<StorageBreakdown />
|
||||
@@ -1,6 +1,6 @@
|
||||
import NavLinks from '@/app/ui/nav-links'
|
||||
import styles from '@/app/styles/page.module.scss'
|
||||
import HeaderPanel from '../ui/header/headerPanel';
|
||||
import HeaderPanel from '@/app/ui/header/headerPanel';
|
||||
import { getQueryClient } from '@/app/providers/getQueryClient';
|
||||
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
|
||||
import { getUserData } from '@/app/actions/action';
|
||||
@@ -2,26 +2,39 @@ import LogoIcon from '@/app/ui/logo-icon';
|
||||
import styles from '@/app/styles/login.module.scss'
|
||||
import Link from 'next/link';
|
||||
import RegisterForm from '@/app/ui/register-form';
|
||||
import LanguageSwitcher from '@/app/components/LanguageSwitcher';
|
||||
import {useTranslations} from 'next-intl';
|
||||
|
||||
export default function Page() {
|
||||
const t = useTranslations('Login-register-form');
|
||||
|
||||
return (
|
||||
<div className={`${styles['login-container-wrapper']}`}>
|
||||
<div className={`${styles['login-container-language']}`}>
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
<div className={`${styles['login-container']}`}>
|
||||
<div className={`${styles['logo']}`}>
|
||||
<LogoIcon />
|
||||
<h1>NO COPY</h1>
|
||||
<p>Создать аккаунт</p>
|
||||
<p>
|
||||
{t('create-an-account')}
|
||||
</p>
|
||||
</div>
|
||||
<div className={`${styles['subscription-info']}`}>
|
||||
<h4>🎉 Бесплатный старт</h4>
|
||||
<p>При регистрации вы получаете подписку "ДЕМО" с возможностью маркировки до 3 файлов в месяц и 10 МБ хранилища.</p>
|
||||
<h4>🎉 {t('free-start')}</h4>
|
||||
<p>
|
||||
{t('free-start-description')}
|
||||
</p>
|
||||
</div>
|
||||
<RegisterForm />
|
||||
<div className="divider">
|
||||
<span>или зарегистрироваться через</span>
|
||||
<span>
|
||||
{t('or-register-via')}
|
||||
</span>
|
||||
</div>
|
||||
<div className={`${styles['register-link']}`}>
|
||||
<p>Уже есть аккаунт? <Link href="login">Войти</Link></p>
|
||||
<p>{t('already-have-an-account')}? <Link href="login">{t('login')}</Link></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@ export async function authorization(
|
||||
let parsed = await response.json();
|
||||
await createSession(parsed.token, parsed.email);
|
||||
} else {
|
||||
throw (`Запрос завершился с ошибкой: ${response.status}`);
|
||||
throw ('request-ended-with-an-error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -146,7 +146,7 @@ export async function registration(
|
||||
path: string
|
||||
}) => {
|
||||
if (errors[obj.path[0]]) {
|
||||
errors[obj.path[0]] = `${errors[obj.path[0]]} ${obj.message}`;
|
||||
errors[obj.path[0]] = `${errors[obj.path[0]]}&${obj.message}`;
|
||||
} else {
|
||||
errors[obj.path[0]] = obj.message;
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export async function registration(
|
||||
|
||||
try {
|
||||
const { full_name, email, password, phone } = validatedFields.data;
|
||||
const response = await fetch(`${API_BASE_URL}/v1/api/auth/register`, {
|
||||
const response = await fetch(`${API_BASE_URL}/v1/api/auth/register1`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ fullName: full_name, email, password, companyName: company, phone: phone }),
|
||||
headers: {
|
||||
@@ -186,7 +186,7 @@ export async function registration(
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
const errors: Record<string, string> = {};
|
||||
errors['server'] = `Запрос завершился с ошибкой: ${error}`
|
||||
errors['server'] = 'request-ended-with-an-error'
|
||||
|
||||
return {
|
||||
previousState: {
|
||||
|
||||
@@ -20,26 +20,26 @@ export const SignupFormSchema = z
|
||||
.object({
|
||||
full_name: z
|
||||
.string()
|
||||
.min(3, { error: 'Имя должно содержать не менее 3 символов.' })
|
||||
.min(3, { error: 'register-error-name' })
|
||||
.trim(),
|
||||
email: z.email({ error: 'Пожалуйста, введите правильный адрес электронной почты.' }).trim(),
|
||||
email: z.email({ error: 'register-error-valid-email' }).trim(),
|
||||
phone: z
|
||||
.string()
|
||||
.min(12, { error: 'Пожалуйста, введите телефонный номер.' }),
|
||||
.min(12, { error: 'register-error-phone' }),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, { error: 'Длина пароля должна быть не менее 8 символов.' })
|
||||
.regex(/[a-zA-Zа-яёА-ЯЁ]/, { error: 'Пароль должен содержать как минимум 1 букву.' })
|
||||
.regex(/[0-9]/, { error: 'Пароль должен содержать как минимум 1 цифру.' })
|
||||
.min(8, { error: 'register-error-password-symbols' })
|
||||
.regex(/[a-zA-Zа-яёА-ЯЁ]/, { error: 'register-error-password-one-letter'})
|
||||
.regex(/[0-9]/, { error: 'register-error-password-one-digit' })
|
||||
.trim(),
|
||||
confirm_password: z
|
||||
.string(),
|
||||
agree: z
|
||||
.string()
|
||||
.min(2, { error: 'Пожалуйста, ознакомьтесь с условиями использования и политикой конфиденциальности.' })
|
||||
.min(2, { error: 'register-error-agree' })
|
||||
})
|
||||
.refine((data) => data.password === data.confirm_password, {
|
||||
message: 'Повторите пароль',
|
||||
message: 'repeat-password',
|
||||
path: ['confirm_password'],
|
||||
});
|
||||
|
||||
@@ -47,11 +47,11 @@ export const loginFormSchema = z
|
||||
.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, { error: 'Адрес электронной почты не должен быть пустым.' })
|
||||
.min(1, { error: 'login-error-email' })
|
||||
.trim(),
|
||||
password: z
|
||||
.string()
|
||||
.min(1, { error: 'Пароль не должен быть пустым.' })
|
||||
.min(1, { error: 'login-error-password' })
|
||||
.trim(),
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
'use client'
|
||||
|
||||
import { useLocale } from 'next-intl'
|
||||
import { usePathname, useRouter } from '@/i18n/navigation'
|
||||
import { routing } from '@/i18n/routing'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { useClickOutside } from '@/app/hooks/useClickOutside';
|
||||
|
||||
export default function LanguageSwitcher() {
|
||||
const locale = useLocale()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const switchLanguage = (newLocale: string) => {
|
||||
router.replace(pathname, { locale: newLocale })
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
useClickOutside(dropdownRef, () => {
|
||||
setIsOpen(false);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') setIsOpen(false)
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleEscape)
|
||||
return () => document.removeEventListener('keydown', handleEscape)
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="flex items-center justify-between w-full px-4 py-2 text-sm font-medium text-[#6366f1] bg-white border-2 border-[#6366f1] rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<span className="mr-2">{locale === 'ru' ? 'РУ' : locale.toUpperCase()}</span>
|
||||
</span>
|
||||
<svg
|
||||
className={`w-5 h-5 ml-2 transition-transform ${isOpen ? 'rotate-180' : ''}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute right-0 z-10 w-full mt-2 origin-top-right bg-white rounded-md shadow-lg border-2 border-[#6366f1] focus:outline-none">
|
||||
<ul
|
||||
className="py-1"
|
||||
role="listbox"
|
||||
aria-labelledby="language-selector"
|
||||
>
|
||||
{routing.locales.map((lang) => (
|
||||
<li key={lang} role="option">
|
||||
<button
|
||||
onClick={() => switchLanguage(lang)}
|
||||
className={`flex items-center w-full px-4 py-2 text-sm text-left hover:bg-blue-100 ${locale === lang ? 'bg-blue-50 text-blue-600 font-medium' : 'text-[#6366f1]'
|
||||
}`}
|
||||
role="menuitem"
|
||||
>
|
||||
<span className="mr-2 font-medium">
|
||||
{lang === 'ru' ? 'РУ' : lang.toUpperCase()}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import Providers from './providers/getQueryServer'
|
||||
import type { Metadata } from "next";
|
||||
import "./styles/globals.css";
|
||||
import "./styles/global-styles.scss"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "No copy",
|
||||
description: "No copy",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="ru">
|
||||
<body className={``}>
|
||||
<Providers>
|
||||
{children}
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,12 @@
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
|
||||
&-language {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import NotificationsButton from './notificationsButton';
|
||||
import UserMenuButton from './userMenuButton';
|
||||
import Link from 'next/link';
|
||||
import LanguageSwitcher from '@/app/components/LanguageSwitcher';
|
||||
|
||||
export default function HeaderPanel() {
|
||||
return (
|
||||
<header className="header">
|
||||
<div className="header-action">
|
||||
<LanguageSwitcher/>
|
||||
<Link
|
||||
href={'/pages/payment'}
|
||||
className="tokens-balance"
|
||||
|
||||
+28
-10
@@ -3,53 +3,71 @@
|
||||
import styles from '@/app/styles/login.module.scss'
|
||||
import { useActionState } from 'react';
|
||||
import { authorization } from '@/app/actions/auth';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export default function LoginForm() {
|
||||
const [state, formAction, isPending] = useActionState(
|
||||
authorization,
|
||||
undefined,
|
||||
);
|
||||
const t = useTranslations('Login-register-form');
|
||||
|
||||
return (
|
||||
<form action={formAction}>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
<label className={`${styles['form-label']}`}>Email адрес</label>
|
||||
<label className={`${styles['form-label']}`}>
|
||||
{t('email-adress')}
|
||||
</label>
|
||||
<input
|
||||
type="readOnly"
|
||||
id="email"
|
||||
name="email"
|
||||
className={`${styles['form-input']}`}
|
||||
placeholder="Введите ваш email"
|
||||
placeholder={t('enter-email')}
|
||||
defaultValue={state?.previousState?.email ? state?.previousState?.email : ''}
|
||||
/>
|
||||
{state?.error?.email && (
|
||||
<p className={`${styles['form-error']}`}>{state?.error?.email}</p>
|
||||
<p className={`${styles['form-error']}`}>
|
||||
{t(state?.error?.email)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
<label className={`${styles['form-label']}`}>Пароль</label>
|
||||
<label className={`${styles['form-label']}`}>
|
||||
{t('password')}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
className={`${styles['form-input']}`}
|
||||
placeholder="Введите пароль"
|
||||
placeholder={t('enter-password')}
|
||||
/>
|
||||
{state?.error?.password && (
|
||||
<p className={`${styles['form-error']}`}>{state?.error?.password}</p>
|
||||
<p className={`${styles['form-error']}`}>
|
||||
{t(state?.error?.password)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{state?.error.server && (
|
||||
<p className={`${styles['form-error']}`}>{state?.error.server}</p>
|
||||
<p className={`${styles['form-error']}`}>
|
||||
{t(state?.error.server)}
|
||||
</p>
|
||||
)}
|
||||
<div className={`${styles['form-options']}`}>
|
||||
<div className={`${styles['form-checkbox']}`}>
|
||||
<input type="checkbox" id="remember" name="remember" />
|
||||
<label>Запомнить меня</label>
|
||||
<label>
|
||||
{t('remember-me')}
|
||||
</label>
|
||||
</div>
|
||||
<a href="forgot-password" className={`${styles['forgot-password']}`} >Забыли пароль?</a>
|
||||
<a href="forgot-password" className={`${styles['forgot-password']}`} >
|
||||
{t('forgot-password')}?
|
||||
</a>
|
||||
</div>
|
||||
<button type="submit" className={`${styles['btn']}`}>Войти в систему</button>
|
||||
<button type="submit" className={`${styles['btn']}`}>
|
||||
{t('sign-in')}
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -5,12 +5,14 @@ import { useActionState } from 'react';
|
||||
import { registration } from '@/app/actions/auth';
|
||||
import PhoneInput from '@/app/ui/inputs/phone-input';
|
||||
import Link from 'next/link';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export default function RegisterForm() {
|
||||
const [state, formAction, isPending] = useActionState(
|
||||
registration,
|
||||
undefined,
|
||||
);
|
||||
const t = useTranslations('Login-register-form');
|
||||
|
||||
return (
|
||||
<form action={(e) => {
|
||||
@@ -20,23 +22,27 @@ export default function RegisterForm() {
|
||||
<label
|
||||
className={`${styles['form-label']} ${styles['required']}`}
|
||||
>
|
||||
Полное имя
|
||||
{t('full-name')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="full_name"
|
||||
name="full_name"
|
||||
className={`${styles['form-input']}`}
|
||||
placeholder="Иванов Иван Иванович"
|
||||
placeholder={t('name-placeholder')}
|
||||
defaultValue={state?.previousState?.full_name ? state?.previousState?.full_name : ''}
|
||||
/>
|
||||
{state?.error?.full_name && (
|
||||
<p className="text-sm text-red-500">{state?.error?.full_name}</p>
|
||||
<p className="text-sm text-red-500">
|
||||
{t(state?.error?.full_name)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${styles['form-row']}`}>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
<label className={`${styles['form-label']} ${styles['required']}`}>Email адрес</label>
|
||||
<label className={`${styles['form-label']} ${styles['required']}`}>
|
||||
{t('email-adress')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="email"
|
||||
@@ -46,14 +52,20 @@ export default function RegisterForm() {
|
||||
defaultValue={state?.previousState?.email ? state?.previousState?.email : ''}
|
||||
/>
|
||||
{state?.error?.email && (
|
||||
<p className="text-sm text-red-500">{state?.error?.email}</p>
|
||||
<p className="text-sm text-red-500">
|
||||
{t(state?.error?.email)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
<label className={`${styles['form-label']} ${styles['required']}`}>Телефон</label>
|
||||
<label className={`${styles['form-label']} ${styles['required']}`}>
|
||||
{t('phone')}
|
||||
</label>
|
||||
<PhoneInput phoneState={state?.previousState?.phone} />
|
||||
{state?.error?.phone && (
|
||||
<p className="text-sm text-red-500">{state?.error?.phone}</p>
|
||||
<p className="text-sm text-red-500">
|
||||
{t(state?.error?.phone)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,43 +73,55 @@ export default function RegisterForm() {
|
||||
<label
|
||||
className={`${styles['form-label']}`}
|
||||
>
|
||||
Компания
|
||||
{t('company')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="company"
|
||||
name="company"
|
||||
className={`${styles['form-input']}`}
|
||||
placeholder="ООО «Ваша компания»"
|
||||
placeholder={t('company-placeholder')}
|
||||
defaultValue={state?.previousState?.company ? state?.previousState?.company : ''}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${styles['form-row']}`}>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
<label className={`${styles['form-label']} ${styles['required']}`}>Пароль</label>
|
||||
<label className={`${styles['form-label']} ${styles['required']}`}>
|
||||
{t('password')}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password" name="password"
|
||||
className={`${styles['form-input']}`}
|
||||
placeholder="Минимум 8 символов"
|
||||
placeholder={t('password-placeholder')}
|
||||
defaultValue={state?.previousState?.password ? state?.previousState?.password : ''}
|
||||
/>
|
||||
{state?.error?.password && (
|
||||
<p className="text-sm text-red-500">{state?.error?.password}</p>
|
||||
<p className="text-sm text-red-500">
|
||||
{
|
||||
state?.error?.password.split('&').map((e) => {
|
||||
return t(e)
|
||||
})
|
||||
}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
<label className={`${styles['form-label']} ${styles['required']}`}>Подтвердите пароль</label>
|
||||
<label className={`${styles['form-label']} ${styles['required']}`}>
|
||||
{t('confirm-password')}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
className={`${styles['form-input']}`}
|
||||
placeholder="Повторите пароль"
|
||||
placeholder={t('repeat-password')}
|
||||
defaultValue={state?.previousState?.confirm_password ? state?.previousState?.confirm_password : ''}
|
||||
/>
|
||||
{state?.error?.confirm_password && (
|
||||
<p className="text-sm text-red-500">{state?.error?.confirm_password}</p>
|
||||
<p className="text-sm text-red-500">
|
||||
{t(state?.error?.confirm_password)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,22 +132,28 @@ export default function RegisterForm() {
|
||||
name="agree"
|
||||
defaultChecked={state?.previousState?.agree ? true : false}
|
||||
/>
|
||||
<label htmlFor="agree">Я соглашаюсь с
|
||||
<Link
|
||||
<label htmlFor="agree">
|
||||
{t('i-agree-to')} <Link
|
||||
href="/terms-and-privacy-policy"
|
||||
target="_blank"
|
||||
>
|
||||
условиями использования и политикой конфиденциальности
|
||||
{t('terms-of-use')}
|
||||
</Link>
|
||||
</label>
|
||||
</div>
|
||||
{state?.error?.agree && (
|
||||
<p className="text-sm text-red-500">{state?.error?.agree}</p>
|
||||
<p className="text-sm text-red-500">
|
||||
{t(state?.error?.agree)}
|
||||
</p>
|
||||
)}
|
||||
{state?.error?.server && (
|
||||
<p className="text-sm text-red-500">{state?.error?.server}</p>
|
||||
<p className="text-sm text-red-500">
|
||||
{t(state?.error?.server)}
|
||||
</p>
|
||||
)}
|
||||
<button type="submit" className={`${styles['btn']}`}>Создать аккаунт</button>
|
||||
<button type="submit" className={`${styles['btn']}`}>
|
||||
{t('create-an-account')}
|
||||
</button>
|
||||
</form >
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"HomePage": {
|
||||
"title": "Welcome to our application",
|
||||
"description": "This is a demo application with i18n support"
|
||||
},
|
||||
"Global": {
|
||||
"statistics": "Statistik"
|
||||
},
|
||||
"Login-register-form": {
|
||||
"already-have-an-account": "Already have an account",
|
||||
"company": "Company",
|
||||
"company-placeholder": "Your company",
|
||||
"confirm-password": "Confirm password",
|
||||
"copyright-protection": "Copyright protection",
|
||||
"create-an-account": "Create an account",
|
||||
"email-adress": "Email adress",
|
||||
"enter-email": "Enter your email",
|
||||
"enter-password": "Enter your password",
|
||||
"forgot-password": "Forgot your password",
|
||||
"free-start": "Free start",
|
||||
"free-start-description": "Upon registration, you receive a \"DEMO\" subscription with the ability to tag up to 3 files per month and 10 MB of storage.",
|
||||
"full-name": "Full name",
|
||||
"i-agree-to": "I agree to the",
|
||||
"login": "Login",
|
||||
"login-error-email": "Email address must not be empty.",
|
||||
"login-error-password": "The password must not be empty.",
|
||||
"monitoring-violations": "Real-time monitoring of violations",
|
||||
"name-placeholder": "Ivanov Ivan Ivanovich",
|
||||
"no-account": "No account",
|
||||
"or-log-in-via": "or log in via",
|
||||
"or-register-via": "or register via",
|
||||
"password": "Password",
|
||||
"password-placeholder": "Minimum 8 characters",
|
||||
"phone": "Phone",
|
||||
"protecting-watermark": "Protecting content with hidden watermarks",
|
||||
"register": "Register",
|
||||
"register-error-agree": "Please review the terms of use and privacy policy.",
|
||||
"register-error-name": "Name must be at least 3 characters long.",
|
||||
"register-error-password-one-digit": "Password must contain at least 1 digit.",
|
||||
"register-error-password-one-letter": "Password must contain at least 1 letter.",
|
||||
"register-error-password-symbols": "Password must be at least 8 characters long.",
|
||||
"register-error-phone": "Please enter a phone number.",
|
||||
"register-error-valid-email": "Please enter a valid email address.",
|
||||
"remember-me": "Remember me",
|
||||
"repeat-password": "Repeat password",
|
||||
"request-ended-with-an-error": "The request ended with an error",
|
||||
"safe-storage": "Reliable storage and data security",
|
||||
"sign-in": "Sign in",
|
||||
"terms-of-use": "terms of use and privacy policy"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"HomePage": {
|
||||
"title": "Добро пожаловать в наше приложение",
|
||||
"description": "Это демонстрационное приложение с поддержкой i18n"
|
||||
},
|
||||
"Global": {
|
||||
"statistics": "Статистика"
|
||||
},
|
||||
"Login-register-form": {
|
||||
"already-have-an-account": "Уже есть аккаунт",
|
||||
"company": "Компания",
|
||||
"company-placeholder": "ООО «Ваша компания»",
|
||||
"confirm-password": "Подтвердите пароль",
|
||||
"copyright-protection": "Защита авторских прав",
|
||||
"create-an-account": "Создать аккаунт",
|
||||
"email-adress": "Email адрес",
|
||||
"enter-email": "Введите ваш email",
|
||||
"enter-password": "Введите пароль",
|
||||
"forgot-password": "Забыли пароль",
|
||||
"free-start": "Бесплатный старт",
|
||||
"free-start-description": "При регистрации вы получаете подписку \"ДЕМО\" с возможностью маркировки до 3 файлов в месяц и 10 МБ хранилища.",
|
||||
"full-name": "Полное имя",
|
||||
"i-agree-to": "Я соглашаюсь с",
|
||||
"login": "Войти",
|
||||
"login-error-email": "Адрес электронной почты не должен быть пустым.",
|
||||
"login-error-password": "Пароль не должен быть пустым.",
|
||||
"monitoring-violations": "Мониторинг нарушений в реальном времени",
|
||||
"name-placeholder": "Иванов Иван Иванович",
|
||||
"no-account": "Нет аккаунта",
|
||||
"or-log-in-via": "или войти через",
|
||||
"or-register-via": "или зарегистрироваться через",
|
||||
"password": "Пароль",
|
||||
"password-placeholder": "Минимум 8 символов",
|
||||
"phone": "Телефон",
|
||||
"protecting-watermark": "Защита контента скрытыми водяными знаками",
|
||||
"register": "Зарегистрироваться",
|
||||
"register-error-agree": "Пожалуйста, ознакомьтесь с условиями использования и политикой конфиденциальности.",
|
||||
"register-error-name": "Имя должно содержать не менее 3 символов.",
|
||||
"register-error-password-one-digit": "Пароль должен содержать как минимум 1 цифру.",
|
||||
"register-error-password-one-letter": "Пароль должен содержать как минимум 1 букву.",
|
||||
"register-error-password-symbols": "Длина пароля должна быть не менее 8 символов.",
|
||||
"register-error-phone": "Пожалуйста, введите телефонный номер.",
|
||||
"register-error-valid-email": "Пожалуйста, введите правильный адрес электронной почты.",
|
||||
"remember-me": "Запомнить меня",
|
||||
"repeat-password": "Повторите пароль",
|
||||
"request-ended-with-an-error": "Запрос завершился с ошибкой",
|
||||
"safe-storage": "Надежное хранение и безопасность данных",
|
||||
"sign-in": "Войти в систему",
|
||||
"terms-of-use": "условиями использования и политикой конфиденциальности"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { createNavigation } from 'next-intl/navigation';
|
||||
import { routing } from './routing';
|
||||
|
||||
export const { Link, redirect, usePathname, useRouter, getPathname } =
|
||||
createNavigation(routing);
|
||||
@@ -0,0 +1,15 @@
|
||||
import { getRequestConfig } from 'next-intl/server';
|
||||
import { hasLocale } from 'next-intl';
|
||||
import { routing } from './routing';
|
||||
|
||||
export default getRequestConfig(async ({ requestLocale }) => {
|
||||
const requested = await requestLocale;
|
||||
const locale = hasLocale(routing.locales, requested)
|
||||
? requested
|
||||
: routing.defaultLocale;
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: (await import(`./messages/${locale}.json`)).default
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { defineRouting } from 'next-intl/routing';
|
||||
|
||||
export const routing = defineRouting({
|
||||
locales: ['ru', 'en'],
|
||||
defaultLocale: 'ru'
|
||||
});
|
||||
+41
-7
@@ -1,24 +1,58 @@
|
||||
import { NextResponse, NextRequest } from 'next/server';
|
||||
import { cookies } from 'next/headers'
|
||||
import { decrypt } from '@/app/actions/session';
|
||||
import createIntlMiddleware from 'next-intl/middleware'
|
||||
import { routing } from '@/i18n/routing'
|
||||
|
||||
const protectedRoutes = ['/pages']
|
||||
const publicRoutes = ['/login', '/register', '/']
|
||||
const protectedRoutes = ['/pages'];
|
||||
const publicRoutes = ['/login', '/register', '/'];
|
||||
const intlMiddleware = createIntlMiddleware(routing);
|
||||
|
||||
export default async function proxy(request: NextRequest) {
|
||||
const path = request.nextUrl.pathname;
|
||||
const isProtectedRoute = protectedRoutes.includes(path.slice(0, 6));
|
||||
const isPublicRoute = publicRoutes.includes(path);
|
||||
|
||||
if (
|
||||
path.startsWith('/_next') ||
|
||||
path.includes('/api/') ||
|
||||
path.includes('.')
|
||||
) {
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
const intlResponse = intlMiddleware(request);
|
||||
|
||||
if (intlResponse) {
|
||||
const localizedPath = intlResponse.headers.get('x-middleware-request') ||
|
||||
intlResponse.headers.get('x-middleware-next') ||
|
||||
path
|
||||
|
||||
const isProtectedRoute = protectedRoutes.some(route =>
|
||||
localizedPath.includes(route) ||
|
||||
path.includes(route)
|
||||
)
|
||||
|
||||
const isPublicRoute = publicRoutes.some(route =>
|
||||
localizedPath === `/${request.nextUrl.locale}${route}` ||
|
||||
localizedPath === route
|
||||
)
|
||||
|
||||
|
||||
const cookie = (await cookies()).get('session')?.value;
|
||||
const session = await decrypt(cookie);
|
||||
|
||||
if (isProtectedRoute && !session?.token) {
|
||||
return NextResponse.redirect(new URL('/login', request.nextUrl));
|
||||
const loginUrl = new URL('/login', request.nextUrl)
|
||||
loginUrl.searchParams.set('locale', request.nextUrl.locale || 'ru')
|
||||
return NextResponse.redirect(loginUrl)
|
||||
}
|
||||
|
||||
if (path === '/') {
|
||||
return NextResponse.redirect(new URL('/pages/dashboard', request.nextUrl));
|
||||
if (path === '/en' || path === '/ru' || localizedPath === `/${request.nextUrl.locale}`) {
|
||||
const dashboardUrl = new URL('/pages/dashboard', request.nextUrl)
|
||||
dashboardUrl.searchParams.set('locale', request.nextUrl.locale || 'ru')
|
||||
return NextResponse.redirect(dashboardUrl)
|
||||
}
|
||||
|
||||
return intlResponse
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
|
||||
Reference in New Issue
Block a user