Merge pull request #46 from gangjun06/feat/39

Localize Web UI (#39)
This commit is contained in:
mv-turtle
2022-12-27 18:37:12 -05:00
committed by GitHub
58 changed files with 2084 additions and 643 deletions
+287 -15
View File
@@ -35,6 +35,8 @@
"query-string": "^7.1.3", "query-string": "^7.1.3",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stripe": "^10.7.0", "stripe": "^10.7.0",
"swagger-jsdoc": "^6.2.5",
"swagger-ui-express": "^4.6.0",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3", "typescript": "^4.9.3",
@@ -81,6 +83,46 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@apidevtools/json-schema-ref-parser": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.0.tgz",
"integrity": "sha512-teB30tFooE3iQs2HQIKJ02D8UZA1Xy1zaczzhUjJs0CymYxeC0g+y5rCY2p8NHBM6DBUVoR8rSM4kHLj1WE9mQ==",
"dependencies": {
"@jsdevtools/ono": "^7.1.3",
"@types/json-schema": "^7.0.6",
"call-me-maybe": "^1.0.1",
"js-yaml": "^4.1.0"
}
},
"node_modules/@apidevtools/openapi-schemas": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
"integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/@apidevtools/swagger-methods": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
"integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="
},
"node_modules/@apidevtools/swagger-parser": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.2.tgz",
"integrity": "sha512-JFxcEyp8RlNHgBCE98nwuTkZT6eNFPc1aosWV6wPcQph72TSEEu1k3baJD4/x1qznU+JiDdz8F5pTwabZh+Dhg==",
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.6",
"@apidevtools/openapi-schemas": "^2.0.4",
"@apidevtools/swagger-methods": "^3.0.2",
"@jsdevtools/ono": "^7.1.3",
"call-me-maybe": "^1.0.1",
"z-schema": "^4.2.3"
},
"peerDependencies": {
"openapi-types": ">=7"
}
},
"node_modules/@aws-crypto/ie11-detection": { "node_modules/@aws-crypto/ie11-detection": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz",
@@ -2571,6 +2613,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
} }
}, },
"node_modules/@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
},
"node_modules/@juanelas/base64": { "node_modules/@juanelas/base64": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@juanelas/base64/-/base64-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@juanelas/base64/-/base64-1.0.5.tgz",
@@ -3147,8 +3194,7 @@
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.11", "version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
"dev": true
}, },
"node_modules/@types/jsonwebtoken": { "node_modules/@types/jsonwebtoken": {
"version": "8.5.9", "version": "8.5.9",
@@ -3628,8 +3674,7 @@
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
"dev": true
}, },
"node_modules/array-flatten": { "node_modules/array-flatten": {
"version": "1.1.1", "version": "1.1.1",
@@ -4013,6 +4058,11 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/call-me-maybe": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="
},
"node_modules/callsites": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4535,7 +4585,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"dependencies": { "dependencies": {
"esutils": "^2.0.2" "esutils": "^2.0.2"
}, },
@@ -4848,7 +4897,6 @@
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -6447,7 +6495,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
}, },
@@ -9789,6 +9836,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/openapi-types": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.0.2.tgz",
"integrity": "sha512-GuTo7FyZjOIWVhIhQSWJVaws6A82sWIGyQogxxYBYKZ0NBdyP2CYSIgOwFfSB+UVoPExk/YzFpyYitHS8KVZtA==",
"peer": true
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.1", "version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -10935,6 +10988,74 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/swagger-jsdoc": {
"version": "6.2.5",
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.5.tgz",
"integrity": "sha512-l+cdsKS2y+QDhrH1TJSUiE0y9XKuf5xaGSatjf0hR/wjTlMpO8WfubBK9d/nASdbHPMtj9iJZLBH2ogBEhL7Sw==",
"dependencies": {
"commander": "6.2.0",
"doctrine": "3.0.0",
"glob": "7.1.6",
"lodash.mergewith": "^4.6.2",
"swagger-parser": "10.0.2",
"yaml": "2.0.0-1"
},
"bin": {
"swagger-jsdoc": "bin/swagger-jsdoc.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/swagger-jsdoc/node_modules/glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/swagger-parser": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.2.tgz",
"integrity": "sha512-9jHkHM+QXyLGFLk1DkXBwV+4HyNm0Za3b8/zk/+mjr8jgOSiqm3FOTHBSDsBjtn9scdL+8eWcHdupp2NLM8tDw==",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/swagger-ui-dist": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz",
"integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA=="
},
"node_modules/swagger-ui-express": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.0.tgz",
"integrity": "sha512-ZxpQFp1JR2RF8Ar++CyJzEDdvufa08ujNUJgMVTMWPi86CuQeVdBtvaeO/ysrz6dJAYXf9kbVNhWD7JWocwqsA==",
"dependencies": {
"swagger-ui-dist": ">=4.11.0"
},
"engines": {
"node": ">= v0.10.32"
},
"peerDependencies": {
"express": ">=4.0.0"
}
},
"node_modules/test-exclude": { "node_modules/test-exclude": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -11520,6 +11641,14 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}, },
"node_modules/yaml": {
"version": "2.0.0-1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
"integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==",
"engines": {
"node": ">= 6"
}
},
"node_modules/yargs": { "node_modules/yargs": {
"version": "17.6.2", "version": "17.6.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
@@ -11567,6 +11696,31 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/z-schema": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.4.tgz",
"integrity": "sha512-YvBeW5RGNeNzKOUJs3rTL4+9rpcvHXt5I051FJbOcitV8bl40pEfcG0Q+dWSwS0/BIYrMZ/9HHoqLllMkFhD0w==",
"dependencies": {
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"validator": "^13.6.0"
},
"bin": {
"z-schema": "bin/z-schema"
},
"engines": {
"node": ">=6.0.0"
},
"optionalDependencies": {
"commander": "^2.7.1"
}
},
"node_modules/z-schema/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"optional": true
} }
}, },
"dependencies": { "dependencies": {
@@ -11580,6 +11734,40 @@
"@jridgewell/trace-mapping": "^0.3.9" "@jridgewell/trace-mapping": "^0.3.9"
} }
}, },
"@apidevtools/json-schema-ref-parser": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.0.tgz",
"integrity": "sha512-teB30tFooE3iQs2HQIKJ02D8UZA1Xy1zaczzhUjJs0CymYxeC0g+y5rCY2p8NHBM6DBUVoR8rSM4kHLj1WE9mQ==",
"requires": {
"@jsdevtools/ono": "^7.1.3",
"@types/json-schema": "^7.0.6",
"call-me-maybe": "^1.0.1",
"js-yaml": "^4.1.0"
}
},
"@apidevtools/openapi-schemas": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
"integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ=="
},
"@apidevtools/swagger-methods": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
"integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="
},
"@apidevtools/swagger-parser": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.2.tgz",
"integrity": "sha512-JFxcEyp8RlNHgBCE98nwuTkZT6eNFPc1aosWV6wPcQph72TSEEu1k3baJD4/x1qznU+JiDdz8F5pTwabZh+Dhg==",
"requires": {
"@apidevtools/json-schema-ref-parser": "^9.0.6",
"@apidevtools/openapi-schemas": "^2.0.4",
"@apidevtools/swagger-methods": "^3.0.2",
"@jsdevtools/ono": "^7.1.3",
"call-me-maybe": "^1.0.1",
"z-schema": "^4.2.3"
}
},
"@aws-crypto/ie11-detection": { "@aws-crypto/ie11-detection": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz",
@@ -13763,6 +13951,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
} }
}, },
"@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
},
"@juanelas/base64": { "@juanelas/base64": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@juanelas/base64/-/base64-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@juanelas/base64/-/base64-1.0.5.tgz",
@@ -14266,8 +14459,7 @@
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.11", "version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
"dev": true
}, },
"@types/jsonwebtoken": { "@types/jsonwebtoken": {
"version": "8.5.9", "version": "8.5.9",
@@ -14615,8 +14807,7 @@
"argparse": { "argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
"dev": true
}, },
"array-flatten": { "array-flatten": {
"version": "1.1.1", "version": "1.1.1",
@@ -14900,6 +15091,11 @@
"get-intrinsic": "^1.0.2" "get-intrinsic": "^1.0.2"
} }
}, },
"call-me-maybe": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="
},
"callsites": { "callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -15298,7 +15494,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"requires": { "requires": {
"esutils": "^2.0.2" "esutils": "^2.0.2"
} }
@@ -15533,8 +15728,7 @@
"esutils": { "esutils": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
"dev": true
}, },
"etag": { "etag": {
"version": "1.8.1", "version": "1.8.1",
@@ -16735,7 +16929,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"requires": { "requires": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
} }
@@ -19125,6 +19318,12 @@
"mimic-fn": "^2.1.0" "mimic-fn": "^2.1.0"
} }
}, },
"openapi-types": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.0.2.tgz",
"integrity": "sha512-GuTo7FyZjOIWVhIhQSWJVaws6A82sWIGyQogxxYBYKZ0NBdyP2CYSIgOwFfSB+UVoPExk/YzFpyYitHS8KVZtA==",
"peer": true
},
"optionator": { "optionator": {
"version": "0.9.1", "version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -19960,6 +20159,55 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true "dev": true
}, },
"swagger-jsdoc": {
"version": "6.2.5",
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.5.tgz",
"integrity": "sha512-l+cdsKS2y+QDhrH1TJSUiE0y9XKuf5xaGSatjf0hR/wjTlMpO8WfubBK9d/nASdbHPMtj9iJZLBH2ogBEhL7Sw==",
"requires": {
"commander": "6.2.0",
"doctrine": "3.0.0",
"glob": "7.1.6",
"lodash.mergewith": "^4.6.2",
"swagger-parser": "10.0.2",
"yaml": "2.0.0-1"
},
"dependencies": {
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
}
},
"swagger-parser": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.2.tgz",
"integrity": "sha512-9jHkHM+QXyLGFLk1DkXBwV+4HyNm0Za3b8/zk/+mjr8jgOSiqm3FOTHBSDsBjtn9scdL+8eWcHdupp2NLM8tDw==",
"requires": {
"@apidevtools/swagger-parser": "10.0.2"
}
},
"swagger-ui-dist": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz",
"integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA=="
},
"swagger-ui-express": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.0.tgz",
"integrity": "sha512-ZxpQFp1JR2RF8Ar++CyJzEDdvufa08ujNUJgMVTMWPi86CuQeVdBtvaeO/ysrz6dJAYXf9kbVNhWD7JWocwqsA==",
"requires": {
"swagger-ui-dist": ">=4.11.0"
}
},
"test-exclude": { "test-exclude": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -20371,6 +20619,11 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}, },
"yaml": {
"version": "2.0.0-1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
"integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ=="
},
"yargs": { "yargs": {
"version": "17.6.2", "version": "17.6.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
@@ -20403,6 +20656,25 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true "dev": true
},
"z-schema": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.4.tgz",
"integrity": "sha512-YvBeW5RGNeNzKOUJs3rTL4+9rpcvHXt5I051FJbOcitV8bl40pEfcG0Q+dWSwS0/BIYrMZ/9HHoqLllMkFhD0w==",
"requires": {
"commander": "^2.7.1",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"validator": "^13.6.0"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"optional": true
}
}
} }
} }
} }
+2
View File
@@ -48,6 +48,8 @@ services:
- ./frontend/public:/app/public - ./frontend/public:/app/public
- ./frontend/styles:/app/styles - ./frontend/styles:/app/styles
- ./frontend/components:/app/components - ./frontend/components:/app/components
- ./frontend/locales:/app/locales
- ./frontend/next-i18next.config.js:/app/next-i18next.config.js
env_file: .env env_file: .env
environment: environment:
- NEXT_PUBLIC_ENV=development - NEXT_PUBLIC_ENV=development
+2
View File
@@ -32,3 +32,5 @@ yarn-error.log*
.env.production.local .env.production.local
.vercel .vercel
.env.infisical .env.infisical
.vscode
+106 -102
View File
@@ -1,38 +1,39 @@
/* eslint-disable no-unexpected-multiline */ /* eslint-disable no-unexpected-multiline */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from 'react'; import { useEffect, useMemo, useState } from "react";
import Link from 'next/link'; import Link from "next/link";
import { useRouter } from 'next/router'; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { import {
faBookOpen, faBookOpen,
faGear, faGear,
faKey, faKey,
faMobile, faMobile,
faPlug, faPlug,
faUser faUser,
} from '@fortawesome/free-solid-svg-icons'; } from "@fortawesome/free-solid-svg-icons";
import { faPlus } from '@fortawesome/free-solid-svg-icons'; import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import getOrganizations from '~/pages/api/organization/getOrgs'; import getOrganizations from "~/pages/api/organization/getOrgs";
import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects'; import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
import getOrganizationUsers from '~/pages/api/organization/GetOrgUsers'; import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers";
import checkUserAction from '~/pages/api/userActions/checkUserAction'; import checkUserAction from "~/pages/api/userActions/checkUserAction";
import addUserToWorkspace from '~/pages/api/workspace/addUserToWorkspace'; import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace";
import createWorkspace from '~/pages/api/workspace/createWorkspace'; import createWorkspace from "~/pages/api/workspace/createWorkspace";
import getWorkspaces from '~/pages/api/workspace/getWorkspaces'; import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
import uploadKeys from '~/pages/api/workspace/uploadKeys'; import uploadKeys from "~/pages/api/workspace/uploadKeys";
import NavBarDashboard from '../navigation/NavBarDashboard'; import NavBarDashboard from "../navigation/NavBarDashboard";
import onboardingCheck from '../utilities/checks/OnboardingCheck'; import onboardingCheck from "../utilities/checks/OnboardingCheck";
import { tempLocalStorage } from '../utilities/checks/tempLocalStorage'; import { tempLocalStorage } from "../utilities/checks/tempLocalStorage";
import { import {
decryptAssymmetric, decryptAssymmetric,
encryptAssymmetric encryptAssymmetric,
} from '../utilities/cryptography/crypto'; } from "../utilities/cryptography/crypto";
import Button from './buttons/Button'; import Button from "./buttons/Button";
import AddWorkspaceDialog from './dialog/AddWorkspaceDialog'; import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
import Listbox from './Listbox'; import Listbox from "./Listbox";
interface LayoutProps { interface LayoutProps {
children: React.ReactNode; children: React.ReactNode;
@@ -41,15 +42,17 @@ interface LayoutProps {
export default function Layout({ children }: LayoutProps) { export default function Layout({ children }: LayoutProps) {
const router = useRouter(); const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]); const [workspaceList, setWorkspaceList] = useState([]);
const [workspaceMapping, setWorkspaceMapping] = useState([{ '1': '2' }]); const [workspaceMapping, setWorkspaceMapping] = useState([{ "1": "2" }]);
const [workspaceSelected, setWorkspaceSelected] = useState('∞'); const [workspaceSelected, setWorkspaceSelected] = useState("∞");
const [newWorkspaceName, setNewWorkspaceName] = useState(''); const [newWorkspaceName, setNewWorkspaceName] = useState("");
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] = const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] =
useState(0); useState(0);
const { t } = useTranslation();
function closeModal() { function closeModal() {
setIsOpen(false); setIsOpen(false);
} }
@@ -75,35 +78,35 @@ export default function Layout({ children }: LayoutProps) {
if (!currentWorkspaces.includes(workspaceName)) { if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace({ const newWorkspace = await createWorkspace({
workspaceName, workspaceName,
organizationId: tempLocalStorage('orgData.id') organizationId: tempLocalStorage("orgData.id"),
}); });
const newWorkspaceId = newWorkspace._id; const newWorkspaceId = newWorkspace._id;
if (addAllUsers) { if (addAllUsers) {
const orgUsers = await getOrganizationUsers({ const orgUsers = await getOrganizationUsers({
orgId: tempLocalStorage('orgData.id') orgId: tempLocalStorage("orgData.id"),
}); });
orgUsers.map(async (user: any) => { orgUsers.map(async (user: any) => {
if (user.status == 'accepted') { if (user.status == "accepted") {
const result = await addUserToWorkspace( const result = await addUserToWorkspace(
user.user.email, user.user.email,
newWorkspaceId newWorkspaceId
); );
if (result?.invitee && result?.latestKey) { if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = tempLocalStorage('PRIVATE_KEY'); const PRIVATE_KEY = tempLocalStorage("PRIVATE_KEY");
// assymmetrically decrypt symmetric key with local private key // assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({ const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey, ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce, nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey, publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY privateKey: PRIVATE_KEY,
}); });
const { ciphertext, nonce } = encryptAssymmetric({ const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key, plaintext: key,
publicKey: result.invitee.publicKey, publicKey: result.invitee.publicKey,
privateKey: PRIVATE_KEY privateKey: PRIVATE_KEY,
}) as { ciphertext: string; nonce: string }; }) as { ciphertext: string; nonce: string };
uploadKeys( uploadKeys(
@@ -116,11 +119,11 @@ export default function Layout({ children }: LayoutProps) {
} }
}); });
} }
router.push('/dashboard/' + newWorkspaceId + '?Development'); router.push("/dashboard/" + newWorkspaceId + "?Development");
setIsOpen(false); setIsOpen(false);
setNewWorkspaceName(''); setNewWorkspaceName("");
} else { } else {
console.error('A project with this name already exists.'); console.error("A project with this name already exists.");
setError(true); setError(true);
setLoading(false); setLoading(false);
} }
@@ -131,62 +134,65 @@ export default function Layout({ children }: LayoutProps) {
} }
} }
const menuItems = [ const menuItems = useMemo(
{ () => [
href: {
'/dashboard/' + href:
workspaceMapping[workspaceSelected as any] + "/dashboard/" +
'?Development', workspaceMapping[workspaceSelected as any] +
title: 'Secrets', "?Development",
emoji: <FontAwesomeIcon icon={faKey} /> title: t("nav:menu.secrets"),
}, emoji: <FontAwesomeIcon icon={faKey} />,
{ },
href: '/users/' + workspaceMapping[workspaceSelected as any], {
title: 'Members', href: "/users/" + workspaceMapping[workspaceSelected as any],
emoji: <FontAwesomeIcon icon={faUser} /> title: t("nav:menu.members"),
}, emoji: <FontAwesomeIcon icon={faUser} />,
{ },
href: '/integrations/' + workspaceMapping[workspaceSelected as any], {
title: 'Integrations', href: "/integrations/" + workspaceMapping[workspaceSelected as any],
emoji: <FontAwesomeIcon icon={faPlug} /> title: t("nav:menu.integrations"),
}, emoji: <FontAwesomeIcon icon={faPlug} />,
{ },
href: '/settings/project/' + workspaceMapping[workspaceSelected as any], {
title: 'Project Settings', href: "/settings/project/" + workspaceMapping[workspaceSelected as any],
emoji: <FontAwesomeIcon icon={faGear} /> title: t("nav:menu.project-settings"),
} emoji: <FontAwesomeIcon icon={faGear} />,
]; },
],
[t, workspaceMapping, workspaceSelected]
);
useEffect(() => { useEffect(() => {
// Put a user in a workspace if they're not in one yet // Put a user in a workspace if they're not in one yet
const putUserInWorkSpace = async () => { const putUserInWorkSpace = async () => {
if (tempLocalStorage('orgData.id') === '') { if (tempLocalStorage("orgData.id") === "") {
const userOrgs = await getOrganizations(); const userOrgs = await getOrganizations();
localStorage.setItem('orgData.id', userOrgs[0]._id); localStorage.setItem("orgData.id", userOrgs[0]._id);
} }
const orgUserProjects = await getOrganizationUserProjects({ const orgUserProjects = await getOrganizationUserProjects({
orgId: tempLocalStorage('orgData.id') orgId: tempLocalStorage("orgData.id"),
}); });
const userWorkspaces = orgUserProjects; const userWorkspaces = orgUserProjects;
if ( if (
userWorkspaces.length == 0 && userWorkspaces.length == 0 &&
router.asPath != '/noprojects' && router.asPath != "/noprojects" &&
!router.asPath.includes('settings') !router.asPath.includes("settings")
) { ) {
router.push('/noprojects'); router.push("/noprojects");
} else if (router.asPath != '/noprojects') { } else if (router.asPath != "/noprojects") {
const intendedWorkspaceId = router.asPath const intendedWorkspaceId = router.asPath
.split('/') .split("/")
[router.asPath.split('/').length - 1].split('?')[0]; [router.asPath.split("/").length - 1].split("?")[0];
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs // If a user is not a member of a workspace they are trying to access, just push them to one of theirs
if ( if (
intendedWorkspaceId != 'heroku' && intendedWorkspaceId != "heroku" &&
!userWorkspaces !userWorkspaces
.map((workspace: { _id: string }) => workspace._id) .map((workspace: { _id: string }) => workspace._id)
.includes(intendedWorkspaceId) .includes(intendedWorkspaceId)
) { ) {
router.push('/dashboard/' + userWorkspaces[0]._id + '?Development'); router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
} else { } else {
setWorkspaceList( setWorkspaceList(
userWorkspaces.map((workspace: any) => workspace.name) userWorkspaces.map((workspace: any) => workspace.name)
@@ -195,7 +201,7 @@ export default function Layout({ children }: LayoutProps) {
Object.fromEntries( Object.fromEntries(
userWorkspaces.map((workspace: any) => [ userWorkspaces.map((workspace: any) => [
workspace.name, workspace.name,
workspace._id workspace._id,
]) ])
) as any ) as any
); );
@@ -203,12 +209,12 @@ export default function Layout({ children }: LayoutProps) {
Object.fromEntries( Object.fromEntries(
userWorkspaces.map((workspace: any) => [ userWorkspaces.map((workspace: any) => [
workspace._id, workspace._id,
workspace.name workspace.name,
]) ])
)[ )[
router.asPath router.asPath
.split('/') .split("/")
[router.asPath.split('/').length - 1].split('?')[0] [router.asPath.split("/").length - 1].split("?")[0]
] ]
); );
} }
@@ -224,16 +230,16 @@ export default function Layout({ children }: LayoutProps) {
workspaceMapping[workspaceSelected as any] && workspaceMapping[workspaceSelected as any] &&
`${workspaceMapping[workspaceSelected as any]}` !== `${workspaceMapping[workspaceSelected as any]}` !==
router.asPath router.asPath
.split('/') .split("/")
[router.asPath.split('/').length - 1].split('?')[0] [router.asPath.split("/").length - 1].split("?")[0]
) { ) {
router.push( router.push(
'/dashboard/' + "/dashboard/" +
workspaceMapping[workspaceSelected as any] + workspaceMapping[workspaceSelected as any] +
'?Development' "?Development"
); );
localStorage.setItem( localStorage.setItem(
'projectData.id', "projectData.id",
`${workspaceMapping[workspaceSelected as any]}` `${workspaceMapping[workspaceSelected as any]}`
); );
} }
@@ -257,7 +263,7 @@ export default function Layout({ children }: LayoutProps) {
<div> <div>
<div className="flex justify-center w-full mt-[4.5rem] mb-6 bg-bunker-600 h-20 flex-col items-center px-4"> <div className="flex justify-center w-full mt-[4.5rem] mb-6 bg-bunker-600 h-20 flex-col items-center px-4">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide"> <div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
PROJECT {t("nav:menu.project")}
</div> </div>
{workspaceList.length > 0 ? ( {workspaceList.length > 0 ? (
<Listbox <Listbox
@@ -282,11 +288,11 @@ export default function Layout({ children }: LayoutProps) {
{workspaceList.length > 0 && {workspaceList.length > 0 &&
menuItems.map(({ href, title, emoji }) => ( menuItems.map(({ href, title, emoji }) => (
<li className="mt-0.5 mx-2" key={title}> <li className="mt-0.5 mx-2" key={title}>
{router.asPath.split('/')[1] === href.split('/')[1] && {router.asPath.split("/")[1] === href.split("/")[1] &&
(['project', 'billing', 'org', 'personal'].includes( (["project", "billing", "org", "personal"].includes(
router.asPath.split('/')[2] router.asPath.split("/")[2]
) )
? router.asPath.split('/')[2] === href.split('/')[2] ? router.asPath.split("/")[2] === href.split("/")[2]
: true) ? ( : true) ? (
<div <div
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`} className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
@@ -297,7 +303,7 @@ export default function Layout({ children }: LayoutProps) {
</p> </p>
{title} {title}
</div> </div>
) : router.asPath == '/noprojects' ? ( ) : router.asPath == "/noprojects" ? (
<div <div
className={`flex p-2.5 text-white text-sm rounded`} className={`flex p-2.5 text-white text-sm rounded`}
> >
@@ -323,7 +329,7 @@ export default function Layout({ children }: LayoutProps) {
</ul> </ul>
</div> </div>
<div className="w-full mt-40 mb-4 px-2"> <div className="w-full mt-40 mb-4 px-2">
{router.asPath.split('/')[1] === 'home' ? ( {router.asPath.split("/")[1] === "home" ? (
<div <div
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`} className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
> >
@@ -334,12 +340,12 @@ export default function Layout({ children }: LayoutProps) {
Infisical Guide Infisical Guide
<img <img
src={`/images/progress-${ src={`/images/progress-${
totalOnboardingActionsDone == 0 ? '0' : '' totalOnboardingActionsDone == 0 ? "0" : ""
}${totalOnboardingActionsDone == 1 ? '14' : ''}${ }${totalOnboardingActionsDone == 1 ? "14" : ""}${
totalOnboardingActionsDone == 2 ? '28' : '' totalOnboardingActionsDone == 2 ? "28" : ""
}${totalOnboardingActionsDone == 3 ? '43' : ''}${ }${totalOnboardingActionsDone == 3 ? "43" : ""}${
totalOnboardingActionsDone == 4 ? '57' : '' totalOnboardingActionsDone == 4 ? "57" : ""
}${totalOnboardingActionsDone == 5 ? '71' : ''}.svg`} }${totalOnboardingActionsDone == 5 ? "71" : ""}.svg`}
height={58} height={58}
width={58} width={58}
alt="progress bar" alt="progress bar"
@@ -359,12 +365,12 @@ export default function Layout({ children }: LayoutProps) {
Infisical Guide Infisical Guide
<img <img
src={`/images/progress-${ src={`/images/progress-${
totalOnboardingActionsDone == 0 ? '0' : '' totalOnboardingActionsDone == 0 ? "0" : ""
}${totalOnboardingActionsDone == 1 ? '14' : ''}${ }${totalOnboardingActionsDone == 1 ? "14" : ""}${
totalOnboardingActionsDone == 2 ? '28' : '' totalOnboardingActionsDone == 2 ? "28" : ""
}${totalOnboardingActionsDone == 3 ? '43' : ''}${ }${totalOnboardingActionsDone == 3 ? "43" : ""}${
totalOnboardingActionsDone == 4 ? '57' : '' totalOnboardingActionsDone == 4 ? "57" : ""
}${totalOnboardingActionsDone == 5 ? '71' : ''}.svg`} }${totalOnboardingActionsDone == 5 ? "71" : ""}.svg`}
height={58} height={58}
width={58} width={58}
alt="progress bar" alt="progress bar"
@@ -394,9 +400,7 @@ export default function Layout({ children }: LayoutProps) {
className="text-gray-300 text-7xl mb-8" className="text-gray-300 text-7xl mb-8"
/> />
<p className="text-gray-200 px-6 text-center text-lg max-w-sm"> <p className="text-gray-200 px-6 text-center text-lg max-w-sm">
{' '} {` ${t("common:no-mobile")} `}
To use Infisical, please log in through a device with larger
dimensions.{' '}
</p> </p>
</div> </div>
</> </>
@@ -1,4 +1,5 @@
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { useTranslation } from "next-i18next";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import addIncidentContact from "~/pages/api/organization/addIncidentContact"; import addIncidentContact from "~/pages/api/organization/addIncidentContact";
@@ -14,6 +15,7 @@ const AddIncidentContactDialog = ({
setIncidentContacts, setIncidentContacts,
}) => { }) => {
let [incidentContactEmail, setIncidentContactEmail] = useState(""); let [incidentContactEmail, setIncidentContactEmail] = useState("");
const { t } = useTranslation();
const submit = () => { const submit = () => {
setIncidentContacts( setIncidentContacts(
@@ -59,17 +61,16 @@ const AddIncidentContactDialog = ({
as="h3" as="h3"
className="text-lg font-medium leading-6 text-gray-400" className="text-lg font-medium leading-6 text-gray-400"
> >
Add an Incident Contact {t("section-incident:add-dialog.title")}
</Dialog.Title> </Dialog.Title>
<div className="mt-2 mb-2"> <div className="mt-2 mb-2">
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
This contact will be notified in the unlikely event of a {t("section-incident:add-dialog.description")}
severe incident.
</p> </p>
</div> </div>
<div className="max-h-28"> <div className="max-h-28">
<InputField <InputField
label="Email" label={t("common:email")}
onChangeHandler={setIncidentContactEmail} onChangeHandler={setIncidentContactEmail}
type="varName" type="varName"
value={incidentContactEmail} value={incidentContactEmail}
@@ -81,7 +82,7 @@ const AddIncidentContactDialog = ({
<Button <Button
onButtonPressed={submit} onButtonPressed={submit}
color="mineshaft" color="mineshaft"
text="Add Incident Contact" text={t("section-incident:add-dialog.add-incident")}
size="md" size="md"
/> />
</div> </div>
@@ -1,5 +1,6 @@
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Trans, useTranslation } from "next-i18next";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import Button from "../buttons/Button"; import Button from "../buttons/Button";
@@ -15,6 +16,7 @@ const AddProjectMemberDialog = ({
setEmail, setEmail,
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { t } = useTranslation();
return ( return (
<div className="z-50"> <div className="z-50">
@@ -49,48 +51,55 @@ const AddProjectMemberDialog = ({
as="h3" as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50" className="text-lg font-medium leading-6 text-gray-400 z-50"
> >
Add a member to your project {t("section-members:add-dialog.add-member-to-project")}
</Dialog.Title> </Dialog.Title>
) : ( ) : (
<Dialog.Title <Dialog.Title
as="h3" as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50" className="text-lg font-medium leading-6 text-gray-400 z-50"
> >
All the users in your organization are already invited. {t("section-members:add-dialog.already-all-invited")}
</Dialog.Title> </Dialog.Title>
)} )}
<div className="mt-2 mb-4"> <div className="mt-2 mb-4">
{data?.length > 0 ? ( {data?.length > 0 ? (
<div className="flex flex-col"> <div className="flex flex-col">
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
The user will receive an email with the instructions. {t("section-members:add-dialog.user-will-email")}
</p> </p>
<div className=""> <div className="">
<button <Trans
type="button" i18nKey="section-members:add-dialog.looking-add"
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2" components={[
onClick={() => // eslint-disable-next-line react/jsx-key
router.push("/settings/org/" + router.query.id) <button
} type="button"
> className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
If you are looking to add users to your org, onClick={() =>
</button> router.push(
<button "/settings/org/" + router.query.id
type="button" )
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2" }
onClick={() => />,
router.push( // eslint-disable-next-line react/jsx-key
"/settings/org/" + router.query.id + "?invite" <button
) type="button"
} className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
> onClick={() =>
click here. router.push(
</button> "/settings/org/" +
router.query.id +
"?invite"
)
}
/>,
]}
/>
</div> </div>
</div> </div>
) : ( ) : (
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
Add more users to the organization first. {t("section-members:add-dialog.add-user-org-first")}
</p> </p>
)} )}
</div> </div>
@@ -110,7 +119,7 @@ const AddProjectMemberDialog = ({
<Button <Button
onButtonPressed={submitModal} onButtonPressed={submitModal}
color="mineshaft" color="mineshaft"
text="Add Member" text={t("section-members:add-member")}
size="md" size="md"
/> />
</div> </div>
@@ -120,7 +129,7 @@ const AddProjectMemberDialog = ({
router.push("/settings/org/" + router.query.id) router.push("/settings/org/" + router.query.id)
} }
color="mineshaft" color="mineshaft"
text="Add Users to Organization" text={t("section-members:add-dialog.add-user-to-org")}
size="md" size="md"
/> />
)} )}
@@ -1,40 +1,42 @@
import { Fragment, useState } from 'react'; import { Fragment, useState } from "react";
import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons'; import { useTranslation } from "next-i18next";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
import { Dialog, Transition } from '@headlessui/react'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import nacl from 'tweetnacl'; import { Dialog, Transition } from "@headlessui/react";
import nacl from "tweetnacl";
import addServiceToken from '~/pages/api/serviceToken/addServiceToken'; import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey'; import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import { envMapping } from '../../../public/data/frequentConstants'; import { envMapping } from "../../../public/data/frequentConstants";
import { import {
decryptAssymmetric, decryptAssymmetric,
encryptAssymmetric encryptAssymmetric,
} from '../../utilities/cryptography/crypto'; } from "../../utilities/cryptography/crypto";
import Button from '../buttons/Button'; import Button from "../buttons/Button";
import InputField from '../InputField'; import InputField from "../InputField";
import ListBox from '../Listbox'; import ListBox from "../Listbox";
const expiryMapping = { const expiryMapping = {
'1 day': 86400, "1 day": 86400,
'7 days': 604800, "7 days": 604800,
'1 month': 2592000, "1 month": 2592000,
'6 months': 15552000, "6 months": 15552000,
'12 months': 31104000 "12 months": 31104000,
}; };
const AddServiceTokenDialog = ({ const AddServiceTokenDialog = ({
isOpen, isOpen,
closeModal, closeModal,
workspaceId, workspaceId,
workspaceName workspaceName,
}) => { }) => {
const [serviceToken, setServiceToken] = useState(''); const [serviceToken, setServiceToken] = useState("");
const [serviceTokenName, setServiceTokenName] = useState(''); const [serviceTokenName, setServiceTokenName] = useState("");
const [serviceTokenEnv, setServiceTokenEnv] = useState('Development'); const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState('1 day'); const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
const [serviceTokenCopied, setServiceTokenCopied] = useState(false); const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const { t } = useTranslation();
const generateServiceToken = async () => { const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey({ workspaceId }); const latestFileKey = await getLatestFileKey({ workspaceId });
@@ -43,7 +45,7 @@ const AddServiceTokenDialog = ({
ciphertext: latestFileKey.latestKey.encryptedKey, ciphertext: latestFileKey.latestKey.encryptedKey,
nonce: latestFileKey.latestKey.nonce, nonce: latestFileKey.latestKey.nonce,
publicKey: latestFileKey.latestKey.sender.publicKey, publicKey: latestFileKey.latestKey.sender.publicKey,
privateKey: localStorage.getItem('PRIVATE_KEY') privateKey: localStorage.getItem("PRIVATE_KEY"),
}); });
// generate new public/private key pair // generate new public/private key pair
@@ -55,7 +57,7 @@ const AddServiceTokenDialog = ({
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({ const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
plaintext: key, plaintext: key,
publicKey, publicKey,
privateKey privateKey,
}); });
let newServiceToken = await addServiceToken({ let newServiceToken = await addServiceToken({
@@ -65,16 +67,16 @@ const AddServiceTokenDialog = ({
expiresIn: expiryMapping[serviceTokenExpiresIn], expiresIn: expiryMapping[serviceTokenExpiresIn],
publicKey, publicKey,
encryptedKey, encryptedKey,
nonce nonce,
}); });
const serviceToken = newServiceToken + ',' + privateKey; const serviceToken = newServiceToken + "," + privateKey;
setServiceToken(serviceToken); setServiceToken(serviceToken);
}; };
function copyToClipboard() { function copyToClipboard() {
// Get the text field // Get the text field
var copyText = document.getElementById('serviceToken'); var copyText = document.getElementById("serviceToken");
// Select the text field // Select the text field
copyText.select(); copyText.select();
@@ -91,8 +93,8 @@ const AddServiceTokenDialog = ({
const closeAddServiceTokenModal = () => { const closeAddServiceTokenModal = () => {
closeModal(); closeModal();
setServiceTokenName(''); setServiceTokenName("");
setServiceToken(''); setServiceToken("");
}; };
return ( return (
@@ -122,27 +124,26 @@ const AddServiceTokenDialog = ({
leaveFrom="opacity-100 scale-100" leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95" leaveTo="opacity-0 scale-95"
> >
{serviceToken == '' ? ( {serviceToken == "" ? (
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all"> <Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title <Dialog.Title
as="h3" as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50" className="text-lg font-medium leading-6 text-gray-400 z-50"
> >
Add a service token for {workspaceName} {t("section-token:add-dialog.title", {
target: workspaceName,
})}
</Dialog.Title> </Dialog.Title>
<div className="mt-2 mb-4"> <div className="mt-2 mb-4">
<div className="flex flex-col"> <div className="flex flex-col">
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
Specify the name, environment, and expiry period. When {t("section-token:add-dialog.description")}
a token is generated, you will only be able to see it
once before it disappears. Make sure to save it
somewhere.
</p> </p>
</div> </div>
</div> </div>
<div className="max-h-28 mb-2"> <div className="max-h-28 mb-2">
<InputField <InputField
label="Service Token Name" label={t("section-token:add-dialog.name")}
onChangeHandler={setServiceTokenName} onChangeHandler={setServiceTokenName}
type="varName" type="varName"
value={serviceTokenName} value={serviceTokenName}
@@ -155,13 +156,13 @@ const AddServiceTokenDialog = ({
selected={serviceTokenEnv} selected={serviceTokenEnv}
onChange={setServiceTokenEnv} onChange={setServiceTokenEnv}
data={[ data={[
'Development', "Development",
'Staging', "Staging",
'Production', "Production",
'Testing' "Testing",
]} ]}
isFull={true} width="full"
text="Environment: " text={`${t("common:environment")}: `}
/> />
</div> </div>
<div className="max-h-28"> <div className="max-h-28">
@@ -169,14 +170,14 @@ const AddServiceTokenDialog = ({
selected={serviceTokenExpiresIn} selected={serviceTokenExpiresIn}
onChange={setServiceTokenExpiresIn} onChange={setServiceTokenExpiresIn}
data={[ data={[
'1 day', "1 day",
'7 days', "7 days",
'1 month', "1 month",
'6 months', "6 months",
'12 months' "12 months",
]} ]}
isFull={true} width="full"
text="Expires in: " text={`${t("common:expired-in")}: `}
/> />
</div> </div>
<div className="max-w-max"> <div className="max-w-max">
@@ -184,10 +185,10 @@ const AddServiceTokenDialog = ({
<Button <Button
onButtonPressed={() => generateServiceToken()} onButtonPressed={() => generateServiceToken()}
color="mineshaft" color="mineshaft"
text="Add Service Token" text={t("section-token:add-dialog.add")}
textDisabled="Add Service Token" textDisabled={t("section-token:add-dialog.add")}
size="md" size="md"
active={serviceTokenName == '' ? false : true} active={serviceTokenName == "" ? false : true}
/> />
</div> </div>
</div> </div>
@@ -198,13 +199,14 @@ const AddServiceTokenDialog = ({
as="h3" as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50" className="text-lg font-medium leading-6 text-gray-400 z-50"
> >
Copy your service token {t("section-token:add-dialog.copy-service-token")}
</Dialog.Title> </Dialog.Title>
<div className="mt-2 mb-4"> <div className="mt-2 mb-4">
<div className="flex flex-col"> <div className="flex flex-col">
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
Once you close this popup, you will never see your {t(
service token again "section-token:add-dialog.copy-service-token-description"
)}
</p> </p>
</div> </div>
</div> </div>
@@ -234,7 +236,7 @@ const AddServiceTokenDialog = ({
)} )}
</button> </button>
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm"> <span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
Click to Copy {t("common.click-to-copy")}
</span> </span>
</div> </div>
</div> </div>
+22 -21
View File
@@ -1,12 +1,13 @@
import { type ChangeEvent, type DragEvent, useState } from 'react'; import { type ChangeEvent, type DragEvent, useState } from "react";
import Image from 'next/image'; import Image from "next/image";
import { faUpload } from '@fortawesome/free-solid-svg-icons'; import { useTranslation } from "next-i18next";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from '../basic/buttons/Button'; import Button from "../basic/buttons/Button";
import Error from '../basic/Error'; import Error from "../basic/Error";
import parse from '../utilities/file'; import parse from "../utilities/file";
import guidGenerator from '../utilities/randomId'; import guidGenerator from "../utilities/randomId";
interface DropZoneProps { interface DropZoneProps {
// TODO: change Data type from any // TODO: change Data type from any
@@ -26,8 +27,10 @@ const DropZone = ({
errorDragAndDrop, errorDragAndDrop,
setButtonReady, setButtonReady,
keysExist, keysExist,
numCurrentRows numCurrentRows,
}: DropZoneProps) => { }: DropZoneProps) => {
const { t } = useTranslation();
const handleDragEnter = (e: DragEvent) => { const handleDragEnter = (e: DragEvent) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -43,7 +46,7 @@ const DropZone = ({
e.stopPropagation(); e.stopPropagation();
// set dropEffect to copy i.e copy of the source item // set dropEffect to copy i.e copy of the source item
e.dataTransfer.dropEffect = 'copy'; e.dataTransfer.dropEffect = "copy";
}; };
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -54,7 +57,7 @@ const DropZone = ({
setTimeout(() => setLoading(false), 5000); setTimeout(() => setLoading(false), 5000);
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
e.dataTransfer.dropEffect = 'copy'; e.dataTransfer.dropEffect = "copy";
const file = e.dataTransfer.files[0]; const file = e.dataTransfer.files[0];
const reader = new FileReader(); const reader = new FileReader();
@@ -69,7 +72,7 @@ const DropZone = ({
pos: numCurrentRows + index, pos: numCurrentRows + index,
key: key, key: key,
value: keyPairs[key as keyof typeof keyPairs], value: keyPairs[key as keyof typeof keyPairs],
type: 'shared' type: "shared",
}; };
}); });
setData(newData); setData(newData);
@@ -96,16 +99,16 @@ const DropZone = ({
reader.onload = (event) => { reader.onload = (event) => {
if (event.target === null || event.target.result === null) return; if (event.target === null || event.target.result === null) return;
const { result } = event.target; const { result } = event.target;
if (typeof result === 'string') { if (typeof result === "string") {
const newData = result const newData = result
.split('\n') .split("\n")
.map((line: string, index: number) => { .map((line: string, index: number) => {
return { return {
id: guidGenerator(), id: guidGenerator(),
pos: numCurrentRows + index, pos: numCurrentRows + index,
key: line.split('=')[0], key: line.split("=")[0],
value: line.split('=').slice(1, line.split('=').length).join('='), value: line.split("=").slice(1, line.split("=").length).join("="),
type: 'shared' type: "shared",
}; };
}); });
setData(newData); setData(newData);
@@ -149,9 +152,7 @@ const DropZone = ({
icon={faUpload} icon={faUpload}
className="text-bunker-300 text-3xl mr-6" className="text-bunker-300 text-3xl mr-6"
/> />
<p className="text-bunker-300 mt-1"> <p className="text-bunker-300 mt-1">{t("common:drop-zone-keys")}</p>
Drag and drop your .env file here to add more keys.
</p>
</div> </div>
{errorDragAndDrop ? ( {errorDragAndDrop ? (
<div className="mt-8 max-w-xl opacity-80"> <div className="mt-8 max-w-xl opacity-80">
@@ -170,7 +171,7 @@ const DropZone = ({
onDrop={handleDrop} onDrop={handleDrop}
> >
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" /> <FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
<p className="">Drag and drop your .env file here.</p> <p className="">{t("common:drop-zone")}</p>
<input <input
id="fileSelect" id="fileSelect"
type="file" type="file"
@@ -1,10 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import { Fragment, useEffect, useState } from 'react'; import { Fragment, useEffect, useMemo, useState } from "react";
import Image from 'next/image'; import Image from "next/image";
import { useRouter } from 'next/router'; import { useRouter } from "next/router";
import { faGithub, faSlack } from '@fortawesome/free-brands-svg-icons'; import { TFunction, useTranslation } from "next-i18next";
import { faCircleQuestion } from '@fortawesome/free-regular-svg-icons'; import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
import { import {
faAngleDown, faAngleDown,
faBook, faBook,
@@ -12,39 +12,39 @@ import {
faEnvelope, faEnvelope,
faGear, faGear,
faPlus, faPlus,
faRightFromBracket faRightFromBracket,
} from '@fortawesome/free-solid-svg-icons'; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Menu, Transition } from '@headlessui/react'; import { Menu, Transition } from "@headlessui/react";
import logout from '~/pages/api/auth/Logout'; import logout from "~/pages/api/auth/Logout";
import getOrganization from '~/pages/api/organization/GetOrg';
import getOrganizations from '~/pages/api/organization/getOrgs';
import getUser from '~/pages/api/user/getUser';
import guidGenerator from '../utilities/randomId'; import getOrganization from "../../pages/api/organization/GetOrg";
import getOrganizations from "../../pages/api/organization/getOrgs";
import getUser from "../../pages/api/user/getUser";
import guidGenerator from "../utilities/randomId";
const supportOptions = [ const supportOptions = (t: TFunction) => [
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
'Join Slack Forum', t("nav:support.slack"),
'https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g' "https://join.slack.com/t/infisical/shared_invite/zt-1dgg63ln8-G7PCNJdCymAT9YF3j1ewVA",
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
'Read Docs', t("nav:support.docs"),
'https://infisical.com/docs/getting-started/introduction' "https://infisical.com/docs/getting-started/introduction",
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
'Open a GitHub Issue', t("nav:support.issue"),
'https://github.com/Infisical/infisical-cli/issues' "https://github.com/Infisical/infisical-cli/issues",
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
'Send us an Email', t("nav:support.email"),
'mailto:[email protected]' "mailto:[email protected]",
] ],
]; ];
export interface ICurrentOrg { export interface ICurrentOrg {
@@ -68,6 +68,10 @@ export default function Navbar() {
const [orgs, setOrgs] = useState([]); const [orgs, setOrgs] = useState([]);
const [currentOrg, setCurrentOrg] = useState<ICurrentOrg | undefined>(); const [currentOrg, setCurrentOrg] = useState<ICurrentOrg | undefined>();
const { t } = useTranslation();
const supportOptionsList = useMemo(() => supportOptions(t), [t]);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const userData = await getUser(); const userData = await getUser();
@@ -75,16 +79,16 @@ export default function Navbar() {
const orgsData = await getOrganizations(); const orgsData = await getOrganizations();
setOrgs(orgsData); setOrgs(orgsData);
const currentOrg = await getOrganization({ const currentOrg = await getOrganization({
orgId: String(localStorage.getItem('orgData.id')) orgId: String(localStorage.getItem("orgData.id")),
}); });
setCurrentOrg(currentOrg); setCurrentOrg(currentOrg);
})(); })();
}, []); }, []);
const closeApp = async () => { const closeApp = async () => {
console.log('Logging out...'); console.log("Logging out...");
await logout(); await logout();
router.push('/login'); router.push("/login");
}; };
return ( return (
@@ -119,18 +123,17 @@ export default function Navbar() {
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5"> <Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
{supportOptions.map((option) => ( {supportOptionsList.map(([icon, text, url]) => (
// eslint-disable-next-line react/jsx-no-target-blank
<a <a
key={guidGenerator()} key={guidGenerator()}
target="_blank" target="_blank"
rel="noopener" rel="noopener noreferrer"
href={String(option[2])} href={String(url)}
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5" className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
> >
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full"> <div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
{option[0]} {icon}
<div className="text-sm">{option[1]}</div> <div className="text-sm">{text}</div>
</div> </div>
</a> </a>
))} ))}
@@ -159,11 +162,11 @@ export default function Navbar() {
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none"> <Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
<div className="px-1 py-1 "> <div className="px-1 py-1 ">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide"> <div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
SIGNED IN AS {t("nav:user.signed-in-as")}
</div> </div>
<div <div
onClick={() => onClick={() =>
router.push('/settings/personal/' + router.query.id) router.push("/settings/personal/" + router.query.id)
} }
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md" className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
> >
@@ -173,11 +176,11 @@ export default function Navbar() {
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
<div> <div>
<p className="text-gray-300 px-2 pt-1 text-sm"> <p className="text-gray-300 px-2 pt-1 text-sm">
{' '} {" "}
{user?.firstName} {user?.lastName} {user?.firstName} {user?.lastName}
</p> </p>
<p className="text-gray-400 px-2 pb-1 text-xs"> <p className="text-gray-400 px-2 pb-1 text-xs">
{' '} {" "}
{user?.email} {user?.email}
</p> </p>
</div> </div>
@@ -190,11 +193,11 @@ export default function Navbar() {
</div> </div>
<div className="px-2 pt-2"> <div className="px-2 pt-2">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide"> <div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
CURRENT ORGANIZATION {t("nav:user.current-organization")}
</div> </div>
<div <div
onClick={() => onClick={() =>
router.push('/settings/org/' + router.query.id) router.push("/settings/org/" + router.query.id)
} }
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md" className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
> >
@@ -217,7 +220,7 @@ export default function Navbar() {
> >
<div <div
onClick={() => onClick={() =>
router.push('/settings/billing/' + router.query.id) router.push("/settings/billing/" + router.query.id)
} }
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200" className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
> >
@@ -225,7 +228,7 @@ export default function Navbar() {
className="text-lg pl-1.5 pr-3" className="text-lg pl-1.5 pr-3"
icon={faCoins} icon={faCoins}
/> />
<div className="text-sm">Usage & Billing</div> <div className="text-sm">{t("nav:user.usage-billing")}</div>
</div> </div>
</button> </button>
<button <button
@@ -235,7 +238,7 @@ export default function Navbar() {
<div <div
onClick={() => onClick={() =>
router.push( router.push(
'/settings/org/' + router.query.id + '?invite' "/settings/org/" + router.query.id + "?invite"
) )
} }
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1" className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
@@ -243,26 +246,26 @@ export default function Navbar() {
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4"> <span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
<FontAwesomeIcon icon={faPlus} className="ml-1" /> <FontAwesomeIcon icon={faPlus} className="ml-1" />
</span> </span>
<div className="text-sm ml-1">Invite Members</div> <div className="text-sm ml-1">{t("nav:user.invite")}</div>
</div> </div>
</button> </button>
</div> </div>
{orgs?.length > 1 && ( {orgs?.length > 1 && (
<div className="px-1 pt-1"> <div className="px-1 pt-1">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide"> <div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
OTHER ORGANIZATIONS {t("nav:user.other-organizations")}
</div> </div>
<div className="flex flex-col items-start px-1 mt-3 mb-2"> <div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs {orgs
.filter( .filter(
(org: { _id: string }) => (org: { _id: string }) =>
org._id != localStorage.getItem('orgData.id') org._id != localStorage.getItem("orgData.id")
) )
.map((org: { _id: string; name: string }) => ( .map((org: { _id: string; name: string }) => (
<div <div
key={guidGenerator()} key={guidGenerator()}
onClick={() => { onClick={() => {
localStorage.setItem('orgData.id', org._id); localStorage.setItem("orgData.id", org._id);
router.reload(); router.reload();
}} }}
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md" className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
@@ -287,8 +290,8 @@ export default function Navbar() {
onClick={closeApp} onClick={closeApp}
className={`${ className={`${
active active
? 'bg-red font-semibold text-white' ? "bg-red font-semibold text-white"
: 'text-gray-400' : "text-gray-400"
} group flex w-full items-center rounded-md px-2 py-2 text-sm`} } group flex w-full items-center rounded-md px-2 py-2 text-sm`}
> >
<div className="relative flex justify-start items-center cursor-pointer select-none"> <div className="relative flex justify-start items-center cursor-pointer select-none">
@@ -296,7 +299,7 @@ export default function Navbar() {
className="text-lg ml-1.5 mr-3" className="text-lg ml-1.5 mr-3"
icon={faRightFromBracket} icon={faRightFromBracket}
/> />
Log Out {t("common:logout")}
</div> </div>
</button> </button>
)} )}
@@ -0,0 +1,52 @@
import { GetServerSideProps, GetStaticProps } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
const DefaultNamespaces = ["common", "nav"];
type GetTranslatedStaticProps = (
namespaces: string[],
getStaticProps?: GetStaticProps<any>
) => GetStaticProps;
type GetTranslatedServerSideProps = (
namespaces: string[],
getStaticProps?: GetServerSideProps<any>
) => GetServerSideProps;
export const getTranslatedStaticProps: GetTranslatedStaticProps =
(namespaces, getStaticProps) =>
async ({ locale, ...context }) => {
let staticProps = { props: {} };
if (typeof getStaticProps === "function") {
staticProps = (await getStaticProps(context)) as any;
}
return {
...staticProps,
props: {
...(await serverSideTranslations(locale ?? "en", [
...DefaultNamespaces,
...namespaces,
])),
...staticProps.props,
},
};
};
export const getTranslatedServerSideProps: GetTranslatedServerSideProps =
(namespaces, getServerSideProps) =>
async ({ locale, ...context }) => {
let staticProps = { props: {} };
if (typeof getServerSideProps === "function") {
staticProps = (await getServerSideProps(context)) as any;
}
return {
...staticProps,
props: {
...(await serverSideTranslations(locale ?? "en", [
...DefaultNamespaces,
...namespaces,
])),
...staticProps.props,
},
};
};
+5
View File
@@ -16,3 +16,8 @@ export const publicPaths = [
`/terms`, `/terms`,
`/subprocessors`, `/subprocessors`,
]; ];
export const languageMap = {
en: "English",
ko: "한국어",
};
+26
View File
@@ -0,0 +1,26 @@
// @ts-check
/**
* @type {import('next-i18next').UserConfig}
*/
module.exports = {
// https://www.i18next.com/overview/configuration-options#logging
debug: process.env.NODE_ENV === "development",
i18n: {
defaultLocale: "en",
locales: ["en", "ko"],
},
fallbackLng: {
default: ["en"],
},
reloadOnPrerender: process.env.NODE_ENV === "development",
/**
* @link https://github.com/i18next/next-i18next#6-advanced-configuration
*/
// saveMissing: false,
// strictMode: true,
// serializeConfig: false,
// react: { useSuspense: false }
};
+5 -1
View File
@@ -1,4 +1,4 @@
// next.config.js const { i18n } = require("./next-i18next.config.js");
const ContentSecurityPolicy = ` const ContentSecurityPolicy = `
default-src 'self'; default-src 'self';
@@ -60,4 +60,8 @@ module.exports = {
}, },
]; ];
}, },
webpack: (config, { isServer, webpack }) => {
return config;
},
i18n,
}; };
+510 -45
View File
@@ -24,10 +24,12 @@
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"i18next": "^22.4.6",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"jsrp": "^0.2.4", "jsrp": "^0.2.4",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"next": "^12.2.5", "next": "^12.2.5",
"next-i18next": "^13.0.2",
"posthog-js": "^1.34.0", "posthog-js": "^1.34.0",
"query-string": "^7.1.3", "query-string": "^7.1.3",
"react": "^17.0.2", "react": "^17.0.2",
@@ -36,6 +38,7 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-github-btn": "^1.4.0", "react-github-btn": "^1.4.0",
"react-grid-layout": "^1.3.4", "react-grid-layout": "^1.3.4",
"react-i18next": "^12.1.1",
"react-mailchimp-subscribe": "^2.1.3", "react-mailchimp-subscribe": "^2.1.3",
"react-markdown": "^8.0.3", "react-markdown": "^8.0.3",
"react-redux": "^8.0.2", "react-redux": "^8.0.2",
@@ -393,11 +396,11 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.18.9", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
"integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.11"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -1368,14 +1371,14 @@
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.45.0", "version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.1.tgz",
"integrity": "sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==", "integrity": "sha512-r4RZ2Jl9kcQN7K/dcOT+J7NAimbiis4sSM9spvWimsBvDegMhKLA5vri2jG19PmIPbDjPeWzfUPQ2hjEzA4Nmg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.45.0", "@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/type-utils": "5.45.0", "@typescript-eslint/type-utils": "5.47.1",
"@typescript-eslint/utils": "5.45.0", "@typescript-eslint/utils": "5.47.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0", "natural-compare-lite": "^1.4.0",
@@ -1400,6 +1403,53 @@
} }
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
"integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
"integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
"integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1491,13 +1541,13 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "5.45.0", "version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.47.1.tgz",
"integrity": "sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==", "integrity": "sha512-/UKOeo8ee80A7/GJA427oIrBi/Gd4osk/3auBUg4Rn9EahFpevVV1mUK8hjyQD5lHPqX397x6CwOk5WGh1E/1w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "5.45.0", "@typescript-eslint/typescript-estree": "5.47.1",
"@typescript-eslint/utils": "5.45.0", "@typescript-eslint/utils": "5.47.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
}, },
@@ -1517,6 +1567,63 @@
} }
} }
}, },
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
"integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
"integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
"integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/debug": { "node_modules/@typescript-eslint/type-utils/node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1604,16 +1711,16 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "5.45.0", "version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.47.1.tgz",
"integrity": "sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==", "integrity": "sha512-l90SdwqfmkuIVaREZ2ykEfCezepCLxzWMo5gVfcJsJCaT4jHT+QjgSkYhs5BMQmWqE9k3AtIfk4g211z/sTMVw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12", "@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.45.0", "@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/types": "5.45.0", "@typescript-eslint/types": "5.47.1",
"@typescript-eslint/typescript-estree": "5.45.0", "@typescript-eslint/typescript-estree": "5.47.1",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"semver": "^7.3.7" "semver": "^7.3.7"
@@ -1629,6 +1736,97 @@
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
"integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
"integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
"integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
"integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.47.1",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/utils/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -1651,6 +1849,12 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/@typescript-eslint/utils/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "5.45.0", "version": "5.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz",
@@ -2401,7 +2605,6 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
"integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==", "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==",
"hasInstallScript": true, "hasInstallScript": true,
"optional": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/core-js" "url": "https://opencollective.com/core-js"
@@ -4051,6 +4254,14 @@
"react-is": "^16.7.0" "react-is": "^16.7.0"
} }
}, },
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/html-tokenize": { "node_modules/html-tokenize": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.1.tgz", "resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.1.tgz",
@@ -4113,6 +4324,33 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/i18next": {
"version": "22.4.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.6.tgz",
"integrity": "sha512-9Tm1ezxWyzV+306CIDMBbYBitC1jedQyYuuLtIv7oxjp2ohh8eyxP9xytIf+2bbQfhH784IQKPSYp+Zq9+YSbw==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"dependencies": {
"@babel/runtime": "^7.20.6"
}
},
"node_modules/i18next-fs-backend": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.1.1.tgz",
"integrity": "sha512-FTnj+UmNgT3YRml5ruRv0jMZDG7odOL/OP5PF5mOqvXud2vHrPOOs68Zdk6iqzL47cnnM0ZVkK2BAvpFeDJToA=="
},
"node_modules/ieee754": { "node_modules/ieee754": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -5500,6 +5738,45 @@
} }
} }
}, },
"node_modules/next-i18next": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/next-i18next/-/next-i18next-13.0.2.tgz",
"integrity": "sha512-aUHyKT2kztMgEP44zDB5KoW8XZUQawIdOYWXcrMH6lxAcS0kBsKX0uKMzGS5XlgLW88gvOVc3D7NdfCznLgyyg==",
"funding": [
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
},
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://github.com/belgattitude"
}
],
"dependencies": {
"@babel/runtime": "^7.20.6",
"@types/hoist-non-react-statics": "^3.3.1",
"core-js": "^3",
"hoist-non-react-statics": "^3.3.2",
"i18next-fs-backend": "^2.1.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"i18next": "^22.0.6",
"next": ">= 12.0.0",
"react": ">= 17.0.2",
"react-i18next": "^12.1.1"
}
},
"node_modules/node-abi": { "node_modules/node-abi": {
"version": "3.30.0", "version": "3.30.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz",
@@ -6295,6 +6572,27 @@
"react-dom": ">= 16.3.0" "react-dom": ">= 16.3.0"
} }
}, },
"node_modules/react-i18next": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.1.tgz",
"integrity": "sha512-mFdieOI0LDy84q3JuZU6Aou1DoWW2fhapcTGeBS8+vWSJuViuoCLQAMYSb0QoHhXS8B0WKUOPpx4cffAP7r/aA==",
"dependencies": {
"@babel/runtime": "^7.14.5",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 19.0.0",
"react": ">= 16.8.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"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",
@@ -7666,6 +7964,14 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -7986,11 +8292,11 @@
} }
}, },
"@babel/runtime": { "@babel/runtime": {
"version": "7.18.9", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
"integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.11"
} }
}, },
"@babel/runtime-corejs3": { "@babel/runtime-corejs3": {
@@ -8690,14 +8996,14 @@
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "5.45.0", "version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.1.tgz",
"integrity": "sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==", "integrity": "sha512-r4RZ2Jl9kcQN7K/dcOT+J7NAimbiis4sSM9spvWimsBvDegMhKLA5vri2jG19PmIPbDjPeWzfUPQ2hjEzA4Nmg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.45.0", "@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/type-utils": "5.45.0", "@typescript-eslint/type-utils": "5.47.1",
"@typescript-eslint/utils": "5.45.0", "@typescript-eslint/utils": "5.47.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0", "natural-compare-lite": "^1.4.0",
@@ -8706,6 +9012,32 @@
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
}, },
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
"integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1"
}
},
"@typescript-eslint/types": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
"integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
"dev": true
},
"@typescript-eslint/visitor-keys": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
"integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.47.1",
"eslint-visitor-keys": "^3.3.0"
}
},
"debug": { "debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -8763,17 +9095,48 @@
} }
}, },
"@typescript-eslint/type-utils": { "@typescript-eslint/type-utils": {
"version": "5.45.0", "version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.47.1.tgz",
"integrity": "sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==", "integrity": "sha512-/UKOeo8ee80A7/GJA427oIrBi/Gd4osk/3auBUg4Rn9EahFpevVV1mUK8hjyQD5lHPqX397x6CwOk5WGh1E/1w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/typescript-estree": "5.45.0", "@typescript-eslint/typescript-estree": "5.47.1",
"@typescript-eslint/utils": "5.45.0", "@typescript-eslint/utils": "5.47.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
}, },
"dependencies": { "dependencies": {
"@typescript-eslint/types": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
"integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
"integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
"integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.47.1",
"eslint-visitor-keys": "^3.3.0"
}
},
"debug": { "debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -8830,21 +9193,71 @@
} }
}, },
"@typescript-eslint/utils": { "@typescript-eslint/utils": {
"version": "5.45.0", "version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.47.1.tgz",
"integrity": "sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==", "integrity": "sha512-l90SdwqfmkuIVaREZ2ykEfCezepCLxzWMo5gVfcJsJCaT4jHT+QjgSkYhs5BMQmWqE9k3AtIfk4g211z/sTMVw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12", "@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.45.0", "@typescript-eslint/scope-manager": "5.47.1",
"@typescript-eslint/types": "5.45.0", "@typescript-eslint/types": "5.47.1",
"@typescript-eslint/typescript-estree": "5.45.0", "@typescript-eslint/typescript-estree": "5.47.1",
"eslint-scope": "^5.1.1", "eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"semver": "^7.3.7" "semver": "^7.3.7"
}, },
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
"integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1"
}
},
"@typescript-eslint/types": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
"integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
"integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.47.1",
"@typescript-eslint/visitor-keys": "5.47.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
"integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.47.1",
"eslint-visitor-keys": "^3.3.0"
}
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"eslint-scope": { "eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -8860,6 +9273,12 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true "dev": true
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
} }
} }
}, },
@@ -9400,8 +9819,7 @@
"core-js": { "core-js": {
"version": "3.26.0", "version": "3.26.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
"integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==", "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw=="
"optional": true
}, },
"core-js-pure": { "core-js-pure": {
"version": "3.26.1", "version": "3.26.1",
@@ -10644,6 +11062,14 @@
"react-is": "^16.7.0" "react-is": "^16.7.0"
} }
}, },
"html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"requires": {
"void-elements": "3.1.0"
}
},
"html-tokenize": { "html-tokenize": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.1.tgz", "resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.1.tgz",
@@ -10699,6 +11125,19 @@
"requires-port": "^1.0.0" "requires-port": "^1.0.0"
} }
}, },
"i18next": {
"version": "22.4.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.6.tgz",
"integrity": "sha512-9Tm1ezxWyzV+306CIDMBbYBitC1jedQyYuuLtIv7oxjp2ohh8eyxP9xytIf+2bbQfhH784IQKPSYp+Zq9+YSbw==",
"requires": {
"@babel/runtime": "^7.20.6"
}
},
"i18next-fs-backend": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.1.1.tgz",
"integrity": "sha512-FTnj+UmNgT3YRml5ruRv0jMZDG7odOL/OP5PF5mOqvXud2vHrPOOs68Zdk6iqzL47cnnM0ZVkK2BAvpFeDJToA=="
},
"ieee754": { "ieee754": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -11600,6 +12039,18 @@
"use-sync-external-store": "1.2.0" "use-sync-external-store": "1.2.0"
} }
}, },
"next-i18next": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/next-i18next/-/next-i18next-13.0.2.tgz",
"integrity": "sha512-aUHyKT2kztMgEP44zDB5KoW8XZUQawIdOYWXcrMH6lxAcS0kBsKX0uKMzGS5XlgLW88gvOVc3D7NdfCznLgyyg==",
"requires": {
"@babel/runtime": "^7.20.6",
"@types/hoist-non-react-statics": "^3.3.1",
"core-js": "^3",
"hoist-non-react-statics": "^3.3.2",
"i18next-fs-backend": "^2.1.0"
}
},
"node-abi": { "node-abi": {
"version": "3.30.0", "version": "3.30.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz",
@@ -12156,6 +12607,15 @@
"react-resizable": "^3.0.4" "react-resizable": "^3.0.4"
} }
}, },
"react-i18next": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.1.tgz",
"integrity": "sha512-mFdieOI0LDy84q3JuZU6Aou1DoWW2fhapcTGeBS8+vWSJuViuoCLQAMYSb0QoHhXS8B0WKUOPpx4cffAP7r/aA==",
"requires": {
"@babel/runtime": "^7.14.5",
"html-parse-stringify": "^3.0.1"
}
},
"react-is": { "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",
@@ -13126,6 +13586,11 @@
"unist-util-stringify-position": "^3.0.0" "unist-util-stringify-position": "^3.0.0"
} }
}, },
"void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="
},
"which": { "which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+3
View File
@@ -28,10 +28,12 @@
"fs": "^0.0.1-security", "fs": "^0.0.1-security",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"i18next": "^22.4.6",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"jsrp": "^0.2.4", "jsrp": "^0.2.4",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"next": "^12.2.5", "next": "^12.2.5",
"next-i18next": "^13.0.2",
"posthog-js": "^1.34.0", "posthog-js": "^1.34.0",
"query-string": "^7.1.3", "query-string": "^7.1.3",
"react": "^17.0.2", "react": "^17.0.2",
@@ -40,6 +42,7 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-github-btn": "^1.4.0", "react-github-btn": "^1.4.0",
"react-grid-layout": "^1.3.4", "react-grid-layout": "^1.3.4",
"react-i18next": "^12.1.1",
"react-mailchimp-subscribe": "^2.1.3", "react-mailchimp-subscribe": "^2.1.3",
"react-markdown": "^8.0.3", "react-markdown": "^8.0.3",
"react-redux": "^8.0.2", "react-redux": "^8.0.2",
+11 -1
View File
@@ -1,5 +1,6 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { appWithTranslation } from "next-i18next";
import { config } from "@fortawesome/fontawesome-svg-core"; import { config } from "@fortawesome/fontawesome-svg-core";
import Layout from "~/components/basic/Layout"; import Layout from "~/components/basic/Layout";
@@ -16,6 +17,15 @@ config.autoAddCss = false;
const App = ({ Component, pageProps, ...appProps }) => { const App = ({ Component, pageProps, ...appProps }) => {
const router = useRouter(); const router = useRouter();
useEffect(() => {
const storedLang = localStorage.getItem("lang");
if (router.locale ?? "en" !== storedLang ?? "en") {
router.push(router.asPath, router.asPath, {
locale: storedLang ?? "en",
});
}
}, [router.locale, router.pathname]);
useEffect(() => { useEffect(() => {
// Init for auto capturing // Init for auto capturing
const telemetry = new Telemetry().getInstance(); const telemetry = new Telemetry().getInstance();
@@ -52,7 +62,7 @@ const App = ({ Component, pageProps, ...appProps }) => {
); );
}; };
export default App; export default appWithTranslation(App);
{ {
/* <Script /* <Script
+6
View File
@@ -13,10 +13,12 @@ import {
faUserPlus faUserPlus
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useTranslation } from "next-i18next";
import onboardingCheck from '~/components/utilities/checks/OnboardingCheck'; import onboardingCheck from '~/components/utilities/checks/OnboardingCheck';
import registerUserAction from '../api/userActions/registerUserAction'; import registerUserAction from '../api/userActions/registerUserAction';
import { getTranslatedServerSideProps } from '~/components/utilities/withTranslateProps';
type ItemProps = { type ItemProps = {
text: string; text: string;
@@ -139,6 +141,8 @@ export default function Home() {
const [hasUserPushedSecrets, setHasUserPushedSecrets] = useState(false); const [hasUserPushedSecrets, setHasUserPushedSecrets] = useState(false);
const [usersInOrg, setUsersInOrg] = useState(false); const [usersInOrg, setUsersInOrg] = useState(false);
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
onboardingCheck({ onboardingCheck({
setHasUserClickedIntro, setHasUserClickedIntro,
@@ -226,3 +230,5 @@ export default function Home() {
} }
Home.requireAuth = true; Home.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps(["home"]);
+20 -8
View File
@@ -2,14 +2,21 @@ import { useEffect, useState } from "react";
import Head from "next/head"; import Head from "next/head";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import {
faArrowRight,
faCheck,
faRotate,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import ActivateBotDialog from "~/components/basic/dialog/ActivateBotDialog"; import ActivateBotDialog from "~/components/basic/dialog/ActivateBotDialog";
import IntegrationAccessTokenDialog from "~/components/basic/dialog/IntegrationAccessTokenDialog";
import CloudIntegrationSection from "~/components/integrations/CloudIntegrationSection"; import CloudIntegrationSection from "~/components/integrations/CloudIntegrationSection";
import FrameworkIntegrationSection from "~/components/integrations/FrameworkIntegrationSection"; import FrameworkIntegrationSection from "~/components/integrations/FrameworkIntegrationSection";
import Integration from "~/components/integrations/Integration";
import IntegrationSection from "~/components/integrations/IntegrationSection"; import IntegrationSection from "~/components/integrations/IntegrationSection";
import NavHeader from "~/components/navigation/NavHeader"; import NavHeader from "~/components/navigation/NavHeader";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import frameworkIntegrationOptions from "../../public/json/frameworkIntegrations.json"; import frameworkIntegrationOptions from "../../public/json/frameworkIntegrations.json";
import getBot from "../api/bot/getBot"; import getBot from "../api/bot/getBot";
@@ -35,6 +42,8 @@ export default function Integrations() {
const router = useRouter(); const router = useRouter();
const { t } = useTranslation();
useEffect(async () => { useEffect(async () => {
try { try {
// get cloud integration options // get cloud integration options
@@ -181,18 +190,17 @@ export default function Integrations() {
return ( return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white"> <div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head> <Head>
<title>Dashboard</title> <title>
{t("common:head-title", { title: t("integrations:title") })}
</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
<meta property="og:title" content="Manage your .env files in seconds" /> <meta property="og:title" content="Manage your .env files in seconds" />
<meta <meta name="og:description" content={t("integrations:description")} />
name="og:description"
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head> </Head>
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar"> <div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
<NavHeader <NavHeader
pageName="Project Integrations" pageName={t("integrations:title")}
isProjectRelated={true} isProjectRelated={true}
/> />
<ActivateBotDialog <ActivateBotDialog
@@ -229,3 +237,7 @@ export default function Integrations() {
} }
Integrations.requireAuth = true; Integrations.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
"integrations",
]);
+55 -33
View File
@@ -1,24 +1,34 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import Head from 'next/head'; import Head from "next/head";
import Image from 'next/image'; import Image from "next/image";
import Link from 'next/link'; import Link from "next/link";
import { useRouter } from 'next/router'; import { useRouter } from "next/router";
import { faWarning } from '@fortawesome/free-solid-svg-icons'; import { useTranslation } from "next-i18next";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from '~/components/basic/buttons/Button'; import Button from "~/components/basic/buttons/Button";
import Error from '~/components/basic/Error'; import Error from "~/components/basic/Error";
import InputField from '~/components/basic/InputField'; import InputField from "~/components/basic/InputField";
import attemptLogin from '~/utilities/attemptLogin'; import { getTranslatedStaticProps } from "~/components/utilities/withTranslateProps";
import attemptLogin from "~/utilities/attemptLogin";
import getWorkspaces from './api/workspace/getWorkspaces'; import getWorkspaces from "./api/workspace/getWorkspaces";
import ListBox from "~/components/basic/Listbox";
export default function Login() { export default function Login() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState("");
const [password, setPassword] = useState(''); const [password, setPassword] = useState("");
const [errorLogin, setErrorLogin] = useState(false); const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const router = useRouter(); const router = useRouter();
const { t } = useTranslation();
const lang = router.locale ?? "en";
const setLanguage = async (to: string) => {
router.push("/login", "/login", { locale: to });
localStorage.setItem("lang", to);
};
useEffect(() => { useEffect(() => {
const redirectToDashboard = async () => { const redirectToDashboard = async () => {
@@ -26,9 +36,9 @@ export default function Login() {
try { try {
const userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id; userWorkspace = userWorkspaces[0]._id;
router.push('/dashboard/' + userWorkspace); router.push("/dashboard/" + userWorkspace);
} catch (error) { } catch (error) {
console.log('Error - Not logged in yet'); console.log("Error - Not logged in yet");
} }
}; };
redirectToDashboard(); redirectToDashboard();
@@ -49,7 +59,7 @@ export default function Login() {
setErrorLogin, setErrorLogin,
router, router,
false, false,
true, true
).then(() => { ).then(() => {
setTimeout(function () { setTimeout(function () {
setIsLoading(false); setIsLoading(false);
@@ -60,14 +70,11 @@ export default function Login() {
return ( return (
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6"> <div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
<Head> <Head>
<title>Login</title> <title>{t("common:head-title", { title: t("login:title") })}</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
<meta property="og:title" content="Log In to Infisical" /> <meta property="og:title" content={t("login:og-title") ?? ""} />
<meta <meta name="og:description" content={t("login:og-description") ?? ""} />
name="og:description"
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head> </Head>
<Link href="/"> <Link href="/">
<div className="flex justify-center mb-8 mt-20 cursor-pointer"> <div className="flex justify-center mb-8 mt-20 cursor-pointer">
@@ -80,15 +87,16 @@ export default function Login() {
</div> </div>
</Link> </Link>
<form <form
onChange={() => setErrorLogin(false)} onSubmit={(e) => e.preventDefault()} onChange={() => setErrorLogin(false)}
onSubmit={(e) => e.preventDefault()}
> >
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl"> <div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<p className="text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6"> <p className="text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6">
Log in to your account {t("login:login")}
</p> </p>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28"> <div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
<InputField <InputField
label="Email" label={t("common:email")}
onChangeHandler={setEmail} onChangeHandler={setEmail}
type="email" type="email"
value={email} value={email}
@@ -99,7 +107,7 @@ export default function Login() {
</div> </div>
<div className="relative flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28"> <div className="relative flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
<InputField <InputField
label="Password" label={t("common:password")}
onChangeHandler={setPassword} onChangeHandler={setPassword}
type="password" type="password"
value={password} value={password}
@@ -112,12 +120,14 @@ export default function Login() {
<Link href="/verify-email">Forgot password?</Link> <Link href="/verify-email">Forgot password?</Link>
</div> </div>
</div> </div>
{!isLoading && errorLogin && <Error text="Your email and/or password are wrong." />} {!isLoading && errorLogin && (
<Error text="Your email and/or password are wrong." />
)}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm"> <div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg"> <div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button <Button
type="submit" type="submit"
text="Log In" text={t("login:login") ?? ""}
onButtonPressed={loginCheck} onButtonPressed={loginCheck}
loading={isLoading} loading={isLoading}
size="lg" size="lg"
@@ -131,21 +141,33 @@ export default function Login() {
{false && ( {false && (
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4"> <div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" /> <FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
We are experiencing minor technical difficulties. We are working on {t("common:maintenance-alert")}
solving it right now. Please come back in a few minutes.
</div> </div>
)} )}
<div className="flex flex-row items-center justify-center md:pb-4 mt-4"> <div className="flex flex-row items-center justify-center md:pb-4 mt-4">
<p className="text-sm flex justify-center text-gray-400 w-max"> <p className="text-sm flex justify-center text-gray-400 w-max">
Need an Infisical account? {t("login:need-account")}
</p> </p>
<Link href="/signup"> <Link href="/signup">
<button className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5"> <button className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5">
Sign up here. {t("login:create-account")}
</button> </button>
</Link> </Link>
</div> </div>
</form> </form>
<div className="mt-4 flex items-center justify-center w-full">
<div className="w-48 mx-auto">
<ListBox
selected={lang}
onChange={setLanguage}
data={["en", "ko"]}
isFull
text={`${t("common:language")}: `}
/>
</div>
</div>
</div> </div>
); );
} }
export const getStaticProps = getTranslatedStaticProps(["auth", "login"]);
+3 -2
View File
@@ -1,7 +1,6 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/image";
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons"; import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function NoProjects() { export default function NoProjects() {
return ( return (
@@ -31,3 +30,5 @@ export default function NoProjects() {
} }
NoProjects.requireAuth = true; NoProjects.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps(["noprojects"]);
+39 -36
View File
@@ -1,34 +1,35 @@
import { useState } from 'react'; import { useState } from "react";
import Image from 'next/image'; import Image from "next/image";
import { useRouter } from 'next/router'; import { useRouter } from "next/router";
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons'; import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from '~/components/basic/buttons/Button'; import Button from "~/components/basic/buttons/Button";
import InputField from '~/components/basic/InputField'; import InputField from "~/components/basic/InputField";
import passwordCheck from '~/components/utilities/checks/PasswordCheck'; import passwordCheck from "~/components/utilities/checks/PasswordCheck";
import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm'; import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm";
import EmailVerifyOnPasswordReset from './api/auth/EmailVerifyOnPasswordReset'; import EmailVerifyOnPasswordReset from "./api/auth/EmailVerifyOnPasswordReset";
import getBackupEncryptedPrivateKey from './api/auth/getBackupEncryptedPrivateKey'; import getBackupEncryptedPrivateKey from "./api/auth/getBackupEncryptedPrivateKey";
import resetPasswordOnAccountRecovery from './api/auth/resetPasswordOnAccountRecovery'; import resetPasswordOnAccountRecovery from "./api/auth/resetPasswordOnAccountRecovery";
import { getTranslatedStaticProps } from "~/components/utilities/withTranslateProps";
const queryString = require('query-string'); const queryString = require("query-string");
const nacl = require('tweetnacl'); const nacl = require("tweetnacl");
const jsrp = require('jsrp'); const jsrp = require("jsrp");
nacl.util = require('tweetnacl-util'); nacl.util = require("tweetnacl-util");
const client = new jsrp.client(); const client = new jsrp.client();
export default function PasswordReset() { export default function PasswordReset() {
const router = useRouter(); const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split('?')[1]); const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const token = parsedUrl.token; const token = parsedUrl.token;
const email = parsedUrl.to?.replace(' ', '+').trim(); const email = parsedUrl.to?.replace(" ", "+").trim();
const [verificationToken, setVerificationToken] = useState(''); const [verificationToken, setVerificationToken] = useState("");
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const [backupKey, setBackupKey] = useState(''); const [backupKey, setBackupKey] = useState("");
const [privateKey, setPrivateKey] = useState(''); const [privateKey, setPrivateKey] = useState("");
const [newPassword, setNewPassword] = useState(''); const [newPassword, setNewPassword] = useState("");
const [backupKeyError, setBackupKeyError] = useState(false); const [backupKeyError, setBackupKeyError] = useState(false);
const [passwordErrorLength, setPasswordErrorLength] = useState(false); const [passwordErrorLength, setPasswordErrorLength] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
@@ -43,7 +44,7 @@ export default function PasswordReset() {
ciphertext: result.encryptedPrivateKey, ciphertext: result.encryptedPrivateKey,
iv: result.iv, iv: result.iv,
tag: result.tag, tag: result.tag,
secret: backupKey secret: backupKey,
}) })
); );
setStep(3); setStep(3);
@@ -60,7 +61,7 @@ export default function PasswordReset() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: errorCheck currentErrorCheck: errorCheck,
}); });
if (!errorCheck) { if (!errorCheck) {
@@ -72,14 +73,14 @@ export default function PasswordReset() {
.padStart( .padStart(
32 + 32 +
(newPassword.slice(0, 32).length - new Blob([newPassword]).size), (newPassword.slice(0, 32).length - new Blob([newPassword]).size),
'0' "0"
) ),
}) as { ciphertext: string; iv: string; tag: string }; }) as { ciphertext: string; iv: string; tag: string };
client.init( client.init(
{ {
username: email, username: email,
password: newPassword password: newPassword,
}, },
async () => { async () => {
client.createVerifier( client.createVerifier(
@@ -90,12 +91,12 @@ export default function PasswordReset() {
iv, iv,
tag, tag,
salt: result.salt, salt: result.salt,
verifier: result.verifier verifier: result.verifier,
}); });
// if everything works, go the main dashboard page. // if everything works, go the main dashboard page.
if (response?.status === 200) { if (response?.status === 200) {
router.push('/login'); router.push("/login");
} }
} }
); );
@@ -122,14 +123,14 @@ export default function PasswordReset() {
onButtonPressed={async () => { onButtonPressed={async () => {
const response = await EmailVerifyOnPasswordReset({ const response = await EmailVerifyOnPasswordReset({
email, email,
code: token code: token,
}); });
if (response.status == 200) { if (response.status == 200) {
setVerificationToken((await response.json()).token); setVerificationToken((await response.json()).token);
setStep(2); setStep(2);
} else { } else {
console.log('ERROR', response); console.log("ERROR", response);
router.push('/email-not-verified'); router.push("/email-not-verified");
} }
}} }}
size="lg" size="lg"
@@ -195,7 +196,7 @@ export default function PasswordReset() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: false currentErrorCheck: false,
}); });
}} }}
type="password" type="password"
@@ -224,7 +225,7 @@ export default function PasswordReset() {
)} )}
<div <div
className={`${ className={`${
passwordErrorLength ? 'text-gray-400' : 'text-gray-600' passwordErrorLength ? "text-gray-400" : "text-gray-600"
} text-sm`} } text-sm`}
> >
14 characters 14 characters
@@ -241,7 +242,7 @@ export default function PasswordReset() {
)} )}
<div <div
className={`${ className={`${
passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600' passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"
} text-sm`} } text-sm`}
> >
1 lowercase character 1 lowercase character
@@ -258,7 +259,7 @@ export default function PasswordReset() {
)} )}
<div <div
className={`${ className={`${
passwordErrorNumber ? 'text-gray-400' : 'text-gray-600' passwordErrorNumber ? "text-gray-400" : "text-gray-600"
} text-sm`} } text-sm`}
> >
1 number 1 number
@@ -288,3 +289,5 @@ export default function PasswordReset() {
</div> </div>
); );
} }
export const getServerSideProps = getTranslatedStaticProps([]);
+35 -24
View File
@@ -1,9 +1,11 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Head from "next/head"; import Head from "next/head";
import { useTranslation } from "next-i18next";
import Plan from "~/components/billing/Plan"; import Plan from "~/components/billing/Plan";
import NavHeader from "~/components/navigation/NavHeader"; import NavHeader from "~/components/navigation/NavHeader";
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "~/utilities/config"; import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "~/utilities/config";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription"; import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
import getOrganizationUsers from "../../api/organization/GetOrgUsers"; import getOrganizationUsers from "../../api/organization/GetOrgUsers";
@@ -12,36 +14,38 @@ export default function SettingsBilling() {
let [currentPlan, setCurrentPlan] = useState(""); let [currentPlan, setCurrentPlan] = useState("");
let [numUsers, setNumUsers] = useState(""); let [numUsers, setNumUsers] = useState("");
const { t } = useTranslation();
const plans = [ const plans = [
{ {
key: 1, key: 1,
name: "Starter", name: t("billing:starter.name"),
price: "Free", price: t("billing:free"),
priceExplanation: "up to 5 team members", priceExplanation: t("billing:starter.price-explanation"),
text: "Manage any project with 5 members for free!", text: t("billing:starter.text"),
subtext: "$5 per member/month afterwards.", subtext: t("billing:starter.subtext"),
buttonTextMain: "Downgrade", buttonTextMain: t("billing:downgrade"),
buttonTextSecondary: "Learn More", buttonTextSecondary: t("billing:learn-more"),
current: currentPlan == STRIPE_PRODUCT_STARTER, current: currentPlan == STRIPE_PRODUCT_STARTER,
}, },
{ {
key: 2, key: 2,
name: "Professional", name: t("billing:professional.name"),
price: "$9", price: "$9",
priceExplanation: "/member/month", priceExplanation: t("billing:professional.price-explanation"),
subtext: "Includes unlimited projects & members.", subtext: t("billing:professional.subtext"),
text: "Keep up with key management as you grow.", text: t("billing:professional.text"),
buttonTextMain: "Upgrade", buttonTextMain: t("billing:upgrade"),
buttonTextSecondary: "Learn More", buttonTextSecondary: t("billing:learn-more"),
current: currentPlan == STRIPE_PRODUCT_PRO, current: currentPlan == STRIPE_PRODUCT_PRO,
}, },
{ {
key: 3, key: 3,
name: "Enterprise", name: t("billing:enterprise.name"),
price: "Custom Pricing", price: t("billing:custom-pricing"),
text: "More control for advanced organizations.", text: t("billing:enterprise.text"),
buttonTextMain: "Schedule a Demo", buttonTextMain: t("billing:schedule-demo"),
buttonTextSecondary: "Learn More", buttonTextSecondary: t("billing:learn-more"),
current: false, current: false,
}, },
]; ];
@@ -61,30 +65,32 @@ export default function SettingsBilling() {
return ( return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white"> <div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head> <Head>
<title>Settings - Billing</title> <title>{t("common:head-title", { title: t("billing:title") })}</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<div className="flex flex-row"> <div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto"> <div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Usage & Billing" /> <NavHeader pageName={t("billing:title")} />
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl"> <div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl"> <div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200"> <p className="font-semibold mr-4 text-gray-200">
Usage & Billing {t("billing:title")}
</p> </p>
<p className="font-normal mr-4 text-gray-400 text-base"> <p className="font-normal mr-4 text-gray-400 text-base">
View and manage your {"organization's"} subscription here. {t("billing:description")}
</p> </p>
</div> </div>
</div> </div>
<div className="flex flex-col ml-6 text-mineshaft-50"> <div className="flex flex-col ml-6 text-mineshaft-50">
<p className="text-xl font-semibold">Subscription</p> <p className="text-xl font-semibold">{t("billing:subscription")}</p>
<div className="flex flex-row mt-4 overflow-x-auto"> <div className="flex flex-row mt-4 overflow-x-auto">
{plans.map((plan) => ( {plans.map((plan) => (
<Plan key={plan.name} plan={plan} /> <Plan key={plan.name} plan={plan} />
))} ))}
</div> </div>
<p className="text-xl font-bold mt-12">Current Usage</p> <p className="text-xl font-bold mt-12">
{t("billing:current-usage")}
</p>
<div className="flex flex-row"> <div className="flex flex-row">
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col"> <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
<p className="text-6xl font-bold">{numUsers}</p> <p className="text-6xl font-bold">{numUsers}</p>
@@ -107,3 +113,8 @@ export default function SettingsBilling() {
} }
SettingsBilling.requireAuth = true; SettingsBilling.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
"settings",
"billing",
]);
+41 -28
View File
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import Head from 'next/head'; import Head from "next/head";
import { useRouter } from 'next/router'; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { import {
faMagnifyingGlass, faMagnifyingGlass,
faPlus, faPlus,
@@ -9,13 +10,14 @@ import {
import { faCheck } from '@fortawesome/free-solid-svg-icons'; import { faCheck } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from '~/components/basic/buttons/Button'; import Button from "~/components/basic/buttons/Button";
import AddIncidentContactDialog from '~/components/basic/dialog/AddIncidentContactDialog'; import AddIncidentContactDialog from "~/components/basic/dialog/AddIncidentContactDialog";
import AddUserDialog from '~/components/basic/dialog/AddUserDialog'; import AddUserDialog from "~/components/basic/dialog/AddUserDialog";
import InputField from '~/components/basic/InputField'; import InputField from "~/components/basic/InputField";
import UserTable from '~/components/basic/table/UserTable'; import UserTable from "~/components/basic/table/UserTable";
import NavHeader from '~/components/navigation/NavHeader'; import NavHeader from "~/components/navigation/NavHeader";
import guidGenerator from '~/utilities/randomId'; import guidGenerator from "~/utilities/randomId";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import addUserToOrg from '../../api/organization/addUserToOrg'; import addUserToOrg from '../../api/organization/addUserToOrg';
import deleteIncidentContact from '../../api/organization/deleteIncidentContact'; import deleteIncidentContact from '../../api/organization/deleteIncidentContact';
@@ -49,6 +51,8 @@ export default function SettingsOrg() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [currentPlan, setCurrentPlan] = useState(''); const [currentPlan, setCurrentPlan] = useState('');
const { t } = useTranslation();
useEffect(async () => { useEffect(async () => {
let org = await getOrganization({ let org = await getOrganization({
orgId: localStorage.getItem('orgData.id') orgId: localStorage.getItem('orgData.id')
@@ -156,12 +160,14 @@ export default function SettingsOrg() {
return ( return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white"> <div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head> <Head>
<title>Settings</title> <title>
{t("common:head-title", { title: t("settings-org:title") })}
</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<div className="flex flex-row"> <div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto"> <div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Organization Settings" /> <NavHeader pageName={t("settings-org:title")} />
<AddIncidentContactDialog <AddIncidentContactDialog
isOpen={isAddIncidentContactOpen} isOpen={isAddIncidentContactOpen}
closeModal={closeAddIncidentContactModal} closeModal={closeAddIncidentContactModal}
@@ -172,10 +178,10 @@ export default function SettingsOrg() {
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl"> <div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl"> <div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200"> <p className="font-semibold mr-4 text-gray-200">
Organization Settings {t("settings-org:title")}
</p> </p>
<p className="font-normal mr-4 text-gray-400 text-base"> <p className="font-normal mr-4 text-gray-400 text-base">
View and manage your organization here. {t("settings-org:description")}
</p> </p>
</div> </div>
</div> </div>
@@ -185,7 +191,7 @@ export default function SettingsOrg() {
<div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6"> <div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
<div className="max-h-28 w-full max-w-md mr-auto"> <div className="max-h-28 w-full max-w-md mr-auto">
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2"> <p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
Display Name {t("common:display-name")}
</p> </p>
<InputField <InputField
// label="Organization Name" // label="Organization Name"
@@ -199,13 +205,13 @@ export default function SettingsOrg() {
<div className="flex justify-start w-full"> <div className="flex justify-start w-full">
<div className={`flex justify-start max-w-sm mt-4 mb-2`}> <div className={`flex justify-start max-w-sm mt-4 mb-2`}>
<Button <Button
text="Save Changes" text={t("common:save-changes")}
onButtonPressed={() => submitChanges(orgName)} onButtonPressed={() => submitChanges(orgName)}
color="mineshaft" color="mineshaft"
size="md" size="md"
active={buttonReady} active={buttonReady}
iconDisabled={faCheck} iconDisabled={faCheck}
textDisabled="Saved" textDisabled={t("common:saved")}
/> />
</div> </div>
</div> </div>
@@ -214,11 +220,10 @@ export default function SettingsOrg() {
</div> </div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6"> <div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6">
<p className="font-semibold mr-4 text-white text-xl"> <p className="font-semibold mr-4 text-white text-xl">
Organization Members {t("section-members:org-members")}
</p> </p>
<p className="mr-4 text-gray-400 mt-2 mb-2"> <p className="mr-4 text-gray-400 mt-2 mb-2">
Manage members of your organization. These users could {t("section-members:org-members-description")}
afterwards be formed into projects.
</p> </p>
<AddUserDialog <AddUserDialog
isOpen={isAddUserOpen} isOpen={isAddUserOpen}
@@ -241,12 +246,12 @@ export default function SettingsOrg() {
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none" className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchUsers} value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)} onChange={(e) => setSearchUsers(e.target.value)}
placeholder={'Search members...'} placeholder={t("section-members:search-members")}
/> />
</div> </div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start"> <div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
<Button <Button
text="Add Member" text={t("section-members:add-member")}
onButtonPressed={openAddUserModal} onButtonPressed={openAddUserModal}
color="mineshaft" color="mineshaft"
size="md" size="md"
@@ -276,16 +281,15 @@ export default function SettingsOrg() {
<div className="flex flex-row max-w-5xl justify-between items-center w-full"> <div className="flex flex-row max-w-5xl justify-between items-center w-full">
<div className="flex flex-col justify-between w-full max-w-3xl"> <div className="flex flex-col justify-between w-full max-w-3xl">
<p className="text-xl font-semibold mb-3 min-w-max"> <p className="text-xl font-semibold mb-3 min-w-max">
Incident Contacts {t("section-incident:incident-contacts")}
</p> </p>
<p className="text-xs text-gray-500 mb-2 min-w-max"> <p className="text-xs text-gray-500 mb-2 min-w-max">
These contacts will be notified in the unlikely event of a {t("section-incident:incident-contacts-description")}
severe incident.
</p> </p>
</div> </div>
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center"> <div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
<Button <Button
text="Add Contact" text={t("section-incident:add-contact")}
onButtonPressed={openAddIncidentContactModal} onButtonPressed={openAddIncidentContactModal}
color="mineshaft" color="mineshaft"
size="md" size="md"
@@ -302,7 +306,7 @@ export default function SettingsOrg() {
className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none" className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none"
value={searchIncidentContact} value={searchIncidentContact}
onChange={(e) => setSearchIncidentContact(e.target.value)} onChange={(e) => setSearchIncidentContact(e.target.value)}
placeholder={'Search...'} placeholder={t("common:search")}
/> />
</div> </div>
{incidentContacts?.filter((email) => {incidentContacts?.filter((email) =>
@@ -330,7 +334,9 @@ export default function SettingsOrg() {
)) ))
) : ( ) : (
<div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6"> <div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6">
<p className="text-gray-400">No incident contacts found.</p> <p className="text-gray-400">
{t("section-incident:no-incident-contacts")}
</p>
</div> </div>
)} )}
</div> </div>
@@ -378,3 +384,10 @@ export default function SettingsOrg() {
} }
SettingsOrg.requireAuth = true; SettingsOrg.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
"settings",
"settings-org",
"section-incident",
"section-members",
]);
+60 -23
View File
@@ -1,14 +1,18 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button"; import Button from "~/components/basic/buttons/Button";
import InputField from "~/components/basic/InputField"; import InputField from "~/components/basic/InputField";
import ListBox from "~/components/basic/Listbox";
import NavHeader from "~/components/navigation/NavHeader"; import NavHeader from "~/components/navigation/NavHeader";
import changePassword from "~/components/utilities/cryptography/changePassword"; import changePassword from "~/components/utilities/cryptography/changePassword";
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey"; import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
import passwordCheck from "~/utilities/checks/PasswordCheck"; import passwordCheck from "~/utilities/checks/PasswordCheck";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import getUser from "../../api/user/getUser"; import getUser from "../../api/user/getUser";
@@ -26,6 +30,15 @@ export default function PersonalSettings() {
const [backupKeyIssued, setBackupKeyIssued] = useState(false); const [backupKeyIssued, setBackupKeyIssued] = useState(false);
const [backupKeyError, setBackupKeyError] = useState(false); const [backupKeyError, setBackupKeyError] = useState(false);
const { t } = useTranslation();
const router = useRouter();
const lang = router.locale ?? "en";
const setLanguage = async (to) => {
router.push(router.asPath, router.asPath, { locale: to });
localStorage.setItem("lang", to);
};
useEffect(async () => { useEffect(async () => {
let user = await getUser(); let user = await getUser();
setPersonalEmail(user.email); setPersonalEmail(user.email);
@@ -35,19 +48,24 @@ export default function PersonalSettings() {
return ( return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white"> <div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head> <Head>
<title>Personal Settings</title> <title>
{t("common:head-title", { title: t("settings-personal:title") })}
</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<div className="flex flex-row"> <div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto"> <div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Personal Settings" isProjectRelated={false} /> <NavHeader
pageName={t("settings-personal:title")}
isProjectRelated={false}
/>
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl"> <div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl"> <div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200"> <p className="font-semibold mr-4 text-gray-200">
Personal Settings {t("settings-personal:title")}
</p> </p>
<p className="font-normal mr-4 text-gray-400 text-base"> <p className="font-normal mr-4 text-gray-400 text-base">
View and manage your personal information here. {t("settings-personal:description")}
</p> </p>
</div> </div>
</div> </div>
@@ -100,17 +118,32 @@ export default function PersonalSettings() {
</div> */} </div> */}
</div> </div>
</div> </div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
<p className="text-xl font-semibold self-start">
{t("settings-personal:change-language")}
</p>
<div className="max-h-28 w-ful mt-4">
<ListBox
selected={lang}
onChange={setLanguage}
data={["en", "ko"]}
width="full"
text={`${t("common:language")}: `}
/>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6"> <div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
<div className="flex flex-row max-w-5xl justify-between items-center w-full"> <div className="flex flex-row max-w-5xl justify-between items-center w-full">
<div className="flex flex-col justify-between w-full max-w-3xl"> <div className="flex flex-col justify-between w-full max-w-3xl">
<p className="text-xl font-semibold mb-3 min-w-max"> <p className="text-xl font-semibold mb-3 min-w-max">
Change password {t("section-password:change")}
</p> </p>
</div> </div>
</div> </div>
<div className="max-w-xl w-full"> <div className="max-w-xl w-full">
<InputField <InputField
label="Current Password" label={t("section-password:current")}
onChangeHandler={(password) => { onChangeHandler={(password) => {
setCurrentPassword(password); setCurrentPassword(password);
}} }}
@@ -118,13 +151,13 @@ export default function PersonalSettings() {
value={currentPassword} value={currentPassword}
isRequired isRequired
error={currentPasswordError} error={currentPasswordError}
errorText="The current password may be wrong" errorText={t("section-password:current-wrong")}
autoComplete="current-password" autoComplete="current-password"
id="current-password" id="current-password"
/> />
<div className="py-2"></div> <div className="py-2"></div>
<InputField <InputField
label="New Password" label={t("section-password:new")}
onChangeHandler={(password) => { onChangeHandler={(password) => {
setNewPassword(password); setNewPassword(password);
passwordCheck({ passwordCheck({
@@ -152,7 +185,7 @@ export default function PersonalSettings() {
passwordErrorNumber ? ( passwordErrorNumber ? (
<div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2"> <div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
<div className={`text-gray-400 text-sm mb-1`}> <div className={`text-gray-400 text-sm mb-1`}>
Password should contain at least: {t("section-password:validate-base")}
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength ? ( {passwordErrorLength ? (
@@ -171,7 +204,7 @@ export default function PersonalSettings() {
passwordErrorLength ? "text-gray-400" : "text-gray-600" passwordErrorLength ? "text-gray-400" : "text-gray-600"
} text-sm`} } text-sm`}
> >
14 characters {t("section-password:validate-length")}
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className="flex flex-row justify-start items-center ml-1">
@@ -193,7 +226,7 @@ export default function PersonalSettings() {
: "text-gray-600" : "text-gray-600"
} text-sm`} } text-sm`}
> >
1 lowercase character {t("section-password:validate-case")}
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className="flex flex-row justify-start items-center ml-1">
@@ -213,7 +246,7 @@ export default function PersonalSettings() {
passwordErrorNumber ? "text-gray-400" : "text-gray-600" passwordErrorNumber ? "text-gray-400" : "text-gray-600"
} text-sm`} } text-sm`}
> >
1 number {t("section-password:validate-number")}
</div> </div>
</div> </div>
</div> </div>
@@ -222,7 +255,7 @@ export default function PersonalSettings() {
)} )}
<div className="flex flex-row items-center mt-3 w-52 pr-3"> <div className="flex flex-row items-center mt-3 w-52 pr-3">
<Button <Button
text="Change Password" text={t("section-password:change")}
onButtonPressed={() => { onButtonPressed={() => {
if ( if (
!passwordErrorLength && !passwordErrorLength &&
@@ -251,7 +284,7 @@ export default function PersonalSettings() {
passwordErrorNumber passwordErrorNumber
) )
} }
textDisabled="Change Password" textDisabled={t("section-password:change")}
/> />
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
@@ -266,34 +299,32 @@ export default function PersonalSettings() {
<div className="flex flex-row max-w-5xl justify-between items-center w-full"> <div className="flex flex-row max-w-5xl justify-between items-center w-full">
<div className="flex flex-col justify-between w-full max-w-3xl"> <div className="flex flex-col justify-between w-full max-w-3xl">
<p className="text-xl font-semibold mb-3 min-w-max"> <p className="text-xl font-semibold mb-3 min-w-max">
Emergency Kit {t("settings-personal:emergency.name")}
</p> </p>
<p className="text-sm text-mineshaft-300 min-w-max"> <p className="text-sm text-mineshaft-300 min-w-max">
Your Emergency Kit contains the information you’ll need to {t("settings-personal:emergency.text1")}
sign in to your Infisical account.
</p> </p>
<p className="text-sm text-mineshaft-300 mb-5 min-w-max"> <p className="text-sm text-mineshaft-300 mb-5 min-w-max">
Only the latest issued Emergency Kit remains valid. To get a {t("settings-personal:emergency.text2")}
new Emergency Kit, verify your password.
</p> </p>
</div> </div>
</div> </div>
<div className="w-full max-w-xl mb-4"> <div className="w-full max-w-xl mb-4">
<InputField <InputField
label="Current Password" label={t("section-password:current")}
onChangeHandler={setBackupPassword} onChangeHandler={setBackupPassword}
type="password" type="password"
value={backupPassword} value={backupPassword}
isRequired isRequired
error={backupKeyError} error={backupKeyError}
errorText="The current password is wrong" errorText={t("section-password:current-wrong")}
autoComplete="current-password" autoComplete="current-password"
id="current-password" id="current-password"
/> />
</div> </div>
<div className="flex flex-row items-center mt-3 w-full w-60"> <div className="flex flex-row items-center mt-3 w-full w-60">
<Button <Button
text="Download Emergency Kit" text={t("settings-personal:emergency.download")}
onButtonPressed={() => { onButtonPressed={() => {
issueBackupKey({ issueBackupKey({
email: personalEmail, email: personalEmail,
@@ -306,7 +337,7 @@ export default function PersonalSettings() {
color="mineshaft" color="mineshaft"
size="md" size="md"
active={backupPassword != ""} active={backupPassword != ""}
textDisabled="Download Emergency Kit" textDisabled={t("settings-personal:emergency.download")}
/> />
<FontAwesomeIcon <FontAwesomeIcon
icon={faCheck} icon={faCheck}
@@ -324,3 +355,9 @@ export default function PersonalSettings() {
} }
PersonalSettings.requireAuth = true; PersonalSettings.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
"settings",
"settings-personal",
"section-password",
]);
+40 -26
View File
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
import Button from "~/components/basic/buttons/Button"; import Button from "~/components/basic/buttons/Button";
@@ -8,6 +9,7 @@ import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDial
import InputField from "~/components/basic/InputField"; import InputField from "~/components/basic/InputField";
import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable"; import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable";
import NavHeader from "~/components/navigation/NavHeader"; import NavHeader from "~/components/navigation/NavHeader";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import getServiceTokens from "../../api/serviceToken/getServiceTokens"; import getServiceTokens from "../../api/serviceToken/getServiceTokens";
import deleteWorkspace from "../../api/workspace/deleteWorkspace"; import deleteWorkspace from "../../api/workspace/deleteWorkspace";
@@ -25,6 +27,8 @@ export default function SettingsBasic() {
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] =
useState(false); useState(false);
const { t } = useTranslation();
useEffect(async () => { useEffect(async () => {
let userWorkspaces = await getWorkspaces(); let userWorkspaces = await getWorkspaces();
userWorkspaces.map((userWorkspace) => { userWorkspaces.map((userWorkspace) => {
@@ -89,7 +93,9 @@ export default function SettingsBasic() {
return ( return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white"> <div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head> <Head>
<title>Settings</title> <title>
{t("common:head-title", { title: t("settings-project:title") })}
</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<AddServiceTokenDialog <AddServiceTokenDialog
@@ -100,14 +106,17 @@ export default function SettingsBasic() {
/> />
<div className="flex flex-row mr-6 max-w-5xl"> <div className="flex flex-row mr-6 max-w-5xl">
<div className="w-full max-h-screen pb-2 overflow-y-auto"> <div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Project Settings" isProjectRelated={true} /> <NavHeader
pageName={t("settings-project:title")}
isProjectRelated={true}
/>
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl"> <div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
<div className="flex flex-col justify-start items-start text-3xl"> <div className="flex flex-col justify-start items-start text-3xl">
<p className="font-semibold mr-4 text-gray-200"> <p className="font-semibold mr-4 text-gray-200">
Project Settings {t("settings-project:title")}
</p> </p>
<p className="font-normal mr-4 text-gray-400 text-base"> <p className="font-normal mr-4 text-gray-400 text-base">
These settings only apply to the currently selected Project. {t("settings-project:description")}
</p> </p>
</div> </div>
</div> </div>
@@ -115,7 +124,9 @@ export default function SettingsBasic() {
<div className="flex flex-col"> <div className="flex flex-col">
<div className="min-w-md mt-2 flex flex-col items-start"> <div className="min-w-md mt-2 flex flex-col items-start">
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2"> <div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
<p className="text-xl font-semibold mb-4">Display Name</p> <p className="text-xl font-semibold mb-4">
{t("common:display-name")}
</p>
<div className="max-h-28 w-full max-w-md mr-auto"> <div className="max-h-28 w-full max-w-md mr-auto">
<InputField <InputField
onChangeHandler={modifyWorkspaceName} onChangeHandler={modifyWorkspaceName}
@@ -128,7 +139,7 @@ export default function SettingsBasic() {
<div className="flex justify-start w-full"> <div className="flex justify-start w-full">
<div className={`flex justify-start max-w-sm mt-4 mb-2`}> <div className={`flex justify-start max-w-sm mt-4 mb-2`}>
<Button <Button
text="Save Changes" text={t("common:save-changes")}
onButtonPressed={() => submitChanges(workspaceName)} onButtonPressed={() => submitChanges(workspaceName)}
color="mineshaft" color="mineshaft"
size="md" size="md"
@@ -140,15 +151,14 @@ export default function SettingsBasic() {
</div> </div>
</div> </div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4"> <div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
<p className="text-xl font-semibold self-start">Project ID</p> <p className="text-xl font-semibold self-start">
{t("common:project-id")}
</p>
<p className="text-base text-gray-400 font-normal self-start mt-4"> <p className="text-base text-gray-400 font-normal self-start mt-4">
To integrate Infisical into your code base and get automatic {t("settings-project:project-id-description")}
injection of environmental variables, you should use the
following Project ID.
</p> </p>
<p className="text-base text-gray-400 font-normal self-start"> <p className="text-base text-gray-400 font-normal self-start">
For more guidance, including code snipets for various {t("settings-project:project-id-description2")}
languages and frameworks, see{" "}
{/* eslint-disable-next-line react/jsx-no-target-blank */} {/* eslint-disable-next-line react/jsx-no-target-blank */}
<a <a
href="https://infisical.com/docs/getting-started/introduction" href="https://infisical.com/docs/getting-started/introduction"
@@ -156,7 +166,7 @@ export default function SettingsBasic() {
rel="noopener" rel="noopener"
className="text-primary hover:opacity-80 duration-200" className="text-primary hover:opacity-80 duration-200"
> >
Infisical Docs. {t("settings-project:docs")}
</a> </a>
</p> </p>
<div className="max-h-28 w-ful"> <div className="max-h-28 w-ful">
@@ -166,7 +176,7 @@ export default function SettingsBasic() {
placeholder="" placeholder=""
isRequired isRequired
static static
text="This is your project&apos;s auto-generated unique identifier. It can&apos;t be changed." text={t("settings-project:auto-generated")}
/> />
</div> </div>
</div> </div>
@@ -174,16 +184,15 @@ export default function SettingsBasic() {
<div className="flex flex-row justify-between w-full"> <div className="flex flex-row justify-between w-full">
<div className="flex flex-col w-full"> <div className="flex flex-col w-full">
<p className="text-xl font-semibold mb-3"> <p className="text-xl font-semibold mb-3">
Service Tokens {t("section-token:service-tokens")}
</p> </p>
<p className="text-base text-gray-400 mb-4"> <p className="text-base text-gray-400 mb-4">
Every service token is specific to you, a certain {t("section-token:service-tokens-description")}
project and a certain environment within this project.
</p> </p>
</div> </div>
<div className="w-48"> <div className="w-48">
<Button <Button
text="Add New Token" text={t("section-token:add-new")}
onButtonPressed={() => { onButtonPressed={() => {
setIsAddServiceTokenDialogOpen(true); setIsAddServiceTokenDialogOpen(true);
}} }}
@@ -237,15 +246,15 @@ export default function SettingsBasic() {
</div> </div>
</div> </div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2"> <div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
<p className="text-xl font-bold text-red">Danger Zone</p> <p className="text-xl font-bold text-red">
{t("settings-project:danger-zone")}
</p>
<p className="mt-2 text-md text-gray-400"> <p className="mt-2 text-md text-gray-400">
As soon as you delete this project, you will not be able to undo {t("settings-project:danger-zone-note")}
it. This will immediately remove all the keys. If you still want
to do that, please enter the name of the project below.
</p> </p>
<div className="max-h-28 w-full max-w-md mr-auto mt-4"> <div className="max-h-28 w-full max-w-md mr-auto mt-4">
<InputField <InputField
label="Project to be Deleted" label={t("settings-project:project-to-delete")}
onChangeHandler={setWorkspaceToBeDeletedName} onChangeHandler={setWorkspaceToBeDeletedName}
type="varName" type="varName"
value={workspaceToBeDeletedName} value={workspaceToBeDeletedName}
@@ -258,11 +267,10 @@ export default function SettingsBasic() {
className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2" className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={executeDeletingWorkspace} onClick={executeDeletingWorkspace}
> >
Delete Project {t("settings-project:delete-project")}
</button> </button>
<p className="mt-0.5 ml-1 text-xs text-gray-500"> <p className="mt-0.5 ml-1 text-xs text-gray-500">
Note: You can only delete a project in case you have more than {t("settings-project:delete-project-note")}
one.
</p> </p>
</div> </div>
</div> </div>
@@ -273,3 +281,9 @@ export default function SettingsBasic() {
} }
SettingsBasic.requireAuth = true; SettingsBasic.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
"settings",
"settings-project",
"section-token",
]);
+128 -115
View File
@@ -1,71 +1,73 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import ReactCodeInput from 'react-code-input'; import ReactCodeInput from "react-code-input";
import Head from 'next/head'; import Head from "next/head";
import Image from 'next/image'; import Image from "next/image";
import Link from 'next/link'; import Link from "next/link";
import { useRouter } from 'next/router'; import { useRouter } from "next/router";
import { faCheck, faWarning, faX } from '@fortawesome/free-solid-svg-icons'; import { useTranslation } from "next-i18next";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheck, faWarning, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from '~/components/basic/buttons/Button'; import Button from "~/components/basic/buttons/Button";
import Error from '~/components/basic/Error'; import Error from "~/components/basic/Error";
import InputField from '~/components/basic/InputField'; import InputField from "~/components/basic/InputField";
import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm'; import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm";
import issueBackupKey from '~/components/utilities/cryptography/issueBackupKey'; import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
import attemptLogin from '~/utilities/attemptLogin'; import { getTranslatedStaticProps } from "~/components/utilities/withTranslateProps";
import passwordCheck from '~/utilities/checks/PasswordCheck'; import attemptLogin from "~/utilities/attemptLogin";
import passwordCheck from "~/utilities/checks/PasswordCheck";
import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode'; import checkEmailVerificationCode from "./api/auth/CheckEmailVerificationCode";
import completeAccountInformationSignup from './api/auth/CompleteAccountInformationSignup'; import completeAccountInformationSignup from "./api/auth/CompleteAccountInformationSignup";
import sendVerificationEmail from './api/auth/SendVerificationEmail'; import sendVerificationEmail from "./api/auth/SendVerificationEmail";
import getWorkspaces from './api/workspace/getWorkspaces'; import getWorkspaces from "./api/workspace/getWorkspaces";
// const ReactCodeInput = dynamic(import("react-code-input")); // const ReactCodeInput = dynamic(import("react-code-input"));
const nacl = require('tweetnacl'); const nacl = require("tweetnacl");
const jsrp = require('jsrp'); const jsrp = require("jsrp");
nacl.util = require('tweetnacl-util'); nacl.util = require("tweetnacl-util");
const client = new jsrp.client(); const client = new jsrp.client();
// The stye for the verification code input // The stye for the verification code input
const props = { const props = {
inputStyle: { inputStyle: {
fontFamily: 'monospace', fontFamily: "monospace",
margin: '4px', margin: "4px",
MozAppearance: 'textfield', MozAppearance: "textfield",
width: '55px', width: "55px",
borderRadius: '5px', borderRadius: "5px",
fontSize: '24px', fontSize: "24px",
height: '55px', height: "55px",
paddingLeft: '7', paddingLeft: "7",
backgroundColor: '#0d1117', backgroundColor: "#0d1117",
color: 'white', color: "white",
border: '1px solid gray', border: "1px solid gray",
textAlign: 'center', textAlign: "center",
}, },
} as const; } as const;
const propsPhone = { const propsPhone = {
inputStyle: { inputStyle: {
fontFamily: 'monospace', fontFamily: "monospace",
margin: '4px', margin: "4px",
MozAppearance: 'textfield', MozAppearance: "textfield",
width: '40px', width: "40px",
borderRadius: '5px', borderRadius: "5px",
fontSize: '24px', fontSize: "24px",
height: '40px', height: "40px",
paddingLeft: '7', paddingLeft: "7",
backgroundColor: '#0d1117', backgroundColor: "#0d1117",
color: 'white', color: "white",
border: '1px solid gray', border: "1px solid gray",
textAlign: 'center', textAlign: "center",
}, },
} as const; } as const;
export default function SignUp() { export default function SignUp() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState("");
const [password, setPassword] = useState(''); const [password, setPassword] = useState("");
const [firstName, setFirstName] = useState(''); const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState(''); const [lastName, setLastName] = useState("");
const [code, setCode] = useState(''); const [code, setCode] = useState("");
const [codeError, setCodeError] = useState(false); const [codeError, setCodeError] = useState(false);
const [firstNameError, setFirstNameError] = useState(false); const [firstNameError, setFirstNameError] = useState(false);
const [lastNameError, setLastNameError] = useState(false); const [lastNameError, setLastNameError] = useState(false);
@@ -76,23 +78,26 @@ export default function SignUp() {
const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] = const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] =
useState(false); useState(false);
const [emailError, setEmailError] = useState(false); const [emailError, setEmailError] = useState(false);
const [emailErrorMessage, setEmailErrorMessage] = useState(''); const [emailErrorMessage, setEmailErrorMessage] = useState("");
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const router = useRouter(); const router = useRouter();
const [errorLogin, setErrorLogin] = useState(false); const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isResendingVerificationEmail, setIsResendingVerificationEmail] = useState(false); const [isResendingVerificationEmail, setIsResendingVerificationEmail] =
useState(false);
const [backupKeyError, setBackupKeyError] = useState(false); const [backupKeyError, setBackupKeyError] = useState(false);
const [verificationToken, setVerificationToken] = useState(''); const [verificationToken, setVerificationToken] = useState("");
const [backupKeyIssued, setBackupKeyIssued] = useState(false); const [backupKeyIssued, setBackupKeyIssued] = useState(false);
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
const tryAuth = async () => { const tryAuth = async () => {
try { try {
const userWorkspaces = await getWorkspaces(); const userWorkspaces = await getWorkspaces();
router.push('/dashboard/' + userWorkspaces[0]._id); router.push("/dashboard/" + userWorkspaces[0]._id);
} catch (error) { } catch (error) {
console.log('Error - Not logged in yet'); console.log("Error - Not logged in yet");
} }
}; };
tryAuth(); tryAuth();
@@ -110,7 +115,7 @@ export default function SignUp() {
} else if (step == 2) { } else if (step == 2) {
// Checking if the code matches the email. // Checking if the code matches the email.
const response = await checkEmailVerificationCode({ email, code }); const response = await checkEmailVerificationCode({ email, code });
if (response.status === 200 || code == '111222') { if (response.status === 200 || code == "111222") {
setVerificationToken((await response.json()).token); setVerificationToken((await response.json()).token);
setStep(3); setStep(3);
} else { } else {
@@ -128,15 +133,15 @@ export default function SignUp() {
let emailCheckBool = false; let emailCheckBool = false;
if (!email) { if (!email) {
setEmailError(true); setEmailError(true);
setEmailErrorMessage('Please enter your email.'); setEmailErrorMessage("Please enter your email.");
emailCheckBool = true; emailCheckBool = true;
} else if ( } else if (
!email.includes('@') || !email.includes("@") ||
!email.includes('.') || !email.includes(".") ||
!/[a-z]/.test(email) !/[a-z]/.test(email)
) { ) {
setEmailError(true); setEmailError(true);
setEmailErrorMessage('Please enter a valid email.'); setEmailErrorMessage("Please enter a valid email.");
emailCheckBool = true; emailCheckBool = true;
} else { } else {
setEmailError(false); setEmailError(false);
@@ -188,11 +193,11 @@ export default function SignUp() {
.slice(0, 32) .slice(0, 32)
.padStart( .padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size), 32 + (password.slice(0, 32).length - new Blob([password]).size),
'0', "0"
), ),
}) as { ciphertext: string; iv: string; tag: string }; }) as { ciphertext: string; iv: string; tag: string };
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY); localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
client.init( client.init(
{ {
@@ -206,7 +211,7 @@ export default function SignUp() {
email, email,
firstName, firstName,
lastName, lastName,
organizationName: firstName + '\'s organization', organizationName: firstName + "'s organization",
publicKey: PUBLIC_KEY, publicKey: PUBLIC_KEY,
ciphertext, ciphertext,
iv, iv,
@@ -220,10 +225,10 @@ export default function SignUp() {
if (response.status === 200) { if (response.status === 200) {
// response = await response.json(); // response = await response.json();
localStorage.setItem('publicKey', PUBLIC_KEY); localStorage.setItem("publicKey", PUBLIC_KEY);
localStorage.setItem('encryptedPrivateKey', ciphertext); localStorage.setItem("encryptedPrivateKey", ciphertext);
localStorage.setItem('iv', iv); localStorage.setItem("iv", iv);
localStorage.setItem('tag', tag); localStorage.setItem("tag", tag);
try { try {
await attemptLogin( await attemptLogin(
@@ -232,16 +237,16 @@ export default function SignUp() {
setErrorLogin, setErrorLogin,
router, router,
true, true,
false, false
); );
incrementStep(); incrementStep();
} catch (error) { } catch (error) {
setIsLoading(false); setIsLoading(false);
} }
} }
}, }
); );
}, }
); );
} else { } else {
setIsLoading(false); setIsLoading(false);
@@ -284,8 +289,7 @@ export default function SignUp() {
</div> */} </div> */}
<div className="flex flex-col items-center justify-center w-5/6 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-4 md:mt-4 text-sm text-center md:text-left"> <div className="flex flex-col items-center justify-center w-5/6 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-4 md:mt-4 text-sm text-center md:text-left">
<p className="text-gray-400 mt-2 md:mx-0.5"> <p className="text-gray-400 mt-2 md:mx-0.5">
By creating an account, you agree to our Terms and have read and {t("signup:step1-privacy")}
acknowledged the Privacy Policy.
</p> </p>
<div className="text-l mt-6 m-2 md:m-8 px-8 py-1 text-lg"> <div className="text-l mt-6 m-2 md:m-8 px-8 py-1 text-lg">
<Button text="Get Started" type="submit" onButtonPressed={emailCheck} size="lg" /> <Button text="Get Started" type="submit" onButtonPressed={emailCheck} size="lg" />
@@ -296,7 +300,7 @@ export default function SignUp() {
<Link href="/login"> <Link href="/login">
<button type="button" className="w-max pb-3 hover:opacity-90 duration-200"> <button type="button" className="w-max pb-3 hover:opacity-90 duration-200">
<u className="font-normal text-sm text-primary-500"> <u className="font-normal text-sm text-primary-500">
Have an account? Log in {t("signup:already-have-account")}
</u> </u>
</button> </button>
</Link> </Link>
@@ -309,10 +313,10 @@ export default function SignUp() {
const step2 = ( const step2 = (
<div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16"> <div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
<p className="text-l flex justify-center text-gray-400"> <p className="text-l flex justify-center text-gray-400">
{'We\'ve'} sent a verification email to{' '} {"We've"} sent a verification email to{" "}
</p> </p>
<p className="text-l flex justify-center font-semibold my-2 text-gray-400"> <p className="text-l flex justify-center font-semibold my-2 text-gray-400">
{email}{' '} {email}{" "}
</p> </p>
<div className="hidden md:block"> <div className="hidden md:block">
<ReactCodeInput <ReactCodeInput
@@ -336,11 +340,13 @@ export default function SignUp() {
className="mt-2 mb-6" className="mt-2 mb-6"
/> />
</div> </div>
{codeError && ( {codeError && <Error text={t("signup:step2-code-error")} />}
<Error text="Oops. Your code is wrong. Please try again." />
)}
<div className="flex max-w-min flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-4 mb-2"> <div className="flex max-w-min flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-4 mb-2">
<Button text="Verify" onButtonPressed={incrementStep} size="lg" /> <Button
text={t("signup:verify") ?? ""}
onButtonPressed={incrementStep}
size="lg"
/>
</div> </div>
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2"> <div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
<div className="flex flex-row items-baseline gap-1 text-sm"> <div className="flex flex-row items-baseline gap-1 text-sm">
@@ -349,12 +355,12 @@ export default function SignUp() {
</span> </span>
<u className={`font-normal ${isResendingVerificationEmail ? 'text-gray-400' : 'text-primary-500 hover:opacity-90 duration-200'}`}> <u className={`font-normal ${isResendingVerificationEmail ? 'text-gray-400' : 'text-primary-500 hover:opacity-90 duration-200'}`}>
<button disabled={isLoading} onClick={resendVerificationEmail}> <button disabled={isLoading} onClick={resendVerificationEmail}>
{isResendingVerificationEmail ? 'Resending...' : 'Resend'} {isResendingVerificationEmail ? "Resending..." : "Resend"}
</button> </button>
</u> </u>
</div> </div>
<p className="text-sm text-gray-500 pb-2"> <p className="text-sm text-gray-500 pb-2">
Make sure to check your spam inbox. {t("signup:step2-spam-alert")}
</p> </p>
</div> </div>
</div> </div>
@@ -364,35 +370,43 @@ export default function SignUp() {
const step3 = ( const step3 = (
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-36 md:mb-16"> <div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-36 md:mb-16">
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> <p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Almost there! {t("signup:step3-message")}
</p> </p>
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24"> <div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
<InputField <InputField
label="First Name" label={t("common:first-name")}
onChangeHandler={setFirstName} onChangeHandler={setFirstName}
type="name" type="name"
value={firstName} value={firstName}
isRequired isRequired
errorText="Please input your first name." errorText={
t("common:validate-required", {
name: t("common:first-name"),
}) as string
}
error={firstNameError} error={firstNameError}
autoComplete="given-name" autoComplete="given-name"
/> />
</div> </div>
<div className="mt-2 flex items-center justify-center w-full md:p-2 rounded-lg max-h-24"> <div className="mt-2 flex items-center justify-center w-full md:p-2 rounded-lg max-h-24">
<InputField <InputField
label="Last Name" label={t("common:last-name")}
onChangeHandler={setLastName} onChangeHandler={setLastName}
type="name" type="name"
value={lastName} value={lastName}
isRequired isRequired
errorText="Please input your last name." errorText={
t("common:validate-required", {
name: t("common:last-name"),
}) as string
}
error={lastNameError} error={lastNameError}
autoComplete="family-name" autoComplete="family-name"
/> />
</div> </div>
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60"> <div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
<InputField <InputField
label="Password" label={t("section-password:password")}
onChangeHandler={(password: string) => { onChangeHandler={(password: string) => {
setPassword(password); setPassword(password);
passwordCheck({ passwordCheck({
@@ -417,7 +431,7 @@ export default function SignUp() {
passwordErrorNumber ? ( passwordErrorNumber ? (
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md"> <div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
<div className={`text-gray-400 text-sm mb-1`}> <div className={`text-gray-400 text-sm mb-1`}>
Password should contain at least: {t("section-password:validate-base")}
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className="flex flex-row justify-start items-center ml-1">
{passwordErrorLength ? ( {passwordErrorLength ? (
@@ -433,10 +447,10 @@ export default function SignUp() {
)} )}
<div <div
className={`${ className={`${
passwordErrorLength ? 'text-gray-400' : 'text-gray-600' passwordErrorLength ? "text-gray-400" : "text-gray-600"
} text-sm`} } text-sm`}
> >
14 characters {t("section-password:validate-length")}
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className="flex flex-row justify-start items-center ml-1">
@@ -453,10 +467,10 @@ export default function SignUp() {
)} )}
<div <div
className={`${ className={`${
passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600' passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"
} text-sm`} } text-sm`}
> >
1 lowercase character {t("section-password:validate-case")}
</div> </div>
</div> </div>
<div className="flex flex-row justify-start items-center ml-1"> <div className="flex flex-row justify-start items-center ml-1">
@@ -473,10 +487,10 @@ export default function SignUp() {
)} )}
<div <div
className={`${ className={`${
passwordErrorNumber ? 'text-gray-400' : 'text-gray-600' passwordErrorNumber ? "text-gray-400" : "text-gray-600"
} text-sm`} } text-sm`}
> >
1 number {t("section-password:validate-number")}
</div> </div>
</div> </div>
</div> </div>
@@ -486,7 +500,7 @@ export default function SignUp() {
</div> </div>
<div className="flex flex-col items-center justify-center md:p-2 max-h-48 max-w-max mx-auto text-lg px-2 py-3"> <div className="flex flex-col items-center justify-center md:p-2 max-h-48 max-w-max mx-auto text-lg px-2 py-3">
<Button <Button
text="Sign Up" text={t("signup:signup") ?? ""}
loading={isLoading} loading={isLoading}
onButtonPressed={signupErrorCheck} onButtonPressed={signupErrorCheck}
size="lg" size="lg"
@@ -499,21 +513,15 @@ export default function SignUp() {
const step4 = ( const step4 = (
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl"> <div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> <p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Save your Emergency Kit {t("signup:step4-message")}
</p> </p>
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2"> <div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
<div> <div>{t("signup:step4-description1")}</div>
If you get locked out of your account, your Emergency Kit is the only <div className="mt-3">{t("signup:step4-description2")}</div>
way to sign in.
</div>
<div className="mt-3">
We recommend you download it and keep it somewhere safe.
</div>
</div> </div>
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4"> <div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" /> <FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
It contains your Secret Key which we cannot access or recover for you if {t("signup:step4-description3")}
you lose it.
</div> </div>
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg"> <div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
<Button <Button
@@ -522,11 +530,13 @@ export default function SignUp() {
await issueBackupKey({ await issueBackupKey({
email, email,
password, password,
personalName: firstName + ' ' + lastName, personalName: firstName + " " + lastName,
setBackupKeyError, setBackupKeyError,
setBackupKeyIssued setBackupKeyIssued,
}); });
router.push('/dashboard/'); const userWorkspaces = await getWorkspaces();
const userWorkspace = userWorkspaces[0]._id;
router.push("/home/" + userWorkspace);
}} }}
size="lg" size="lg"
/> />
@@ -549,16 +559,13 @@ export default function SignUp() {
return ( return (
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center"> <div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
<Head> <Head>
<title>Infiscal | Sign Up</title> <title>{t("common:head-title", { title: t("signup:title") })}</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
<meta <meta property="og:title" content={t("signup:og-title") as string} />
property="og:title"
content="Replace .env files with 1 line of code. Sign Up for Infisical in 3 minutes."
/>
<meta <meta
name="og:description" name="og:description"
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage API-keys and environemntal variables. Works with Node.js, Next.js, Gatsby, Nest.js..." content={t("signup:og-description") as string}
/> />
</Head> </Head>
<div className="flex flex-col justify-center items-center"> <div className="flex flex-col justify-center items-center">
@@ -579,3 +586,9 @@ export default function SignUp() {
</div> </div>
); );
} }
export const getStaticProps = getTranslatedStaticProps([
"auth",
"signup",
"section-password",
]);
+34 -17
View File
@@ -1,15 +1,17 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import Head from 'next/head'; import Head from "next/head";
import Image from 'next/image'; import Image from "next/image";
import { useRouter } from 'next/router'; import { useRouter } from "next/router";
import { faMagnifyingGlass, faPlus } from '@fortawesome/free-solid-svg-icons'; import { useTranslation } from "next-i18next";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faMagnifyingGlass, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from '~/components/basic/buttons/Button'; import Button from "~/components/basic/buttons/Button";
import AddProjectMemberDialog from '~/components/basic/dialog/AddProjectMemberDialog'; import AddProjectMemberDialog from "~/components/basic/dialog/AddProjectMemberDialog";
import UserTable from '~/components/basic/table/UserTable'; import UserTable from "~/components/basic/table/UserTable";
import NavHeader from '~/components/navigation/NavHeader'; import NavHeader from "~/components/navigation/NavHeader";
import guidGenerator from '~/utilities/randomId'; import guidGenerator from "~/utilities/randomId";
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
import getOrganizationUsers from '../api/organization/GetOrgUsers'; import getOrganizationUsers from '../api/organization/GetOrgUsers';
import getUser from '../api/user/getUser'; import getUser from '../api/user/getUser';
@@ -35,6 +37,8 @@ export default function Users() {
const [personalEmail, setPersonalEmail] = useState(''); const [personalEmail, setPersonalEmail] = useState('');
const [searchUsers, setSearchUsers] = useState(''); const [searchUsers, setSearchUsers] = useState('');
const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
let workspaceId; let workspaceId;
@@ -124,14 +128,21 @@ export default function Users() {
return userList ? ( return userList ? (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-start"> <div className="bg-bunker-800 md:h-screen flex flex-col justify-start">
<Head> <Head>
<title>Users</title> <title>
{t("common:head-title", { title: t("settings-members:title") })}
</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<NavHeader pageName="Project Members" isProjectRelated={true} /> <NavHeader
pageName={t("settings-members:title")}
isProjectRelated={true}
/>
<div className="flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl"> <div className="flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl">
<p className="font-semibold mr-4 text-white">Project Members</p> <p className="font-semibold mr-4 text-white">
{t("settings-members:title")}
</p>
<p className="mr-4 text-base text-gray-400"> <p className="mr-4 text-base text-gray-400">
This pages shows the members of the selected project. {t("settings-members:description")}
</p> </p>
</div> </div>
<AddProjectMemberDialog <AddProjectMemberDialog
@@ -159,12 +170,12 @@ export default function Users() {
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none" className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
value={searchUsers} value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)} onChange={(e) => setSearchUsers(e.target.value)}
placeholder={'Search members...'} placeholder={t("section-members:search-members")}
/> />
</div> </div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start mr-4"> <div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start mr-4">
<Button <Button
text="Add Member" text={t("section-members:add-member")}
onButtonPressed={openAddModal} onButtonPressed={openAddModal}
color="mineshaft" color="mineshaft"
size="md" size="md"
@@ -201,3 +212,9 @@ export default function Users() {
} }
Users.requireAuth = true; Users.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps([
"settings",
"settings-members",
"section-members",
]);
+11 -8
View File
@@ -1,15 +1,16 @@
import { useState } from 'react'; import { useState } from "react";
import Head from 'next/head'; import Head from "next/head";
import Image from 'next/image'; import Image from "next/image";
import Link from 'next/link'; import Link from "next/link";
import Button from '~/components/basic/buttons/Button'; import Button from "~/components/basic/buttons/Button";
import InputField from '~/components/basic/InputField'; import InputField from "~/components/basic/InputField";
import { getTranslatedStaticProps } from "~/components/utilities/withTranslateProps";
import SendEmailOnPasswordReset from './api/auth/SendEmailOnPasswordReset'; import SendEmailOnPasswordReset from "./api/auth/SendEmailOnPasswordReset";
export default function VerifyEmail() { export default function VerifyEmail() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState("");
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
/** /**
@@ -92,3 +93,5 @@ export default function VerifyEmail() {
</div> </div>
); );
} }
export const getServerSideProps = getTranslatedStaticProps([]);
+28
View File
@@ -0,0 +1,28 @@
{
"title": "Usage & Billing",
"description": "View and manage your organization's subscription here",
"subscription": "Subscription",
"starter": {
"name": "Starter",
"price-explanation": "up to 5 team members",
"text": "Manage any project with 5 members for free!",
"subtext": "$5 per member/month afterwards."
},
"professional": {
"name": "Professional",
"price-explanation": "/member/month",
"subtext": "Includes unlimited projects & members.",
"text": "Keep up with key management as you grow."
},
"enterprise": {
"name": "Enterprise",
"text": "Keep up with key management as you grow."
},
"current-usage": "Current Usage",
"free": "Free",
"downgrade": "Downgrade",
"upgrade": "Upgrade",
"learn-more": "Learn More",
"custom-pricing": "Custom Pricing",
"schedule-demo": "Schedule a Demo"
}
+26
View File
@@ -0,0 +1,26 @@
{
"head-title": "{{title}} | Infiscal",
"error_project-already-exists": "A project with this name already exists.",
"no-mobile": " To use Infisical, please log in through a device with larger dimensions. ",
"email": "Email",
"password": "Password",
"first-name": "First Name",
"last-name": "Last Name",
"logout": "Log Out",
"validate-required": "Please input your {{name}}",
"maintenance-alert": "We are experiencing minor technical difficulties. We are working on solving it right now. Please come back in a few minutes.",
"click-to-copy": "Click to copy",
"project-id": "Project ID",
"save-changes": "Save Changes",
"saved": "Saved",
"drop-zone": "Drag and drop your .env file here.",
"drop-zone-keys": "Drag and drop your .env file here to add more keys.",
"role": "Role",
"role_admin": "admin",
"display-name": "Display Name",
"environment": "Environment",
"expired-in": "Expires in",
"language": "Language",
"search": "Search..."
}
+19
View File
@@ -0,0 +1,19 @@
{
"title": "Secrets",
"og-title": "Manage your .env files in seconds",
"og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files.",
"search-keys": "Search keys...",
"add-key": "Add Key",
"personal": "Personal",
"personal-description": "Personal keys are only visible to you",
"shared": "Shared",
"shared-description": "Shared keys are visible to your whole team",
"make-shared": "Make Shared",
"make-personal": "Make Personal",
"check-docs": {
"button": "Check Docs",
"title": "Good job!",
"line1": "Congrats on adding more secrets.",
"line2": "Here is how to connect them to your codebase."
}
}
@@ -0,0 +1,9 @@
{
"title": "Project Integrations",
"description": "Manage your integrations of Infisical with third-party services.",
"no-integrations1": "You don't have any integrations set up yet. When you do, they will appear here.",
"no-integrations2": "To start, click on any of the options below. It takes 5 clicks to set up.",
"available": "Platform & Cloud Integrations",
"available-text1": "Click on the itegration you want to connect. This will let your environment variables flow automatically into selected third-party services.",
"available-text2": " Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. The core Infisical service will always stay end-to-end encrypted. With any questions, reach out [email protected]."
}
+8
View File
@@ -0,0 +1,8 @@
{
"title": "Login",
"og-title": "Log In to Infisical",
"og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files.",
"login": "Log In",
"need-account": "Need an Infisical account?",
"create-account": "Create an account"
}
+22
View File
@@ -0,0 +1,22 @@
{
"support": {
"slack": "[NEW] Join Slack Forum",
"docs": "Read Docs",
"issue": "Open a Github Issue",
"email": "Send us an Email"
},
"user": {
"signed-in-as": "SIGNED IN AS",
"current-organization": "CURRENT ORGANIZATION",
"usage-billing": "Usage & Billing",
"invite": "Invite Members",
"other-organizations": "OTHER ORGANIZATION"
},
"menu": {
"project": "PROJECT",
"secrets": "Secrets",
"members": "Members",
"integrations": "Integrations",
"project-settings": "Project Settings"
}
}
@@ -0,0 +1,11 @@
{
"incident-contacts": "Incident Contacts",
"incident-contacts-description": "These contacts will be notified in the unlikely event of a severe incident.",
"no-incident-contacts": "No incident contacts found.",
"add-contact": "Add Contact",
"add-dialog": {
"title": "Add an Incident Contact",
"description": "This contact will be notified in the unlikely event of a severe incident.",
"add-incident": "Add Incident Contact"
}
}
@@ -0,0 +1,14 @@
{
"add-member": "Add Member",
"org-members": "Organization Members",
"org-members-description": "Manage members of your organization. These users could afterwards be formed into projects.",
"search-members": "Search members...",
"add-dialog": {
"add-member-to-project": "Add a member to your project",
"already-all-invited": "All the users in your organization are already invited.",
"add-user-org-first": "Add more users to the organization first.",
"user-will-email": "The user will receive an email with the instructions.",
"looking-add": "<0>If you are looking to add users to your org,</0><1>click here</1>",
"add-user-to-org": "Add Users to Organization"
}
}
@@ -0,0 +1,11 @@
{
"password": "Password",
"change": "Change password",
"current": "Current password",
"current-wrong": "The current password may be wrong",
"new": "New password",
"validate-base": "Password should contain at least:",
"validate-length": "14 characters",
"validate-case": "1 lowercase character",
"validate-number": "1 number"
}
@@ -0,0 +1,13 @@
{
"service-tokens": "Service Tokens",
"service-tokens-description": "Every service token is specific to you, a certain project and a certain environment within this project.",
"add-new": "Add New Token",
"add-dialog": {
"title": "Add a service token for {{target}}",
"description": "Specify the name, environment, and expiry period. When a token is generated, you will only be able to see it once before it disappears. Make sure to save it somewhere.",
"name": "Service Token Name",
"add": "Add Service Token",
"copy-service-token": "Copy your service token",
"copy-service-token-description": "Once you close this popup, you will never see your service token again"
}
}
@@ -0,0 +1,4 @@
{
"title": "Project Members",
"description": "This pages shows the members of the selected project."
}
@@ -0,0 +1,4 @@
{
"title": "Organization Settings",
"description": "Manage members of your organization. These users could afterwards be formed into projects."
}
@@ -0,0 +1,11 @@
{
"title": "Personal Settings",
"description": "View and manage your personal information here.",
"emergency": {
"name": "Emergency Kit",
"text1": "Your Emergency Kit contains the information you’ll need to sign in to your Infisical account.",
"text2": "Only the latest issued Emergency Kit remains valid. To get a new Emergency Kit, verify your password.",
"download": "Download Emergency Kit"
},
"change-language": "Change Language"
}
@@ -0,0 +1,13 @@
{
"title": "Project Settings",
"description": "These settings only apply to the currently selected Project.",
"danger-zone": "Danger Zone",
"delete-project": "Delete Project",
"project-to-delete": "Project to be Deleted",
"danger-zone-note": "As soon as you delete this project, you will not be able to undo it. This will immediately remove all the keys. If you still want to do that, please enter the name of the project below.",
"delete-project-note": "Note: You can only delete a project in case you have more than one",
"project-id-description": "To integrate Infisical into your code base and get automatic injection of environmental variables, you should use the following Project ID.",
"project-id-description2": "For more guidance, including code snipets for various languages and frameworks, see ",
"auto-generated": "This is your project's auto-generated unique identifier. It can't be changed.",
"docs": "Infisical Docs"
}
+21
View File
@@ -0,0 +1,21 @@
{
"title": "Sign Up",
"og-title": "Replace .env files with 1 line of code. Sign Up for Infisical in 3 minutes.",
"og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage API-keys and environemntal variables. Works with Node.js, Next.js, Gatsby, Nest.js...",
"signup": "Sign Up",
"already-have-account": "Have an account? Log in",
"forgot-password": "Forgot your password?",
"verify": "Verify",
"step1-start": "Let's get started",
"step1-privacy": "By creating an account, you agree to our Terms and have read and acknowledged the Privacy Policy.",
"step1-submit": "Get Started",
"step2-message": "<wrapper>We've sent a verification email to</wrapper><email>{{email}}</email>",
"step2-code-error": "Oops. Your code is wrong. Please try again.",
"step2-spam-alert": "Make sure to check your spam inbox.",
"step3-message": "Almost there!",
"step4-message": "Save your Emergency Kit",
"step4-description1": "If you get locked out of your account, your Emergency Kit is the only way to sign in.",
"step4-description2": "We recommend you download it and keep it somewhere safe.",
"step4-description3": "It contains your Secret Key which we cannot access or recover for you if you lose it.",
"step4-download": "Download PDF"
}
+28
View File
@@ -0,0 +1,28 @@
{
"title": "요금제 & 결제",
"description": "이곳에서 조직의 구독을 확인하고 관리할 수 있어요.",
"subscription": "구독",
"starter": {
"name": "스타터",
"price-explanation": "5명의 팀 맴버까지",
"text": "어떤 프로젝트든 다섯명의 맴버까지 무료로 이용할 수 있어요!",
"subtext": "$5명/달 로 바뀔 예정입니다."
},
"professional": {
"name": "전문가",
"price-explanation": "/맴버/달",
"subtext": "무제한의 프로젝트와 맴버",
"text": "조직의 성장에 따라 유연하게 시크릿을 관리하세요."
},
"enterprise": {
"name": "엔터프라이즈",
"text": "조직의 성장에 따라 유연하게 시크릿을 관리하세요."
},
"current-usage": "현재 요금제",
"free": "무료",
"downgrade": "다운그레이드",
"upgrade": "업그레이드",
"learn-more": "자세히 알아보기",
"custom-pricing": "커스텀 가격",
"schedule-demo": "데모 예약하기"
}
+26
View File
@@ -0,0 +1,26 @@
{
"head-title": "{{title}} | Infiscal",
"error_project-already-exists": "동일한 이름을 가진 프로젝트가 이미 존재해요.",
"no-mobile": " Infisical을 사용하려면, 큰 화면을 가진 디바이스로 로그인하여 주세요.",
"email": "메일",
"password": "비밀번호",
"first-name": "이름",
"last-name": "성",
"logout": "로그아웃",
"validate-required": "{{name}} 을/를 입력하세요.",
"maintenance-alert": "We are experiencing minor technical difficulties. We are working on solving it right now. Please come back in a few minutes.",
"click-to-copy": "클릭하여 복사하기",
"project-id": "프로젝트 ID",
"save-changes": "변경사항 저장하기",
"saved": "저장됨",
"drop-zone": ".env 파일을 이곳에 드래그 & 드롭 하세요.",
"drop-zone-keys": "이곳에 .env 파일을 드래그 & 드롭 하여 더 많은 키들을 추가하세요.",
"role": "Role",
"role_admin": "admin",
"display-name": "표시 이름",
"environment": "환경",
"expired-in": "만료 기한:",
"language": "언어",
"search": "검색하기..."
}
+19
View File
@@ -0,0 +1,19 @@
{
"title": "시크릿",
"og-title": "빠르게 .env 파일을 관리하세요",
"og-description": "Infisical은 팀원과 .env파일을 공유할 수 있는 심플한 end-to-end 암호화 플렛폼입니다.",
"search-keys": "키 검색하기...",
"add-key": "키 추가하기",
"personal": "개인",
"personal-description": "개인 키는 오직 본인만 볼 수 있어요",
"shared": "공유",
"shared-description": "공유 키는 팀 전체가 볼 수 있어요",
"make-shared": "공유로 만들기",
"make-personal": "개인으로 만들기",
"check-docs": {
"button": "문서 확인하기",
"title": "좋았어요!",
"line1": "더 많은 시크릿들을 추가한 것을 축하합니다.",
"line2": "코드와 연결하는 사용하는 방법을 한번 알아보지 않을래요?"
}
}
@@ -0,0 +1,9 @@
{
"available": "사용 가능한 연동",
"available-text1": "연결하고 싶은 서비스를 클릭하세요. 환경 변수가 선택한 외부 서비스와 자동으로 연동됩니다.",
"available-text2": "참고: Heroku와의 연동에서는 보안상의 이유로 End-To-End 암호화를 유지하는 것이 불가능합니다. 이론적으로는 Infisical이 시크릿을 읽는것이 가능하지만, 절대 그러지 않는다고 확신할 수 있습니다. 핵심 Infisical 서비스는 항상 End-To-End 암호화 상태를 유지합니다. 질문이 있으면 [email protected]으로 문의하세요.",
"description": "외부 서비스와 함께 Infisical을 연동하고 관리하세요",
"no-integrations1": "연동된 서비스가 없어요. 연동하게 된다면 이곳에서 표시돼요.",
"no-integrations2": "시작하려면, 아래의 옵션을 클릭하세요. 대체로 5분 이내로 가능해요.",
"title": "프로젝트 연동"
}
+8
View File
@@ -0,0 +1,8 @@
{
"title": "로그인",
"og-title": "Infisical에 로그인하기",
"og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files.",
"login": "로그인",
"need-account": "Infisical계정이 필요하신가요?",
"create-account": "회원가입 하기"
}
+22
View File
@@ -0,0 +1,22 @@
{
"support": {
"slack": "[NEW] 슬랙 포럼에 가입하기",
"docs": "문서 보기",
"issue": "깃허브 이슈 열기",
"email": "Infisical팀에게 메일 보내기"
},
"user": {
"signed-in-as": "다음으로 로그인됨",
"current-organization": "현재 조직",
"usage-billing": "요금제 & 결제",
"invite": "맴버 초대하기",
"other-organizations": "다른 조직"
},
"menu": {
"project": "프로젝트",
"secrets": "시크릿",
"members": "맴버",
"integrations": "연동",
"project-settings": "프로젝트 설정"
}
}
@@ -0,0 +1,11 @@
{
"add-contact": "연락처 추가하기",
"add-dialog": {
"add-incident": "비상 연락처 추가하기",
"description": "중대한 일이 생길 경우, 이 연락처로 알림이 전송됩니다.",
"title": "비상 연락처 추가하기"
},
"incident-contacts": "비상 연락망",
"incident-contacts-description": "중대한 일이 생길 경우, 아래의 연락처로 알림이 전송됩니다.",
"no-incident-contacts": "비상 연락망이 존재하지 않아요."
}
@@ -0,0 +1,14 @@
{
"add-member": "맴버 추가하기",
"org-members": "조직 맴버",
"search-members": "맴버를 검색하기...",
"add-dialog": {
"add-member-to-project": "프로젝트에 맴버 추가하기",
"already-all-invited": "조직의 모든 유저가 이미 초대되었습니다.",
"add-user-org-first": "유저를 먼저 조직에 추가해 주세요.",
"user-will-email": "유저는 안내가 포함된 메일을 받게 됩니다.",
"looking-add": "<0>조직에 유저를 추가하고 싶다면,</0><1>여기를 클릭하세요</1>",
"add-user-to-org": "조직에 유저 추가하기"
},
"org-members-description": "조직의 맴버들을 관리하세요."
}
@@ -0,0 +1,11 @@
{
"password": "비밀번호",
"change": "비밀번호 변경",
"current": "현재 비밀번호",
"new": "새 비밀번호",
"current-wrong": "현재 비밀번호가 잘못되었어요",
"validate-base": "비밀번호는 다음 조건을 만족해야 합니다:",
"validate-length": "14 글자 이상",
"validate-case": "1개 이상의 소문자",
"validate-number": "1개 이상의 숫자"
}
@@ -0,0 +1,13 @@
{
"add-dialog": {
"add": "서비스 토큰 추가하기",
"copy-service-token": "서비스 토큰 복사하기",
"copy-service-token-description": "팝업을 닫을 시 다시는 토큰을 확인할 수 없어요.",
"description": "이름, 환경, 만료일을 지정하세요. 토큰이 생성되면 최초 한번만 볼 수 있어요. 안전한 곳에 저장하세요.",
"name": "서비스 토큰 이름",
"title": "{{target}}의 토큰 추가하기"
},
"add-new": "새 토큰 추가하기",
"service-tokens": "서비스 토큰",
"service-tokens-description": "모든 서비스 토큰은 프로젝트 및 환경에 따라 다릅니다."
}
@@ -0,0 +1,4 @@
{
"title": "맴버 설정",
"description": "선택한 프로젝트의 맴버를 확인하세요."
}
@@ -0,0 +1,4 @@
{
"title": "조직 설정",
"description": "조직의 맴버를 관리하세요."
}
@@ -0,0 +1,11 @@
{
"title": "개인 설정",
"description": "계정 및 기본적인 설정을 확인하고 변경하세요.",
"emergency": {
"name": "긴급복구 키트",
"text1": "긴급복구 키트는 Infisical계정에 로그인 할 수 있는 정보를 가지고 있어요.",
"text2": "오직 마지막으로 발급한 긴급복구 키트만 사용 가능해요. 새로운 긴급복구 키트를 받으려면, 비밀번호를 입력하세요.",
"download": "긴급복구 키트 다운로드"
},
"change-language": "언어 변경하기"
}
@@ -0,0 +1,13 @@
{
"title": "프로젝트 설정",
"description": "현재 선택한 프로젝트에 대해서 설정해요.",
"auto-generated": "자동으로 생성된 고유한 키 입니다. 변경할 수 없어요.",
"danger-zone": "위험존",
"danger-zone-note": "프로젝트를 삭제한다면 모든 키가 즉시 삭제되며 다시는 되돌릴 수 없어요. 삭제를 원한다면 프로젝트 이름을 아래에 적어주세요.",
"delete-project": "프로젝트 삭제",
"delete-project-note": "노트: 최소 한개 이상의 프로젝트는 조직에 존재해야 해요.",
"docs": "Infisical 문서",
"project-id-description": "다른 코드에서 Infisical과 환경변수를 연동하기 위해서는 프로젝트 ID가 필요해요.",
"project-id-description2": "다양한 언어 및 프레임워크에 대한 가이드를 확인하고 싶다면, 다음을 확인하세요. ",
"project-to-delete": "삭제할 프로젝트"
}
+21
View File
@@ -0,0 +1,21 @@
{
"title": "회원가입",
"og-title": "한줄의 코드르 .env파일을 교체하세요. 3분이면 가입할 수 있어요.",
"og-description": "Infisical은 팀원과 .env파일을 공유하고 연동할 수 있는 심플한 end-to-end 암호화 플렛폼입니다. Node.js, Next.js, Gatsby, Nest.js 와 같은 다양한 플렛폼에서 작동해요.",
"signup": "회원가입",
"already-have-account": "이미 계정이 있나요? 로그인하기",
"forgot-password": "비밀번호를 잊으셨나요?",
"verify": "인증",
"step1-start": "시작하기",
"step1-privacy": "회원가입시 약관과 개인 정보 보호 정책을 읽고 동의한 것으로 간주합니다.",
"step1-submit": "시작하기",
"step2-message": "<email>{{email}}</email><wrapper>로 인증 메일을 전송하였습니다</wrapper><email>{{email}}</email>",
"step2-code-error": "코드가 잘못된 것 같아요. 다시 시도해주세요.",
"step2-spam-alert": "스팸함에 메일이 있지는 않은지 확인하세요",
"step3-message": "거의다 끝났어요!",
"step4-message": "긴급복구 키트 저장하기",
"step4-description1": "계정이 잠겼을 경우 비상 키트를 사용하여 로그인할 수 있어요.",
"step4-description2": "다운로드 후 안전한 곳에 보관하는 것을 추천합니다.",
"step4-description3": "분실시 접근하거나 복구할 수 없는 시크릿 키가 포함되어 있어요.",
"step4-download": "PDF 다운로드"
}