Merge branch 'main' into fix/update-pt-br-copies

This commit is contained in:
mv-turtle
2023-03-04 20:54:32 -08:00
committed by GitHub
110 changed files with 5788 additions and 6660 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ module.exports = {
debug: false,
i18n: {
defaultLocale: "en",
locales: ["en", "ko", "fr", "pt-BR"],
locales: ["en", "ko", "fr", "pt-BR", "pt-PT", "es"],
},
fallbackLng: {
default: ["en"],
+60 -45
View File
@@ -13,7 +13,7 @@
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/react-fontawesome": "^0.1.19",
"@headlessui/react": "^1.6.6",
"@headlessui/react": "^1.7.7",
"@hookform/resolvers": "^2.9.10",
"@octokit/rest": "^19.0.7",
"@radix-ui/react-accordion": "^1.1.0",
@@ -30,14 +30,14 @@
"@radix-ui/react-tabs": "^1.0.2",
"@radix-ui/react-toast": "^1.1.2",
"@reduxjs/toolkit": "^1.8.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/react-stripe-js": "^1.16.3",
"@stripe/stripe-js": "^1.46.0",
"@tanstack/react-query": "^4.23.0",
"@types/argon2-browser": "^1.18.1",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios-auth-refresh": "^3.3.3",
"axios-auth-refresh": "^3.3.6",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
"cookies": "^0.8.0",
@@ -45,13 +45,13 @@
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"http-proxy": "^1.18.1",
"i18next": "^22.4.6",
"i18next": "^22.4.9",
"jspdf": "^2.5.1",
"jsrp": "^0.2.4",
"markdown-it": "^13.0.1",
"next": "^12.3.4",
"next-i18next": "^13.0.2",
"posthog-js": "^1.34.0",
"posthog-js": "^1.39.4",
"query-string": "^7.1.3",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
@@ -2544,9 +2544,12 @@
}
},
"node_modules/@headlessui/react": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.6.6.tgz",
"integrity": "sha512-MFJtmj9Xh/hhBMhLccGbBoSk+sk61BlP6sJe4uQcVMtXZhCgGqd2GyIQzzmsdPdTEWGSF434CBi8mnhR6um46Q==",
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.7.tgz",
"integrity": "sha512-BqDOd/tB9u2tA0T3Z0fn18ktw+KbVwMnkxxsGPIH2hzssrQhKB5n/6StZOyvLYP/FsYtvuXfi9I0YowKPv2c1w==",
"dependencies": {
"client-only": "^0.0.1"
},
"engines": {
"node": ">=10"
},
@@ -3935,9 +3938,9 @@
"dev": true
},
"node_modules/@sentry/types": {
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.15.0.tgz",
"integrity": "sha512-MN9haDRh9ZOsTotoDTHu2BT3sT8Vs1F0alhizUpDyjN2YgBCqR6JV+AbAE1XNHwS2+5zbppch1PwJUVeE58URQ==",
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.22.0.tgz",
"integrity": "sha512-LhCL+wb1Jch+OesB2CIt6xpfO1Ab6CRvoNYRRzVumWPLns1T3ZJkarYfhbLaOEIb38EIbPgREdxn2AJT560U4Q==",
"engines": {
"node": ">=8"
}
@@ -6281,14 +6284,14 @@
}
},
"node_modules/@stripe/react-stripe-js": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.10.0.tgz",
"integrity": "sha512-vuIjJUZJ3nyiaGa5z5iyMCzZfGGsgzOOjWjqknbbhkNsewyyginfeky9EZLSz9+iSAsgC9K6MeNOTLKVGcMycQ==",
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.16.3.tgz",
"integrity": "sha512-gS6UDGEuM92K50pFB3o//EqqPxmaqpC8IrsBda4P4LxeULoO0pBFSHXJ5Ab6uA7G2lyO2bluvSLereh0OH9GrQ==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"@stripe/stripe-js": "^1.34.0",
"@stripe/stripe-js": "^1.44.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
@@ -8157,9 +8160,9 @@
}
},
"node_modules/axios-auth-refresh": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.3.tgz",
"integrity": "sha512-2IbDhJ/h6ddNBBnnzn1VFK/qx17pE9aVqiafB8rx5LVHsJ1HtFpUGkbXY7PzTG+8P9HJWcyA3fNZl9BikSuilg==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.6.tgz",
"integrity": "sha512-2CeBUce/SxIfFxow5/n8vApJ97yYF6qoV4gh1UrswT7aEOnlOdBLxxyhOI4IaxGs6BY0l8YujU2jlc4aCmK17Q==",
"peerDependencies": {
"axios": ">= 0.18 < 0.19.0 || >= 0.19.1"
}
@@ -9205,6 +9208,11 @@
"@colors/colors": "1.5.0"
}
},
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
"node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -13224,9 +13232,9 @@
}
},
"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==",
"version": "22.4.9",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.9.tgz",
"integrity": "sha512-8gWMmUz460KJDQp/ob3MNUX84cVuDRY9PLFPnV8d+Qezz/6dkjxwOaH70xjrCNDO+JrUL25iXfAIN9wUkInNZw==",
"funding": [
{
"type": "individual",
@@ -17079,11 +17087,11 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/posthog-js": {
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.34.0.tgz",
"integrity": "sha512-HkRwwzdz31N5ykQIO3SIkSS8nwhdqqnuDZ/qltitX4FhxrV9/tSRavEXz0YLvioOXeNVmQWtsN3krKajErwkwg==",
"version": "1.39.4",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.39.4.tgz",
"integrity": "sha512-Elpf1gwyuObueXi89iH+9pP+WhpkiivP8Qwej4RzOLwSTa7Floaa4rgAw7rnCnX1PtRoJ3F0kqb6q9T+aZjRiA==",
"dependencies": {
"@sentry/types": "^7.2.0",
"@sentry/types": "7.22.0",
"fflate": "^0.4.1",
"rrweb-snapshot": "^1.1.14"
}
@@ -23946,10 +23954,12 @@
}
},
"@headlessui/react": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.6.6.tgz",
"integrity": "sha512-MFJtmj9Xh/hhBMhLccGbBoSk+sk61BlP6sJe4uQcVMtXZhCgGqd2GyIQzzmsdPdTEWGSF434CBi8mnhR6um46Q==",
"requires": {}
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.7.tgz",
"integrity": "sha512-BqDOd/tB9u2tA0T3Z0fn18ktw+KbVwMnkxxsGPIH2hzssrQhKB5n/6StZOyvLYP/FsYtvuXfi9I0YowKPv2c1w==",
"requires": {
"client-only": "^0.0.1"
}
},
"@hookform/resolvers": {
"version": "2.9.10",
@@ -24987,9 +24997,9 @@
"dev": true
},
"@sentry/types": {
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.15.0.tgz",
"integrity": "sha512-MN9haDRh9ZOsTotoDTHu2BT3sT8Vs1F0alhizUpDyjN2YgBCqR6JV+AbAE1XNHwS2+5zbppch1PwJUVeE58URQ=="
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.22.0.tgz",
"integrity": "sha512-LhCL+wb1Jch+OesB2CIt6xpfO1Ab6CRvoNYRRzVumWPLns1T3ZJkarYfhbLaOEIb38EIbPgREdxn2AJT560U4Q=="
},
"@sinclair/typebox": {
"version": "0.24.51",
@@ -26659,9 +26669,9 @@
}
},
"@stripe/react-stripe-js": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.10.0.tgz",
"integrity": "sha512-vuIjJUZJ3nyiaGa5z5iyMCzZfGGsgzOOjWjqknbbhkNsewyyginfeky9EZLSz9+iSAsgC9K6MeNOTLKVGcMycQ==",
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.16.3.tgz",
"integrity": "sha512-gS6UDGEuM92K50pFB3o//EqqPxmaqpC8IrsBda4P4LxeULoO0pBFSHXJ5Ab6uA7G2lyO2bluvSLereh0OH9GrQ==",
"requires": {
"prop-types": "^15.7.2"
}
@@ -28115,9 +28125,9 @@
}
},
"axios-auth-refresh": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.3.tgz",
"integrity": "sha512-2IbDhJ/h6ddNBBnnzn1VFK/qx17pE9aVqiafB8rx5LVHsJ1HtFpUGkbXY7PzTG+8P9HJWcyA3fNZl9BikSuilg==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.6.tgz",
"integrity": "sha512-2CeBUce/SxIfFxow5/n8vApJ97yYF6qoV4gh1UrswT7aEOnlOdBLxxyhOI4IaxGs6BY0l8YujU2jlc4aCmK17Q==",
"requires": {}
},
"axobject-query": {
@@ -28888,6 +28898,11 @@
"string-width": "^4.2.0"
}
},
"client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -31990,9 +32005,9 @@
"dev": true
},
"i18next": {
"version": "22.4.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.6.tgz",
"integrity": "sha512-9Tm1ezxWyzV+306CIDMBbYBitC1jedQyYuuLtIv7oxjp2ohh8eyxP9xytIf+2bbQfhH784IQKPSYp+Zq9+YSbw==",
"version": "22.4.9",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.9.tgz",
"integrity": "sha512-8gWMmUz460KJDQp/ob3MNUX84cVuDRY9PLFPnV8d+Qezz/6dkjxwOaH70xjrCNDO+JrUL25iXfAIN9wUkInNZw==",
"requires": {
"@babel/runtime": "^7.20.6"
}
@@ -34690,11 +34705,11 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"posthog-js": {
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.34.0.tgz",
"integrity": "sha512-HkRwwzdz31N5ykQIO3SIkSS8nwhdqqnuDZ/qltitX4FhxrV9/tSRavEXz0YLvioOXeNVmQWtsN3krKajErwkwg==",
"version": "1.39.4",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.39.4.tgz",
"integrity": "sha512-Elpf1gwyuObueXi89iH+9pP+WhpkiivP8Qwej4RzOLwSTa7Floaa4rgAw7rnCnX1PtRoJ3F0kqb6q9T+aZjRiA==",
"requires": {
"@sentry/types": "^7.2.0",
"@sentry/types": "7.22.0",
"fflate": "^0.4.1",
"rrweb-snapshot": "^1.1.14"
}
+5 -5
View File
@@ -20,7 +20,7 @@
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/react-fontawesome": "^0.1.19",
"@headlessui/react": "^1.6.6",
"@headlessui/react": "^1.7.7",
"@hookform/resolvers": "^2.9.10",
"@octokit/rest": "^19.0.7",
"@radix-ui/react-accordion": "^1.1.0",
@@ -37,14 +37,14 @@
"@radix-ui/react-tabs": "^1.0.2",
"@radix-ui/react-toast": "^1.1.2",
"@reduxjs/toolkit": "^1.8.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/react-stripe-js": "^1.16.3",
"@stripe/stripe-js": "^1.46.0",
"@types/argon2-browser": "^1.18.1",
"@tanstack/react-query": "^4.23.0",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios-auth-refresh": "^3.3.3",
"axios-auth-refresh": "^3.3.6",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
"cookies": "^0.8.0",
@@ -52,13 +52,13 @@
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"http-proxy": "^1.18.1",
"i18next": "^22.4.6",
"i18next": "^22.4.9",
"jspdf": "^2.5.1",
"jsrp": "^0.2.4",
"markdown-it": "^13.0.1",
"next": "^12.3.4",
"next-i18next": "^13.0.2",
"posthog-js": "^1.34.0",
"posthog-js": "^1.39.4",
"query-string": "^7.1.3",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
@@ -10,6 +10,7 @@ const integrationSlugNameMapping: Mapping = {
'vercel': 'Vercel',
'netlify': 'Netlify',
'github': 'GitHub',
'gitlab': 'GitLab',
'render': 'Render',
'flyio': 'Fly.io',
'circleci': 'CircleCI',
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -11,6 +11,12 @@
"image": "Docker Compose",
"docsLink": "https://infisical.com/docs/integrations/platforms/docker-compose"
},
{
"name": "Kubernetes",
"slug": "kubernetes",
"image": "Kubernetes",
"docsLink": "https://infisical.com/docs/integrations/platforms/kubernetes"
},
{
"name": "React",
"slug": "react",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"title": "Activity Logs",
"title": "Audit Logs",
"subtitle": "Event history for this Infisical project.",
"event": {
"readSecrets": "Secrets Viewed",
+2 -2
View File
@@ -13,8 +13,8 @@
"project-id": "Project ID",
"save-changes": "Save Changes",
"saved": "Saved",
"drop-zone": "Drag and drop a .env or .yml file here.",
"drop-zone-keys": "Drag and drop a .env or .yml file here to add more secrets.",
"drop-zone": "Drag and drop a .env, .json, or .yml file here.",
"drop-zone-keys": "Drag and drop a .env, .json, or .yml file here to add more secrets.",
"role": "Role",
"role_admin": "admin",
"display-name": "Display Name",
+11
View File
@@ -0,0 +1,11 @@
{
"title": "Historial de eventos",
"subtitle": "Historial de eventos para este proyecto de Infisical.",
"event": {
"readSecrets": "Secrets vistas",
"updateSecrets": "Secrets actualizadas",
"addSecrets": "Secrets añadidas",
"deleteSecrets": "Secrets eliminadas"
},
"ip-address": "Dirección IP"
}
+28
View File
@@ -0,0 +1,28 @@
{
"title": "Uso & Facturación",
"description": "Visualiza y gestiona la suscripción de tu organización",
"subscription": "Suscripción",
"starter": {
"name": "Starter",
"price-explanation": "hasta 5 miembros",
"text": "¡Hasta 5 personas gratis!",
"subtext": "Después, $5 por miembro/mes."
},
"professional": {
"name": "Professional",
"price-explanation": "/miembro/mes",
"subtext": "Includes unlimited projects & members.",
"text": "Mantén la gestión de claves a medida que creces."
},
"enterprise": {
"name": "Enterprise",
"text": "Mantén la gestión de claves a medida que creces."
},
"current-usage": "Uso actual",
"free": "Gratis",
"downgrade": "Reducir",
"upgrade": "Mejorar",
"learn-more": "Saber más",
"custom-pricing": "A medida",
"schedule-demo": "Solicitar una demo"
}
+34
View File
@@ -0,0 +1,34 @@
{
"head-title": "{{title}} | Infisical",
"error_project-already-exists": "Ya existe un proyecto con este nombre.",
"no-mobile": "Para usar Infisical, inicia sesión con un dispositivo de mayores dimesiones.",
"email": "Correo electrónico",
"password": "Contraseña",
"first-name": "Nombre",
"last-name": "Apellidos",
"logout": "Cerrar sesión",
"validate-required": "Por favor, introduce tu {{name}}",
"maintenance-alert": "Estamos experimentando problemas técnicos. Estamos trabajando para resolverlos. Por favor, vuelve en unos minutos.",
"click-to-copy": "Click para copiar",
"project-id": "ID de proyecto",
"save-changes": "Guardar cambios",
"saved": "Guardado",
"drop-zone": "Arrastra y suelta un archivo .env, .json o .yml aquí.",
"drop-zone-keys": "Arrastra y suelta un archivo .env, .json, or .yml aquí para añadir más secrets.",
"role": "Rol",
"role_admin": "admin",
"display-name": "Nombre visible",
"environment": "Entorno",
"expired-in": "Expira en",
"language": "Idioma",
"search": "Buscar...",
"note": "Nota",
"view-more": "Ver más",
"end-of-history": "Fin del historial",
"select-event": "Selecciona un evento",
"event": "Evento",
"user": "Usuario",
"source": "Fuente",
"time": "Hora",
"timestamp": "Marca temporal"
}
+35
View File
@@ -0,0 +1,35 @@
{
"title": "Secrets",
"og-title": "Gestiona tus archivos .env en segundos",
"og-description": "Infisical es una solución cifrada de extremo a extremo que habilita a los equipos a compartir archivos .env de forma sencilla.",
"search-keys": "Buscar claves...",
"add-key": "Añadir clave",
"personal": "Personal",
"personal-description": "Las claves personales solo son visibles para ti",
"shared": "Compartido",
"shared-description": "Claves compartidas son visibles para todo tu equipo",
"make-shared": "Hacer compartida",
"make-personal": "Hacer personal",
"add-secret": "Añadir una nueva clave",
"check-docs": {
"button": "Comprueba la documentación",
"title": "¡Buen trabajo!",
"line1": "Enhorabuena por añadir claves.",
"line2": "Averigua como usarlo desde tu código."
},
"sidebar": {
"secret": "Secret",
"key": "Clave",
"value": "valor",
"override": "Sobreescribir valor con el valor personal",
"version-history": "Historial de versiones",
"comments": "Comentarios & Notas",
"personal-explanation": "Esta clave es personal. No será compartida con ninguno de tus compañeros.",
"generate-random-hex": "Generar Hex aleatorio",
"digits": "dígitos",
"delete-key-dialog": {
"title": "Eliminar clave",
"confirm-delete-message": "¿Estás seguro de que quieres eliminar esta clave? Esta operación no se puede deshacer."
}
}
}
@@ -0,0 +1,16 @@
{
"title": "Integraciones del proyecto",
"description": "Gestiona tus integraciones de Infisical con aplicaciones de terceros.",
"no-integrations1": "No tienes ninguna integración configurada. Cuando las tengas, aparecerán aquí.",
"no-integrations2": "Para empezar haz click en cualquiera de las opciones siguientes. Se necesitan tan solo 5 pasos.",
"available": "Integraciones con la nube",
"available-text1": "Haz click en la integración que quieras conectar. Esto hará que las variables de entorno se configuren automáticamente en los servicios de terceros seleccionados.",
"available-text2": "Nota: en la integración con Heroku, por motivos de seguridad, es imposible mantener el cifrado de extremo a extremo. En teoría, esto permite a Infisical descifrar tus variables de entorno. En la práctica, te podemos asegurar que esto nunca va a suceder. El núcleo del servicio Infisical siempre estará cifrado de extremo a extremo. Si tienes dudas, escríbenos a [email protected].",
"cloud-integrations": "Integraciones con la nube",
"framework-integrations": "Integraciones con frameworks",
"click-to-start": "Haz click en una integración para empezar a sincronizar las claves.",
"click-to-setup": "Haz click en un framework para ver las instrucciones de uso.",
"grant-access-to-secrets": "Autorizar Infisical el acceso a tus claves",
"why-infisical-needs-access": "La mayoría de las integraciones en la nube requieren a Infisical tener permiso para descifrar tus claves, para que puedan ser enviadas.",
"grant-access-button": "Autorizar acceso"
}
+10
View File
@@ -0,0 +1,10 @@
{
"title": "Iniciar sesión",
"og-title": "Iniciar sesión en Infisical",
"og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files.",
"login": "Iniciar sesión",
"need-account": "¿Necesitas una cuenta de Infisical?",
"create-account": "Crea una cuenta",
"forgot-password": "¿Has olvidado tu contraseña?",
"error-login": "Datos incorrectos."
}
+28
View File
@@ -0,0 +1,28 @@
{
"title": "Registrarse",
"og-title": "Reemplaza los archivos .env con una línea de código. Regístrate en Infisical en solo 3 minutos.",
"og-description": "Infisical es una plataforma sencilla cifrada de extremo a extremo que permite a equipos sincronizar y gestionar API-keys y variables de entorno. Funciona con Node.js, Next.js, Gatsby, Nest.js...",
"signup": "Registrarse",
"already-have-account": "¿Ya tienes una cuenta? Inicia sesión",
"forgot-password": "¿Has olvidado tu contraseña?",
"verify": "Verificar",
"step1-start": "Vamos a empezar",
"step1-privacy": "Al crear la cuenta, aceptas nuestros Términos y has leído y comprendido nuestra Política de privacidad.",
"step1-submit": "Empezar",
"step2-message": "Hemos enviado un código a",
"step2-code-error": "Oops. Tu código es incorrecto. Intentos restantes:",
"step2-resend-alert": "¿No ves el código?",
"step2-resend-submit": "Volver a enviar",
"step2-resend-progress": "Reenviando...",
"step2-spam-alert": "Asegúrate de comprobar la carpeta de spam.",
"step3-message": "¡Casi hemos terminado!",
"step4-message": "Guarda tu Kit de emergencia",
"step4-description1": "Si pierdes el acceso a tu cuenta, tu Kit de emergencia es la única forma de iniciar sesión.",
"step4-description2": "Te recomendamos que lo descargues y lo guardes en un sitio seguro.",
"step4-description3": "Contiene tu Clave secreta, a la que no tenemos acceso y tampoco podemos recuperar si la pierdes.",
"step4-download": "Descargar PDF",
"step5-send-invites": "Enviar invitaciones",
"step5-invite-team": "Invita a tu equipo",
"step5-subtitle": "Infisical está pensado para usarlo con tus compañeros. Invítalos a probarlo.",
"step5-skip": "Saltar"
}
+22
View File
@@ -0,0 +1,22 @@
{
"support": {
"slack": "Unirse al Slack",
"docs": "Leer documentación",
"issue": "Abrir una Github Issue",
"email": "Envíanos un correo electrónico"
},
"user": {
"signed-in-as": "INICIADO COMO",
"current-organization": "ORGANIZACIÓN ACTUAL",
"usage-billing": "Uso & Facturación",
"invite": "Invitar miembros",
"other-organizations": "OTRA ORGANIZACIÓN"
},
"menu": {
"project": "PROYECTO",
"secrets": "Claves",
"members": "Miembros",
"integrations": "Integraciones",
"project-settings": "Configuración del proyecto"
}
}
@@ -0,0 +1,13 @@
{
"api-keys": "Tokens de servicio",
"api-keys-description": "Cada token de servicio es específico para ti, para cada proyecto y para cada entorno en este proyecto.",
"add-new": "Añadir nuevo token",
"add-dialog": {
"title": "Añadir una API Key",
"description": "Especifica el nombre y el período de expiración. Cuando una API key es generada, solo podrás verla una vez hasta que desaparezca. Asegúrate de guardarla en algún lado.",
"name": "Nombre de la API Key",
"add": "Añadir API Key",
"copy-service-token": "Copia tu API key",
"copy-service-token-description": "Una vez que cierres esta ventana, no volverás a ver tu API key"
}
}
@@ -0,0 +1,11 @@
{
"incident-contacts": "Contactos de incidencias",
"incident-contacts-description": "Estos contactos serán notificados en caso de una incidencia grave.",
"no-incident-contacts": "No se han encontrado contactos para incidencias.",
"add-contact": "Añadir contacto",
"add-dialog": {
"title": "Añadir un contacto de incidencias",
"description": "Este contacto será notificado en el caso de una incidencia grave.",
"add-incident": "Añadir contacto de incidencias"
}
}
@@ -0,0 +1,14 @@
{
"add-member": "Añadir miembro",
"org-members": "Miembros de la organización",
"org-members-description": "Gestiona los miembros de tu organización. Estos usuarios podrán ser agrupados en proyectos más tarde.",
"search-members": "Buscar miembros...",
"add-dialog": {
"add-member-to-project": "Añadir un miembro al proyecto",
"already-all-invited": "Todos los uaurios en tu organización han sido invitados.",
"add-user-org-first": "Añadir primero más usuarios a la organización.",
"user-will-email": "El usuario recibirá un correo electrónico con la invitación.",
"looking-add": "<0>Si quieres añadir usuarios a tu organización, haz</0><1>click aquí</1>",
"add-user-to-org": "Añadir usuarios a la organización"
}
}
@@ -0,0 +1,11 @@
{
"password": "Contraseña",
"change": "Cambiar contraseña",
"current": "Contraseña actual",
"current-wrong": "La contraseña actual puede puede que sea incorrecta",
"new": "Nueva contraseña",
"validate-base": "La contraseña debe contener como mínimo:",
"validate-length": "14 caracteres",
"validate-case": "1 letra en minúsculas",
"validate-number": "1 número"
}
@@ -0,0 +1,13 @@
{
"service-tokens": "Tokens de servicio",
"service-tokens-description": "Cada token de servicio es específico para ti, para cada proyecto y para cada entorno en este proyectos.",
"add-new": "Añadir nuevo token",
"add-dialog": {
"title": "Añade un nuevo token de servicio para {{target}}",
"description": "Cuando se genera un token, solo podrás verlo una vez antes de que desaparezca. Asegúrate de guardarlo en algún lugar.",
"name": "Nombre del token de servicio",
"add": "Añadir token de servicio",
"copy-service-token": "Copiar tu token de servicio",
"copy-service-token-description": "Una vez cierres esta ventana, no volverás a ver tu token de nuevo"
}
}
@@ -0,0 +1,4 @@
{
"title": "Miembros del proyecto",
"description": "Esta página muestra los miembros del proyecto seleccionado, y permite modificar sus permisos."
}
@@ -0,0 +1,4 @@
{
"title": "Configuración de la organización",
"description": "Gestiona los miembros de tu organización. Estos usuarios podrán ser gestionados en proyectos más adelante."
}
@@ -0,0 +1,16 @@
{
"title": "Preferencias personales",
"description": "Visualiza y gestiona aquí tu información personal.",
"emergency": {
"name": "Kit de emergencia",
"text1": "Tu Kit de emergencia contiene la información que necesitarás para iniciar sesión en tu cuenta de Infisical.",
"text2": "Solo el último Kit de emergencia descargado es válido. Para conseguir uno nuevo, introduce tu contraseña.",
"download": "Descargar Kit de emergencia"
},
"change-language": "Cambiar idioma",
"api-keys": {
"title": "API Keys",
"description": "Gestiona tus API Keys personales para acceder a la API de Infisical.",
"add-new": "Añadir"
}
}
@@ -0,0 +1,15 @@
{
"title": "Configuración de proyecto",
"description": "Esta configuración solo afecta al proyecto seleccionado.",
"danger-zone": "Zona de peligro",
"delete-project": "Eliminar proyecto",
"project-to-delete": "Proyecto a eliminar",
"danger-zone-note": "Tan pronto como elimines el proyecto, no podrás volver atrás. Esto eliminará todas las claves. Si aún quieres hacerlo, introduce el nombre del proyecto a continuación.",
"delete-project-note": "Nota: Solo puedes eliminar un proyecto si tienes más de uno",
"project-id-description": "Para integrar Infisical en tu aplicación y obtener la inyección automática de variables de entorno, debes usar el siguiente ID de Proyecto.",
"project-id-description2": "Para más guías, incluyendo ejemplos de código en diferentes lenguajes y frameworks, visita ",
"auto-generated": "Este es el ID único y autogenerado de proyecto. No se puede modificar.",
"docs": "Documentación de Infisical",
"auto-capitalization": "Mayúsculas automáticas",
"auto-capitalization-description": "De acuerdo con los estándares, Infisical pondrá en mayúsculas tus claves. Si quieres desactivar esta funcionalidad, lo puedes hacer aquí."
}
+28
View File
@@ -0,0 +1,28 @@
{
"title": "Registrarse",
"og-title": "Reemplaza los archivos .env con una línea de código. Regístrate en Infisical en solo 3 minutos.",
"og-description": "Infisical es una plataforma sencilla cifrada de extremo a extremo que permite a equipos sincronizar y gestionar API-keys y variables de entorno. Funciona con Node.js, Next.js, Gatsby, Nest.js...",
"signup": "Registrarse",
"already-have-account": "¿Ya tienes una cuenta? Inicia sesión",
"forgot-password": "¿Has olvidado tu contraseña?",
"verify": "Verificar",
"step1-start": "Vamos a empezar",
"step1-privacy": "Al crear la cuenta, aceptas nuestros Términos y has leído y comprendido nuestra Política de privacidad.",
"step1-submit": "Empezar",
"step2-message": "Hemos enviado un código a",
"step2-code-error": "Oops. Tu código es incorrecto. Intentos restantes:",
"step2-resend-alert": "¿No ves el código?",
"step2-resend-submit": "Volver a enviar",
"step2-resend-progress": "Reenviando...",
"step2-spam-alert": "Asegúrate de comprobar la carpeta de spam.",
"step3-message": "¡Casi hemos terminado!",
"step4-message": "Guarda tu Kit de emergencia",
"step4-description1": "Si pierdes el acceso a tu cuenta, tu Kit de emergencia es la única forma de iniciar sesión.",
"step4-description2": "Te recomendamos que lo descargues y lo guardes en un sitio seguro.",
"step4-description3": "Contiene tu Clave secreta, a la que no tenemos acceso y tampoco podemos recuperar si la pierdes.",
"step4-download": "Descargar PDF",
"step5-send-invites": "Enviar invitaciones",
"step5-invite-team": "Invita a tu equipo",
"step5-subtitle": "Infisical está pensado para usarlo con tus compañeros. Invítalos a probarlo.",
"step5-skip": "Saltar"
}
+1 -1
View File
@@ -162,7 +162,7 @@ const Layout = ({ children }: LayoutProps) => {
},
{
href: `/activity/${workspaceMapping[workspaceSelected as any]}`,
title: 'Activity Logs',
title: 'Audit Logs',
emoji: <FontAwesomeIcon icon={faFileLines} />
},
{
+24 -3
View File
@@ -71,6 +71,19 @@ const DropZone = ({
}));
break;
}
case 'json': {
const keyPairs = JSON.parse(String(file));
secrets = Object.keys(keyPairs).map((key, index) => ({
id: guidGenerator(),
pos: numCurrentRows + index,
key,
value: keyPairs[key as keyof typeof keyPairs],
comment: '',
type: 'shared',
tags: []
}));
break;
}
case 'yml': {
const parsedFile = parseDocument(file.toString());
const keyPairs = parsedFile.contents!.toJSON();
@@ -97,6 +110,10 @@ const DropZone = ({
}
default:
secrets = '';
createNotification({
text: `The file you are dropping should have one of the following extensions: .env, .yml, .json.`,
type: 'error'
});
break;
}
return secrets;
@@ -126,9 +143,13 @@ const DropZone = ({
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
// parse function's argument looks like to be ArrayBuffer
const newData = getSecrets(event.target.result as ArrayBuffer, fileType);
setData(newData);
setButtonReady(true);
try {
const newData = getSecrets(event.target.result as ArrayBuffer, fileType);
setData(newData);
setButtonReady(true);
} catch (error) {
console.log("Error while dropping the file: ", error)
}
};
// If something is wrong show an error
@@ -10,7 +10,8 @@ interface Framework {
const FrameworkIntegration = ({ framework }: { framework: Framework }) => (
<a
href={framework.docsLink}
rel="noopener"
rel="noopener noreferrer"
target="_blank"
className="relative flex flex-row justify-center bg-bunker-500 hover:bg-gradient-to-tr duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer"
>
<div
@@ -11,7 +11,7 @@ type Props = {
};
export const EmptyState = ({ title, className, children, icon = faCubesStacked }: Props) => (
<div className={twMerge('flex w-full flex-col items-center px-2 pt-6 text-bunker-300', className)}>
<div className={twMerge('flex w-full bg-bunker-700 flex-col items-center px-2 pt-6 text-bunker-300', className)}>
<FontAwesomeIcon icon={icon} size="2x" className='mr-4' />
<div className='flex flex-row items-center py-4'>
<div className="text-bunker-300 text-sm">{title}</div>
+1 -1
View File
@@ -14,7 +14,7 @@ const tagVariants = cva('inline-flex whitespace-nowrap text-sm rounded-sm mr-1.5
red: 'bg-red/80 text-bunker-100'
},
size: {
sm: 'px-1 py-0.5'
sm: 'px-1.5 py-0.5'
}
}
});
-1
View File
@@ -75,7 +75,6 @@ let privateKey;
throw new Error('Insufficient details to decrypt private key');
}
} catch (err) {
console.error(err);
throw new Error('Failed to decrypt private key');
}
+1 -1
View File
@@ -308,7 +308,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
isSelected={router.asPath === `/activity/${currentWorkspace?._id}`}
icon={<FontAwesomeIcon icon={faFileLines} size="lg" />}
>
Activity Logs
Audit Logs
</MenuItem>
</a>
</Link>
@@ -26,7 +26,7 @@ const supportOptions = (t: TFunction) => [
[
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faSlack} />,
t('nav:support.slack'),
'https://join.slack.com/t/infisical/shared_invite/zt-1dgg63ln8-G7PCNJdCymAT9YF3j1ewVA'
'https://infisical.com/slack'
],
[
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faBook} />,
+7 -1
View File
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
@@ -125,7 +126,12 @@ export default function Activity() {
return (
<div className="mx-6 lg:mx-0 w-full h-screen">
<NavHeader pageName="Project Activity" isProjectRelated />
<Head>
<title>Audit Logs</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Head>
<NavHeader pageName="Audit Logs" isProjectRelated />
{currentSidebarAction && (
<ActivitySideBar toggleSidebar={toggleSidebar} currentAction={currentSidebarAction} />
)}
+7 -1
View File
@@ -172,7 +172,7 @@ export default function Integrations() {
let link = '';
switch (integrationOption.slug) {
case 'azure-key-vault':
link = `https://login.microsoftonline.com/${integrationOption.tenantId}/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/azure-key-vault/oauth2/callback&response_mode=query&scope=https://vault.azure.net/.default openid offline_access&state=${state}`;
link = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/integrations/azure-key-vault/oauth2/callback&response_mode=query&scope=https://vault.azure.net/.default openid offline_access&state=${state}`;
break;
case 'aws-parameter-store':
link = `${window.location.origin}/integrations/aws-parameter-store/authorize`;
@@ -192,6 +192,9 @@ export default function Integrations() {
case 'github':
link = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/integrations/github/oauth2/callback&state=${state}`;
break;
case 'gitlab':
link = `https://gitlab.com/oauth/authorize?client_id=${integrationOption.clientId}&redirect_uri=${window.location.origin}/integrations/gitlab/oauth2/callback&response_type=code&state=${state}`;
break;
case 'render':
link = `${window.location.origin}/integrations/render/authorize`
break;
@@ -241,6 +244,9 @@ export default function Integrations() {
case 'github':
link = `${window.location.origin}/integrations/github/create?integrationAuthId=${integrationAuth._id}`;
break;
case 'gitlab':
link = `${window.location.origin}/integrations/gitlab/create?integrationAuthId=${integrationAuth._id}`;
break;
case 'render':
link = `${window.location.origin}/integrations/render/create?integrationAuthId=${integrationAuth._id}`;
break;
@@ -0,0 +1,125 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import queryString from 'query-string';
import { getTranslatedServerSideProps } from '../../../components/utilities/withTranslateProps';
import {
Button,
Card,
CardTitle,
FormControl,
Select,
SelectItem
} from '../../../components/v2';
import { useGetIntegrationAuthApps,useGetIntegrationAuthById } from '../../../hooks/api/integrationAuth';
import { useGetWorkspaceById } from '../../../hooks/api/workspace';
import createIntegration from "../../api/integrations/createIntegration";
export default function GitLabCreateIntegrationPage() {
const router = useRouter();
const { integrationAuthId } = queryString.parse(router.asPath.split('?')[1]);
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
const [owner, setOwner] = useState<string | null>(null);
const [targetApp, setTargetApp] = useState('');
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (workspace) {
setSelectedSourceEnvironment(workspace.environments[0].slug);
}
}, [workspace]);
useEffect(() => {
// TODO: handle case where apps can be empty
if (integrationAuthApps) {
setTargetApp(integrationAuthApps[0].name);
setOwner(integrationAuthApps[0]?.owner ?? null);
}
}, [integrationAuthApps]);
const handleButtonClick = async () => {
try {
setIsLoading(true);
if (!integrationAuth?._id) return;
await createIntegration({
integrationAuthId: integrationAuth?._id,
isActive: true,
app: targetApp,
appId: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp))?.appId ?? null,
sourceEnvironment: selectedSourceEnvironment,
targetEnvironment: null,
owner,
path: null,
region: null
});
setIsLoading(false);
router.push(
`/integrations/${localStorage.getItem('projectData.id')}`
);
} catch (err) {
console.error(err);
}
}
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && targetApp) ? (
<div className="h-full w-full flex justify-center items-center">
<Card className="max-w-md p-8 rounded-md">
<CardTitle className='text-center'>GitLab Integration</CardTitle>
<FormControl
label="Project Environment"
className='mt-4'
>
<Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className='w-full border border-mineshaft-500'
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl
label="GitLab Repo"
className='mt-4'
>
<Select
value={targetApp}
onValueChange={(val) => setTargetApp(val)}
className='w-full border border-mineshaft-500'
>
{integrationAuthApps.map((integrationAuthApp) => (
<SelectItem value={integrationAuthApp.name} key={`gitlab-environment-${integrationAuthApp.name}`}>
{integrationAuthApp.name}
</SelectItem>
))}
</Select>
</FormControl>
<Button
onClick={handleButtonClick}
color="mineshaft"
className='mt-4'
isLoading={isLoading}
>
Create Integration
</Button>
</Card>
</div>
) : <div />
}
GitLabCreateIntegrationPage.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps(['integrations']);
@@ -0,0 +1,39 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import queryString from 'query-string';
import { getTranslatedServerSideProps } from '../../../../components/utilities/withTranslateProps';
import AuthorizeIntegration from "../../../api/integrations/authorizeIntegration";
export default function GitLabOAuth2CallbackPage() {
const router = useRouter();
const { code, state } = queryString.parse(router.asPath.split('?')[1]);
useEffect(() => {
(async () => {
try {
// validate state
if (state !== localStorage.getItem('latestCSRFToken')) return;
localStorage.removeItem('latestCSRFToken');
const integrationAuth = await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id') as string,
code: code as string,
integration: 'gitlab'
});
router.push(
`/integrations/gitlab/create?integrationAuthId=${integrationAuth._id}`
);
} catch (err) {
console.error(err);
}
})();
}, []);
return <div />
}
GitLabOAuth2CallbackPage.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps(['integrations']);
+2 -3
View File
@@ -77,13 +77,13 @@ export default function SettingsBilling() {
}, []);
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<div className="bg-bunker-800 pb-4 flex flex-col justify-between text-white">
<Head>
<title>{t('common:head-title', { title: t('billing:title') })}</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-row">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<div className="w-full pb-2">
<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-col justify-start items-start text-3xl">
@@ -93,7 +93,6 @@ export default function SettingsBilling() {
</div>
<div className="flex flex-col ml-6 text-mineshaft-50 w-max">
<p className="text-xl font-semibold">{t('billing:subscription')}</p>
<div className="mt-4 text-bunker-200 h-14 flex justify-center items-center rounded-md bg-bunker-600 mr-4"> If you are looking to get an annual plan, please reach out to <a className="ml-1.5 underline text-primary underline-offset-2" href="mailto:[email protected]">team@infisical.com</a></div>
<div className="grid grid-cols-2 grid-rows-2 gap-y-6 gap-x-3 mt-4 overflow-x-auto">
{plans.map((plan) => (
<Plan key={plan.name} plan={plan} />
@@ -96,7 +96,7 @@ export default function PersonalSettings() {
<ListBox
isSelected={lang}
onChange={setLanguage}
data={['en', 'ko', 'fr']}
data={['en', 'ko', 'fr', 'es']}
text={`${t('common:language')}: `}
/>
</div>
@@ -1,5 +1,6 @@
import { useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useRouter } from 'next/router';
import { faMagnifyingGlass, faPlus, faTrash, faUsers } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { yupResolver } from '@hookform/resolvers/yup';
@@ -61,6 +62,7 @@ export const OrgMembersTable = ({
userId,
isLoading
}: Props) => {
const router = useRouter();
const [searchMemberFilter, setSearchMemberFilter] = useState('');
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
'addMember',
@@ -159,7 +161,7 @@ export const OrgMembersTable = ({
<Select
defaultValue={role}
isDisabled={userId === user?._id}
className="w-full bg-mineshaft-600"
className="w-40 bg-mineshaft-600"
onValueChange={(selectedRole) =>
onRoleChange(orgMembershipId, selectedRole)
}
@@ -172,8 +174,8 @@ export const OrgMembersTable = ({
</Select>
)}
{(status === 'invited' || status === 'verified') && (
<Button colorSchema="secondary" onClick={() => onInviteMember(email)}>
Resent Invite
<Button className='w-40' colorSchema="secondary" onClick={() => onInviteMember(email)}>
Resend Invite
</Button>
)}
{status === 'completed' && (
@@ -193,7 +195,17 @@ export const OrgMembersTable = ({
</Tag>
))
) : (
<Tag colorSchema="red">This user isn&apos;t part of any projects yet</Tag>
<div className='flex flex-row'>
<Tag colorSchema="red">This user isn&apos;t part of any projects yet</Tag>
<button
type="button"
onClick={() => router.push(`/users/${router.query.id}`)}
className='text-sm bg-mineshaft w-max px-1.5 py-0.5 hover:bg-primary duration-200 hover:text-black cursor-pointer rounded-sm'
>
<FontAwesomeIcon icon={faPlus} className="mr-1" />
Add to projects
</button>
</div>
)}
</Td>
<Td>