feat: renamed old frontend and fixed some minor bugs

This commit is contained in:
=
2025-01-06 14:56:49 +05:30
parent 8c902d7699
commit 54377a44d3
2577 changed files with 35240 additions and 35116 deletions
+1 -2
View File
@@ -1,3 +1,2 @@
node_modules
**/.next
.next
dist
-108
View File
@@ -1,108 +0,0 @@
module.exports = {
overrides: [
{
files: ["next.config.js"]
}
],
root: true,
env: {
browser: true,
es2021: true,
es6: true
},
extends: [
"airbnb",
"airbnb-typescript",
"airbnb/hooks",
"plugin:react/recommended",
"prettier",
"plugin:storybook/recommended"
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: "./tsconfig.json",
ecmaFeatures: {
jsx: true
},
tsconfigRootDir: __dirname
},
plugins: ["react", "prettier", "simple-import-sort", "import"],
rules: {
"@typescript-eslint/no-empty-function": "off",
quotes: ["error", "double", { avoidEscape: true }],
"comma-dangle": ["error", "only-multiline"],
"react/react-in-jsx-scope": "off",
"import/prefer-default-export": "off",
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/ban-ts-comment": "warn",
"react/jsx-props-no-spreading": "off", // switched off for component building
// TODO: This rule will be switched ON after complete revamp of frontend
"@typescript-eslint/no-explicit-any": "off",
"jsx-a11y/control-has-associated-label": "off",
"no-console": "off",
"arrow-body-style": "off",
"no-underscore-dangle": [
"error",
{
allow: ["_id"]
}
],
"jsx-a11y/anchor-is-valid": "off",
// all those <a> tags must be converted to label or a p component
//
"react/require-default-props": "off",
"react/jsx-filename-extension": [
1,
{
extensions: [".tsx", ".ts"]
}
],
// TODO: turn this rule ON after migration. everything should use arrow functions
"react/function-component-definition": [
0,
{
namedComponents: "arrow-function"
}
],
"react/no-unknown-property": [
"error",
{
ignore: ["jsx"]
}
],
"@typescript-eslint/no-non-null-assertion": "off",
"simple-import-sort/exports": "warn",
"simple-import-sort/imports": [
"warn",
{
groups: [
// Node.js builtins. You could also generate this regex if you use a `.js` config.
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
// Note that if you use the `node:` prefix for Node.js builtins,
// you can avoid this complexity: You can simply use "^node:".
[
"^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)"
],
// Packages `react` related packages
["^react", "^next", "^@?\\w"],
["^@app"],
// Internal packages.
["^~(/.*|$)"],
// Relative imports
["^\\.\\.(?!/?$)", "^\\.\\./?$", "^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
// Style imports.
["^.+\\.?(css|scss)$"]
]
}
]
},
ignorePatterns: ["next.config.js", "cypress/**/*.js", "cypress.config.js"],
settings: {
"import/resolver": {
typescript: {
project: ["./tsconfig.json"]
}
}
}
};
+19 -31
View File
@@ -1,36 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.env
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
# debug
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
.vercel
.env.infisical
node_modules
dist
dist-ssr
*.local
.vscode
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+4 -1
View File
@@ -3,5 +3,8 @@
"printWidth": 100,
"trailingComma": "none",
"tabWidth": 2,
"semi": true
"semi": true,
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindStylesheet": "./src/index.css",
"tailwindFunctions": ["clsx", "twMerge"]
}
-28
View File
@@ -1,28 +0,0 @@
const path = require("path");
module.exports = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"storybook-dark-mode",
{
name: "@storybook/addon-styling",
options: {
postCss: {
implementation: require("postcss")
}
}
}
],
framework: {
name: "@storybook/nextjs",
options: {}
},
core: {
disableTelemetry: true
},
docs: {
autodocs: "tag"
}
};
-29
View File
@@ -1,29 +0,0 @@
import { themes } from "@storybook/theming";
import "react-day-picker/dist/style.css";
import "../src/styles/globals.css";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
backgrounds: {
default: "dark",
values: [
{
name: "dark",
value: "rgb(14, 16, 20)"
},
{
name: "paper",
value: "rgb(30, 31, 34)"
}
]
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/
}
},
darkMode: {
dark: { ...themes.dark, appContentBg: "rgb(14,16,20)", appBg: "rgb(14,16,20)" }
}
};
-84
View File
@@ -1,84 +0,0 @@
ARG POSTHOG_HOST=https://app.posthog.com
ARG POSTHOG_API_KEY=posthog-api-key
ARG INTERCOM_ID=intercom-id
ARG NEXT_INFISICAL_PLATFORM_VERSION=next-infisical-platform-version
ARG CAPTCHA_SITE_KEY=captcha-site-key
FROM node:16-alpine AS deps
# Install dependencies only when needed. Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
# RUN apk add --no-cache libc6-compat
WORKDIR /app
# Copy over dependency files
COPY package.json package-lock.json next.config.js ./
# Install dependencies
RUN npm ci --only-production --ignore-scripts
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
# Copy dependencies
COPY --from=deps /app/node_modules ./node_modules
# Copy all files
COPY . .
ENV NODE_ENV production
ENV NEXT_PUBLIC_ENV production
ARG POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
# Build
RUN npm run build
# Production image
FROM node:16-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN mkdir -p /app/.next/cache/images && chown nextjs:nodejs /app/.next/cache/images
VOLUME /app/.next/cache/images
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
ARG SAML_ORG_SLUG
ENV NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG \
BAKED_NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG
ARG NEXT_INFISICAL_PLATFORM_VERSION
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION=$NEXT_INFISICAL_PLATFORM_VERSION
ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY \
BAKED_NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
COPY --chown=nextjs:nodejs --chmod=555 scripts ./scripts
COPY --from=builder /app/public ./public
RUN chown nextjs:nodejs ./public/data
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs --chmod=777 /app/.next/static ./.next/static
RUN chmod -R 777 /app/.next/server
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV NEXT_TELEMETRY_DISABLED 1
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node scripts/healthcheck.js
CMD ["/app/scripts/start.sh"]
+1 -4
View File
@@ -1,5 +1,5 @@
# Base layer
FROM node:16-alpine
FROM node:20-alpine
# Set the working directory
WORKDIR /app
@@ -11,9 +11,6 @@ COPY package-lock.json ./
# Install
RUN npm install --ignore-scripts
# Copy over next.js config
COPY next.config.js ./next.config.js
# Copy all files
COPY . .
+41 -13
View File
@@ -1,22 +1,50 @@
This is the client repository for Infisical.
# React + TypeScript + Vite
## Before you get started with development locally
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Please ensure you have Docker and Docker Compose installed for your OS.
Currently, two official plugins are available:
### Steps to start server
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
- `CD` into the repo
- run command `docker-compose -f docker-compose.dev.yml up --build --force-recreate`
- Visit localhost:8080 and the website should be live
## Expanding the ESLint configuration
### Steps to shutdown this Docker compose
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- `CD` into this repo
- run command `docker-compose -f docker-compose.dev.yml down`
- Configure the top-level `parserOptions` property like this:
### Notes
```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
Any changes made to local files in the `/components`, `/pages`, `/styles` will be hot reloaded. If would like like to watch for other files or folders live, please add them to the docker volume.
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
You will also need to ensure that a .env.local file exists with all required environment variables
```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```
-7
View File
@@ -1,7 +0,0 @@
module.exports = {
e2e: {
baseUrl: 'http://localhost:8080',
viewportWidth: 1480,
viewportHeight: 920,
},
};
-47
View File
@@ -1,47 +0,0 @@
/// <reference types="cypress" />
describe('organization Overview', () => {
beforeEach(() => {
cy.login(`[email protected]`, `testInfisical1`)
})
const projectName = "projectY"
it('can`t create projects with empty names', () => {
cy.get('.button').click()
cy.get('input[placeholder="Type your project name"]').type('abc').clear()
cy.intercept('*').as('anyRequest');
cy.get('@anyRequest').should('not.exist');
})
it('can delete a newly-created project', () => {
// Create a project
cy.get('.button').click()
cy.get('input[placeholder="Type your project name"]').type(`${projectName}`)
cy.contains('button', 'Create Project').click()
cy.url().should('include', '/project')
// Delete a project
cy.get(`[href^="/project/"][href$="/settings"] > a > .group`).click()
cy.contains('button', `Delete ${projectName}`).click()
cy.contains('button', 'Delete Project').should('have.attr', 'disabled')
cy.get('input[placeholder="Type to delete..."]').type('confirm')
cy.contains('button', 'Delete Project').should('not.have.attr', 'disabled')
cy.url().then((currentUrl) => {
let projectId = currentUrl.split("/")[4]
cy.intercept('DELETE', `/api/v1/workspace/${projectId}`).as('deleteProject');
cy.contains('button', 'Delete Project').click();
cy.get('@deleteProject').should('have.property', 'response').and('have.property', 'statusCode', 200);
})
})
it('can display no projects', () => {
cy.intercept('/api/v1/workspace', {
body: {
"workspaces": []
},
})
cy.get('.border-mineshaft-700 > :nth-child(2)').should('have.text', 'You are not part of any projects in this organization yet. When you are, they will appear here.')
})
})
-24
View File
@@ -1,24 +0,0 @@
/// <reference types="cypress" />
describe('Organization Settings', () => {
let orgId;
beforeEach(() => {
cy.login(`[email protected]`, `testInfisical1`)
cy.url().then((currentUrl) => {
orgId = currentUrl.split("/")[4]
cy.visit(`org/${orgId}/settings`)
})
})
it('can rename org', () => {
cy.get('input[placeholder="Acme Corp"]').clear().type('ABC')
cy.intercept('PATCH', `/api/v1/organization/${orgId}/name`).as('renameOrg');
cy.get('form.p-4 > .button').click()
cy.get('@renameOrg').should('have.property', 'response').and('have.property', 'statusCode', 200);
cy.get('.pl-3').should("have.text", "ABC ")
})
})
@@ -1,84 +0,0 @@
/// <reference types="cypress" />
describe('Project Overview', () => {
const projectName = "projectY"
let projectId;
let isFirstTest = true;
before(() => {
cy.login(`[email protected]`, `testInfisical1`)
// Create a project
cy.get('.button').click()
cy.get('input[placeholder="Type your project name"]').type(`${projectName}`)
cy.contains('button', 'Create Project').click()
cy.url().should('include', '/project').then((currentUrl) => {
projectId = currentUrl.split("/")[4]
})
})
beforeEach(() => {
if (isFirstTest) {
isFirstTest = false;
return; // Skip the rest of the beforeEach for the first test
}
cy.login(`[email protected]`, `testInfisical1`)
cy.visit(`/project/${projectId}/secrets/overview`)
})
it('can create secrets', () => {
cy.contains('button', 'Go to Development').click()
cy.contains('button', 'Add a new secret').click()
cy.get('input[placeholder="Type your secret name"]').type('SECRET_A')
cy.contains('button', 'Create Secret').click()
cy.get('.w-80 > .inline-flex > .input').should('have.value', 'SECRET_A')
cy.get(':nth-child(6) > .button > .w-min').should('have.text', '1 Commit')
})
it('can update secrets', () => {
cy.get(':nth-child(2) > .flex > .button').click()
cy.get('.overflow-auto > .relative > .absolute').type('VALUE_A')
cy.get('.button.text-primary > .svg-inline--fa').click()
cy.get(':nth-child(6) > .button > .w-min').should('have.text', '2 Commits')
})
it('can`t create duplicate-name secrets', () => {
cy.get(':nth-child(2) > .flex > .button').click()
cy.contains('button', 'Add Secret').click()
cy.get('input[placeholder="Type your secret name"]').type('SECRET_A')
cy.intercept('POST', `/api/v3/secrets/SECRET_A`).as('createSecret');
cy.contains('button', 'Create Secret').click()
cy.get('@createSecret').should('have.property', 'response').and('have.property', 'statusCode', 400);
})
it('can add another secret', () => {
cy.get(':nth-child(2) > .flex > .button').click()
cy.contains('button', 'Add Secret').click()
cy.get('input[placeholder="Type your secret name"]').type('SECRET_B')
cy.contains('button', 'Create Secret').click()
cy.get(':nth-child(6) > .button > .w-min').should('have.text', '3 Commits')
})
it('can delete a secret', () => {
cy.get(':nth-child(2) > .flex > .button').click()
// cy.get(':nth-child(3) > .shadow-none').trigger('mouseover')
cy.get(':nth-child(3) > .shadow-none > .group > .h-10 > .border-red').click()
cy.contains('button', 'Delete Secret').should('have.attr', 'disabled')
cy.get('input[placeholder="Type to delete..."]').type('SECRET_B')
cy.intercept('DELETE', `/api/v3/secrets/SECRET_B`).as('deleteSecret');
cy.contains('button', 'Delete Secret').should('not.have.attr', 'disabled')
cy.contains('button', 'Delete Secret').click();
cy.get('@deleteSecret').should('have.property', 'response').and('have.property', 'statusCode', 200);
})
it('can add a comment', () => {
return;
cy.get(':nth-child(2) > .flex > .button').click()
// for some reason this hover does not want to work
cy.get('.overflow-auto').trigger('mouseover').then(() => {
cy.get('.shadow-none > .group > .pl-4 > .h-8 > button[aria-label="add-comment"]').should('be.visible').click()
});
})
})
-5
View File
@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
-19
View File
@@ -1,19 +0,0 @@
Cypress.Commands.add('login', (username, password) => {
cy.visit('/login')
cy.get('input[placeholder="Enter your email..."]').type(username)
cy.get('input[placeholder="Enter your password..."]').type(password)
cy.contains('Continue with Email').click()
cy.url().should('include', '/overview')
})
// Cypress.Commands.add('login', (username, password) => {
// cy.session([username, password], () => {
// cy.visit('/login')
// cy.get('input[placeholder="Enter your email..."]').type(username)
// cy.get('input[placeholder="Enter your password..."]').type(password)
// cy.contains('Continue with Email').click()
// cy.url().should('include', '/overview')
// cy.wait(2000);
// })
// })
-20
View File
@@ -1,20 +0,0 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
+137
View File
@@ -0,0 +1,137 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import eslintPluginPrettier from "eslint-plugin-prettier/recommended";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import tseslint from "typescript-eslint";
import { FlatCompat } from "@eslint/eslintrc";
import stylisticPlugin from "@stylistic/eslint-plugin";
import importPlugin from "eslint-plugin-import";
import pluginRouter from "@tanstack/eslint-plugin-router";
const compat = new FlatCompat({
baseDirectory: import.meta.dirname
});
export default tseslint.config(
{ ignores: ["dist"] },
{
extends: [
...pluginRouter.configs["flat/recommended"],
js.configs.recommended,
tseslint.configs.recommended,
...compat.extends("airbnb"),
...compat.extends("@kesills/airbnb-typescript"),
eslintPluginPrettier
],
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
}
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
"simple-import-sort": simpleImportSort,
import: importPlugin
},
settings: {
"import/resolver": {
typescript: {
project: ["./tsconfig.json"]
}
}
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": "off",
"@typescript-eslint/only-throw-error": "off",
"@typescript-eslint/no-empty-function": "off",
quotes: ["error", "double", { avoidEscape: true }],
"comma-dangle": ["error", "only-multiline"],
"react/react-in-jsx-scope": "off",
"import/prefer-default-export": "off",
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/ban-ts-comment": "warn",
"react/jsx-props-no-spreading": "off", // switched off for component building
// TODO: This rule will be switched ON after complete revamp of frontend
"@typescript-eslint/no-explicit-any": "off",
"jsx-a11y/control-has-associated-label": "off",
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: true
}
],
"no-console": "off",
"arrow-body-style": "off",
"no-underscore-dangle": [
"error",
{
allow: ["_id"]
}
],
"jsx-a11y/anchor-is-valid": "off",
// all those <a> tags must be converted to label or a p component
//
"react/require-default-props": "off",
"react/jsx-filename-extension": [
1,
{
extensions: [".tsx", ".ts"]
}
],
// TODO: turn this rule ON after migration. everything should use arrow functions
"react/function-component-definition": [
0,
{
namedComponents: "arrow-function"
}
],
"react/no-unknown-property": [
"error",
{
ignore: ["jsx"]
}
],
"@typescript-eslint/no-non-null-assertion": "off",
"simple-import-sort/exports": "warn",
"simple-import-sort/imports": [
"warn",
{
groups: [
// Node.js builtins. You could also generate this regex if you use a `.js` config.
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
// Note that if you use the `node:` prefix for Node.js builtins,
// you can avoid this complexity: You can simply use "^node:".
[
"^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)"
],
// Packages `react` related packages
["^react", "^next", "^@?\\w"],
["^@app"],
// Internal packages.
["^~(/.*|$)"],
// Relative imports
["^\\.\\.(?!/?$)", "^\\.\\./?$", "^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
// Style imports.
["^.+\\.?(css|scss)$"]
]
}
],
"import/first": "error",
"import/newline-after-import": "error",
"import/no-duplicates": "error"
}
},
{
rules: Object.fromEntries(
Object.keys(stylisticPlugin.configs["all-flat"].rules ?? {}).map((key) => [key, "off"])
)
}
);
+28
View File
@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/infisical.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self';
connect-src 'self' https://*.posthog.com http://127.0.0.1:* https://cdn.jsdelivr.net/npm/@lottiefiles/[email protected]/dist/dotlottie-player.wasm;
script-src 'self' https://*.posthog.com https://js.stripe.com https://api.stripe.com https://widget.intercom.io https://js.intercomcdn.com https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net/npm/@lottiefiles/[email protected]/dist/dotlottie-player.wasm;
style-src 'self' 'unsafe-inline' https://hcaptcha.com https://*.hcaptcha.com;
child-src https://api.stripe.com;
frame-src https://js.stripe.com/ https://api.stripe.com https://www.youtube.com/ https://hcaptcha.com https://*.hcaptcha.com;
connect-src 'self' wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com https://api.pwnedpasswords.com http://127.0.0.1:* https://hcaptcha.com https://*.hcaptcha.com;
img-src 'self' https://static.intercomassets.com https://js.intercomcdn.com https://downloads.intercomcdn.com https://*.stripe.com https://i.ytimg.com/ data:;
media-src https://js.intercomcdn.com;
font-src 'self' https://fonts.intercomcdn.com/ https://fonts.gstatic.com;
"
/>
<title>Infisical</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
-5
View File
@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
-98
View File
@@ -1,98 +0,0 @@
const path = require("path");
const ContentSecurityPolicy = `
default-src 'self';
connect-src 'self' https://*.posthog.com http://127.0.0.1:*;
script-src 'self' https://*.posthog.com https://js.stripe.com https://api.stripe.com https://widget.intercom.io https://js.intercomcdn.com https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' 'unsafe-eval';
style-src 'self' https://rsms.me 'unsafe-inline' https://hcaptcha.com https://*.hcaptcha.com;
child-src https://api.stripe.com;
frame-src https://js.stripe.com/ https://api.stripe.com https://www.youtube.com/ https://hcaptcha.com https://*.hcaptcha.com;
connect-src 'self' wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com https://api.pwnedpasswords.com http://127.0.0.1:* https://hcaptcha.com https://*.hcaptcha.com;
img-src 'self' https://static.intercomassets.com https://js.intercomcdn.com https://downloads.intercomcdn.com https://*.stripe.com https://i.ytimg.com/ data:;
media-src https://js.intercomcdn.com;
font-src 'self' https://fonts.intercomcdn.com/ https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com;
`;
// You can choose which headers to add to the list
// after learning more below.
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
value: "on"
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload"
},
{
key: "X-XSS-Protection",
value: "1; mode=block"
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN"
},
{
key: "Permissions-Policy",
value: "camera=(), microphone=()"
},
{
key: "X-Content-Type-Options",
value: "nosniff"
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin"
},
{
key: "Content-Security-Policy",
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim()
}
];
/**
* @type {import('next').NextConfig}
**/
module.exports = {
output: "standalone",
i18n: {
locales: ["en", "ko", "fr", "pt-BR", "pt-PT", "es"],
defaultLocale: "en"
},
async headers() {
return [
{
// Apply these headers to all routes in your application.
source: "/:path*",
headers: securityHeaders
}
];
},
webpack: (config, { isServer, webpack }) => {
// config
config.module.rules.push({
test: /\.wasm$/,
loader: "base64-loader",
type: "javascript/auto"
});
config.module.noParse = /\.wasm$/;
config.module.rules.forEach((rule) => {
(rule.oneOf || []).forEach((oneOf) => {
if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) {
oneOf.exclude.push(/\.wasm$/);
}
});
});
if (!isServer) {
config.resolve.fallback.fs = false;
}
// Perform customizations to webpack config
config.plugins.push(new webpack.IgnorePlugin({ resourceRegExp: /\/__tests__\// }));
// Important: return the modified config
return config;
}
};
+5456 -18134
View File
File diff suppressed because it is too large Load Diff
+113 -144
View File
@@ -1,165 +1,134 @@
{
"name": "frontend-v2",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"prepare": "cd .. && npm install",
"dev": "next dev",
"build": "next build",
"start": "next start",
"start:docker": "next build && next start",
"lint": "eslint --ext js,ts,tsx ./src",
"lint:fix": "eslint --fix --ext js,ts,tsx ./src",
"type:check": "tsc --project tsconfig.json --noEmit",
"storybook": "storybook dev -p 6006 -s ./public",
"build-storybook": "storybook build"
},
"overrides": {
"@storybook/nextjs": {
"sharp": "npm:dry-uninstall"
}
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint ./src",
"lint:fix": "eslint --fix ./src",
"type:check": "tsc --noEmit --project ./tsconfig.app.json"
},
"dependencies": {
"@casl/ability": "^6.5.0",
"@casl/react": "^3.1.0",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^6.0.1",
"@dnd-kit/sortable": "^7.0.2",
"@emotion/css": "^11.10.0",
"@emotion/server": "^11.10.0",
"@fontsource/inter": "^4.5.15",
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-brands-svg-icons": "^6.1.2",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@hcaptcha/react-hcaptcha": "^1.10.1",
"@headlessui/react": "^1.7.7",
"@hookform/resolvers": "^2.9.10",
"@octokit/rest": "^19.0.7",
"@peculiar/x509": "^1.11.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-popper": "^1.1.3",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
"@reduxjs/toolkit": "^1.8.3",
"@sindresorhus/slugify": "1.1.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",
"@casl/ability": "^6.7.2",
"@casl/react": "^4.0.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@fontsource/inter": "^5.1.0",
"@fortawesome/fontawesome-svg-core": "^6.7.1",
"@fortawesome/free-brands-svg-icons": "^6.7.1",
"@fortawesome/free-regular-svg-icons": "^6.7.1",
"@fortawesome/free-solid-svg-icons": "^6.7.1",
"@fortawesome/react-fontawesome": "^0.2.2",
"@hcaptcha/react-hcaptcha": "^1.11.0",
"@headlessui/react": "^1.7.19",
"@hookform/resolvers": "^3.9.1",
"@lottiefiles/dotlottie-react": "^0.12.0",
"@octokit/rest": "^21.0.2",
"@peculiar/x509": "^1.12.3",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.3",
"@radix-ui/react-dropdown-menu": "^2.1.3",
"@radix-ui/react-hover-card": "^1.1.3",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.3",
"@radix-ui/react-popper": "^1.2.1",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.2",
"@radix-ui/react-select": "^2.1.3",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.3",
"@radix-ui/react-tooltip": "^1.1.5",
"@sindresorhus/slugify": "^2.2.1",
"@tanstack/react-query": "^5.62.7",
"@tanstack/react-router": "^1.87.9",
"@tanstack/virtual-file-routes": "^1.87.6",
"@tanstack/zod-adapter": "^1.91.0",
"@types/nprogress": "^0.2.3",
"@ucast/mongo2js": "^1.3.4",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.28.0",
"axios-auth-refresh": "^3.3.6",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
"cookies": "^0.9.1",
"cva": "npm:class-variance-authority@^0.4.0",
"date-fns": "^2.30.0",
"axios": "^1.7.9",
"classnames": "^2.5.1",
"cva": "npm:class-variance-authority@^0.7.1",
"date-fns": "^4.1.0",
"file-saver": "^2.0.5",
"framer-motion": "^6.2.3",
"fs": "^0.0.2",
"gray-matter": "^4.0.3",
"http-proxy": "^1.18.1",
"i18next": "^22.4.15",
"i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.2.0",
"infisical-node": "^1.0.37",
"framer-motion": "^11.14.1",
"i18next": "^24.1.0",
"i18next-browser-languagedetector": "^8.0.2",
"i18next-http-backend": "^3.0.1",
"jspdf": "^2.5.2",
"jsrp": "^0.2.4",
"jwt-decode": "^3.1.2",
"lottie-react": "^2.4.0",
"markdown-it": "^13.0.1",
"jwt-decode": "^4.0.0",
"ms": "^2.1.3",
"next": "^12.3.4",
"nprogress": "^0.2.0",
"picomatch": "^2.3.1",
"posthog-js": "^1.105.6",
"picomatch": "^4.0.2",
"posthog-js": "^1.198.0",
"qrcode": "^1.5.4",
"query-string": "^7.1.3",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
"react": "^18.3.1",
"react-code-input": "^3.10.1",
"react-day-picker": "^8.8.0",
"react-dom": "^17.0.2",
"react-grid-layout": "^1.3.4",
"react-hook-form": "^7.43.0",
"react-i18next": "^12.2.2",
"react-icons": "^5.3.0",
"react-mailchimp-subscribe": "^2.1.3",
"react-markdown": "^8.0.3",
"react-redux": "^8.0.2",
"react-select": "^5.8.1",
"react-table": "^7.8.0",
"react-toastify": "^9.1.3",
"sanitize-html": "^2.12.1",
"set-cookie-parser": "^2.5.1",
"sharp": "^0.33.2",
"styled-components": "^5.3.7",
"tailwind-merge": "^1.8.1",
"react-day-picker": "^9.4.3",
"react-dom": "^18.3.1",
"react-helmet": "^6.1.0",
"react-hook-form": "^7.54.0",
"react-i18next": "^15.2.0",
"react-icons": "^5.4.0",
"react-select": "^5.9.0",
"react-toastify": "^10.0.6",
"redaxios": "^0.5.1",
"tailwind-merge": "^2.5.5",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"uuid": "^8.3.2",
"uuidv4": "^6.2.13",
"yaml": "^2.2.2",
"yup": "^0.32.11",
"zod": "^3.22.3",
"zustand": "^4.5.0"
"yaml": "^2.6.1",
"zod": "^3.24.1",
"zustand": "^5.0.2"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.5.2",
"@storybook/addon-interactions": "^7.0.23",
"@storybook/addon-links": "^7.0.23",
"@storybook/addon-styling": "^1.3.0",
"@storybook/blocks": "^7.0.23",
"@storybook/client-api": "^7.2.1",
"@storybook/nextjs": "^7.0.23",
"@storybook/react": "^7.0.23",
"@storybook/testing-library": "^0.2.0",
"@tailwindcss/typography": "^0.5.4",
"@types/file-saver": "^2.0.5",
"@types/jsrp": "^0.2.4",
"@types/node": "^18.11.9",
"@types/picomatch": "^2.3.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.15.0",
"@kesills/eslint-config-airbnb-typescript": "^20.0.0",
"@stylistic/eslint-plugin": "^2.12.1",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/eslint-plugin-router": "^1.87.6",
"@tanstack/router-devtools": "^1.87.9",
"@tanstack/router-plugin": "^1.87.11",
"@types/argon2-browser": "^1.18.4",
"@types/file-saver": "^2.0.7",
"@types/jsrp": "^0.2.6",
"@types/ms": "^0.7.34",
"@types/picomatch": "^3.0.1",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.0.26",
"@types/sanitize-html": "^2.9.0",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.7",
"cypress": "^13.3.2",
"eslint": "^8.32.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-helmet": "^6.1.11",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-next": "^13.0.5",
"eslint-config-prettier": "^8.6.0",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.27.4",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^8.0.0",
"eslint-plugin-storybook": "^0.6.12",
"postcss": "^8.4.39",
"prettier": "^2.8.3",
"prettier-plugin-tailwindcss": "^0.2.2",
"storybook": "^7.6.20",
"storybook-dark-mode": "^3.0.0",
"tailwindcss": "3.2",
"typescript": "^4.9.3"
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.14",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.12.0",
"postcss": "^8.4.49",
"prettier": "3.4.2",
"prettier-plugin-tailwindcss": "^0.6.9",
"tailwindcss": "^3.4.16",
"typescript": "~5.6.2",
"typescript-eslint": "^8.15.0",
"vite": "^5.4.11",
"vite-plugin-node-polyfills": "^0.22.0",
"vite-plugin-top-level-await": "^1.4.4",
"vite-plugin-wasm": "^3.3.0",
"vite-tsconfig-paths": "^5.1.4"
}
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
module.exports = {
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
}
-94
View File
@@ -1,94 +0,0 @@
interface Mapping {
[key: string]: string;
}
const integrationSlugNameMapping: Mapping = {
"azure-key-vault": "Azure Key Vault",
"aws-parameter-store": "AWS Parameter Store",
"aws-secret-manager": "AWS Secrets Manager",
heroku: "Heroku",
vercel: "Vercel",
netlify: "Netlify",
github: "GitHub",
gitlab: "GitLab",
render: "Render",
"laravel-forge": "Laravel Forge",
railway: "Railway",
flyio: "Fly.io",
circleci: "CircleCI",
databricks: "Databricks",
travisci: "TravisCI",
supabase: "Supabase",
checkly: "Checkly",
qovery: "Qovery",
"terraform-cloud": "Terraform Cloud",
teamcity: "TeamCity",
"hashicorp-vault": "Vault",
"cloudflare-pages": "Cloudflare Pages",
"cloudflare-workers": "Cloudflare Workers",
codefresh: "Codefresh",
"digital-ocean-app-platform": "Digital Ocean App Platform",
bitbucket: "Bitbucket",
"cloud-66": "Cloud 66",
northflank: "Northflank",
windmill: "Windmill",
"gcp-secret-manager": "GCP Secret Manager",
"hasura-cloud": "Hasura Cloud",
rundeck: "Rundeck",
"azure-devops": "Azure DevOps",
"azure-app-configuration": "Azure App Configuration",
"octopus-deploy": "Octopus Deploy"
};
const envMapping: Mapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test"
};
const reverseEnvMapping: Mapping = {
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing"
};
const contextNetlifyMapping: Mapping = {
dev: "Local development",
"branch-deploy": "Branch deploys",
"deploy-preview": "Deploy Previews",
production: "Production"
};
const reverseContextNetlifyMapping: Mapping = {
"Local development": "dev",
"Branch deploys": "branch-deploy",
"Deploy Previews": "deploy-preview",
Production: "production"
};
const plansDev: Mapping = {
starter: "prod_Mb4ATFT5QAHoPM",
team: "prod_NEpD2WMXUS2eDn",
professional: "prod_Mb4CetZ2jE7jdl",
enterprise: "licence_key_required"
};
const plansProd: Mapping = {
starter: "prod_Mb8oR5XNwyFTul",
team: "prod_NEp7fAB3UJWK6A",
professional: "prod_Mb8pUIpA0OUi5N",
enterprise: "licence_key_required"
};
const plans = plansProd || plansDev;
export {
contextNetlifyMapping,
envMapping,
integrationSlugNameMapping,
plans,
reverseContextNetlifyMapping,
reverseEnvMapping
};
@@ -1,19 +0,0 @@
export interface Tag {
id: string;
name: string;
slug: string;
user: string;
workspace: string;
createdAt: string;
}
export interface SecretDataProps {
pos: number;
key: string;
value: string | undefined;
valueOverride: string | undefined;
id: string;
idOverride?: string;
comment: string;
tags: Tag[];
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-4
View File
@@ -1,4 +0,0 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -10,7 +10,7 @@ fi
echo "Replacing pre-baked value.."
find public .next -type f -name "*.js" |
find assets -type f -name "*.js" |
while read file; do
sed -i "s|$ORIGINAL|$REPLACEMENT|g" "$file"
done
@@ -1,92 +0,0 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, Checkbox, PopoverContent } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { WsTag } from "../../hooks/api/tags/types";
import { ProjectPermissionCan } from "../permissions";
interface Props {
wsTags: WsTag[] | undefined;
secKey: string;
selectedTagIds: Record<string, boolean>;
handleSelectTag: (wsTag: WsTag) => void;
handleTagOnMouseEnter: (wsTag: WsTag) => void;
handleTagOnMouseLeave: () => void;
checkIfTagIsVisible: (wsTag: WsTag) => boolean;
handleOnCreateTagOpen: () => void;
}
const AddTagPopoverContent = ({
wsTags,
secKey,
selectedTagIds,
handleSelectTag,
handleTagOnMouseEnter,
handleTagOnMouseLeave,
checkIfTagIsVisible,
handleOnCreateTagOpen
}: Props) => {
return (
<PopoverContent
side="left"
className="relative max-h-96 w-auto min-w-[200px] overflow-y-auto overflow-x-hidden border border-mineshaft-600 bg-mineshaft-800 p-2 text-bunker-200"
hideCloseBtn
>
<div className=" text-center text-sm font-medium text-bunker-200">
Add tags to {secKey || "this secret"}
</div>
<div className="absolute left-0 mt-2 w-full border-t border-mineshaft-600" />
<div className="flex flex-col space-y-1.5">
{wsTags?.map((wsTag: WsTag) => (
<div
key={`tag-${wsTag.id}`}
className="relative mt-4 flex h-[32px] items-center justify-start rounded-md bg-none p-2 hover:border hover:border-mineshaft-600 hover:bg-mineshaft-700 hover:text-bunker-200"
onClick={() => handleSelectTag(wsTag)}
onMouseEnter={() => handleTagOnMouseEnter(wsTag)}
onMouseLeave={() => handleTagOnMouseLeave()}
tabIndex={0}
role="button"
onKeyDown={() => {}}
>
{(checkIfTagIsVisible(wsTag) || selectedTagIds?.[wsTag.slug]) && (
<Checkbox
id="autoCapitalization"
isChecked={selectedTagIds?.[wsTag.slug]}
className="absolute top-[50%] left-[10px] translate-y-[-50%] "
checkIndicatorBg={`${
!selectedTagIds?.[wsTag.slug] ? "text-transparent" : "text-mineshaft-800"
}`}
/>
)}
<div className="ml-7 flex items-center gap-3">
<div
className="h-[10px] w-[10px] rounded-full"
style={{ background: wsTag?.color ? wsTag.color : "#bec2c8" }}
>
{" "}
</div>
<span>{wsTag.slug}</span>
</div>
</div>
))}
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Tags}>
{(isAllowed) => (
<Button
onClick={() => handleOnCreateTagOpen()}
isDisabled={!isAllowed}
size="xs"
className="mt-2"
leftIcon={<FontAwesomeIcon icon={faPlus} className="ml-1 mr-2" />}
>
Add new tag
</Button>
)}
</ProjectPermissionCan>
</div>
</PopoverContent>
);
};
export default AddTagPopoverContent;
-72
View File
@@ -1,72 +0,0 @@
import { ReactNode, useEffect, useState } from "react";
import { useRouter } from "next/router";
import { publicPaths } from "@app/const";
import checkAuth from "@app/pages/api/auth/CheckAuth";
// #TODO: finish spinner only when the data loads fully
// #TODO: Redirect somewhere if the page does not exist
type Prop = {
children: ReactNode;
};
export default function RouteGuard({ children }: Prop): JSX.Element {
const router = useRouter();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [authorized, setAuthorized] = useState(false);
/**
* redirect to login page if accessing a private page and not logged in
*/
async function authCheck(url: string) {
// Make sure that we don't redirect when the user is on the following pages.
const path = `/${url.split("?")[0].split("/")[1]}`;
// Check if the user is authenticated
const response = await checkAuth();
// #TODO: figure our why sometimes it doesn't output a response
// ANS(akhilmhdh): Because inside the security client the await token() doesn't have try/catch
if (!publicPaths.includes(path)) {
try {
if (response.status !== 200) {
router.push("/login");
console.log("Unauthorized to access.");
setAuthorized(false);
} else {
setAuthorized(true);
console.log("Authorized to access.");
}
} catch (error) {
console.log("Error (probably the authCheck route is stuck again...):", error);
}
}
}
useEffect(() => {
// on initial load - run auth check
(async () => {
await authCheck(router.asPath);
})();
// on route change start - hide page content by setting authorized to false
// #TODO: add the loading page when not yet authorized.
const hideContent = () => setAuthorized(false);
// const onError = () => setAuthorized(true)
router.events.on("routeChangeStart", hideContent);
// router.events.on("routeChangeError", onError);
// on route change complete - run auth check
router.events.on("routeChangeComplete", authCheck);
// unsubscribe from events in useEffect return function
return () => {
router.events.off("routeChangeStart", hideContent);
router.events.off("routeChangeComplete", authCheck);
// router.events.off("routeChangeError", onError);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return children as JSX.Element;
}
@@ -1,5 +1,5 @@
/* eslint-disable react/jsx-props-no-spreading */
import React, { useState } from "react";
import { useState } from "react";
import ReactCodeInput from "react-code-input";
import { useTranslation } from "react-i18next";
@@ -97,7 +97,7 @@ export default function CodeInputStep({
fields={6}
onChange={setCode}
{...props}
className="mt-6 mb-2"
className="mb-2 mt-6"
/>
</div>
<div className="mx-auto mt-4 block w-max md:hidden">
@@ -108,7 +108,7 @@ export default function CodeInputStep({
fields={6}
onChange={setCode}
{...propsPhone}
className="mt-2 mb-2"
className="mb-2 mt-2"
/>
</div>
{codeError && <Error text={t("signup.step2-code-error")} />}
@@ -32,7 +32,7 @@ export default function DonwloadBackupPDFStep({
name
}: DownloadBackupPDFStepProps): JSX.Element {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useToggle();
const handleBackupKeyGenerate = async () => {
@@ -61,17 +61,17 @@ export default function DonwloadBackupPDFStep({
<p className="flex flex-col items-center justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
<FontAwesomeIcon
icon={faWarning}
className="ml-2 mr-3 mb-6 pt-1 text-6xl text-bunker-200"
className="mb-6 ml-2 mr-3 pt-1 text-6xl text-bunker-200"
/>
{t("signup.step4-message")}
</p>
<div className="text-md mt-8 flex w-full max-w-md flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 pb-2 text-center text-bunker-300 md:min-w-[24rem] lg:w-1/6">
<div className="m-2 mx-auto mt-4 flex w-full w-full flex-row items-center rounded-md px-3 text-center text-bunker-300 md:mt-8 md:min-w-[23rem] lg:w-1/6 lg:w-1/6">
<div className="m-2 mx-auto mt-4 flex w-full flex-row items-center rounded-md px-3 text-center text-bunker-300 md:mt-8 md:min-w-[23rem] lg:w-1/6">
<span className="mb-2">
{t("signup.step4-description1")} {t("signup.step4-description3")}
</span>
</div>
<div className="mx-auto mt-0 mb-2 mt-2 flex w-full flex-col items-center justify-center px-3 text-center text-sm md:mt-4 md:mb-4 md:min-w-[20rem] md:max-w-md md:text-left lg:w-1/6">
<div className="mx-auto mb-2 mt-2 flex w-full flex-col items-center justify-center px-3 text-center text-sm md:mb-4 md:mt-4 md:min-w-[20rem] md:max-w-md md:text-left lg:w-1/6">
<div className="text-l w-full py-1 text-lg">
<Button
onClick={handleBackupKeyGenerate}
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import Link from "next/link";
import { Link } from "@tanstack/react-router";
import axios from "axios";
import { createNotification } from "@app/components/notifications";
@@ -27,8 +27,7 @@ export default function EnterEmailStep({
setEmail,
incrementStep
}: DownloadBackupPDFStepProps): JSX.Element {
const { mutateAsync, isLoading } = useSendVerificationEmail();
const { mutateAsync, isPending } = useSendVerificationEmail();
const [emailError, setEmailError] = useState(false);
const { t } = useTranslation();
@@ -51,7 +50,7 @@ export default function EnterEmailStep({
if (!emailCheckBool) {
try {
await mutateAsync({ email: email.toLowerCase() });
setEmail(email.toLowerCase())
setEmail(email.toLowerCase());
incrementStep();
} catch (e) {
if (axios.isAxiosError(e)) {
@@ -96,8 +95,8 @@ export default function EnterEmailStep({
className="h-14"
colorSchema="primary"
variant="outline_bg"
isLoading={isLoading}
isDisabled={isLoading}
isLoading={isPending}
isDisabled={isPending}
>
{" "}
{String(t("signup.step1-submit"))}{" "}
@@ -106,7 +105,7 @@ export default function EnterEmailStep({
</div>
</div>
<div className="mx-auto mb-48 mt-2 flex w-full max-w-md flex-col items-center justify-center pt-2 md:mb-16 md:pb-2">
<Link href="/login">
<Link to="/login">
<button type="button" className="w-max pb-3 duration-200 hover:opacity-90">
<span className="cursor-pointer text-sm text-mineshaft-400 duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
{t("signup.already-have-account")}
@@ -1,8 +1,8 @@
import { useTranslation } from "react-i18next";
import Link from "next/link";
import { faGithub, faGitlab, faGoogle } from "@fortawesome/free-brands-svg-icons";
import { faEnvelope } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "@tanstack/react-router";
import { RegionSelect } from "@app/components/navigation/RegionSelect";
import { useServerConfig } from "@app/context";
@@ -94,7 +94,7 @@ export default function InitialSignupStep({
{t("signup.create-policy")}
</div>
<div className="mt-2 flex flex-row text-xs text-bunker-400">
<Link href="/login">
<Link to="/login">
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
{t("signup.already-have-account")}
</span>
@@ -1,8 +1,6 @@
import React, { useEffect, useState } from "react";
import ReactCodeInput from "react-code-input";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { Link, useNavigate } from "@tanstack/react-router";
import { t } from "i18next";
import Error from "@app/components/basic/Error";
@@ -43,7 +41,7 @@ type Props = {
export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Props) => {
const [mfaCode, setMfaCode] = useState("");
const router = useRouter();
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(false);
const [isLoadingResend, setIsLoadingResend] = useState(false);
const [triesLeft, setTriesLeft] = useState<number | undefined>(undefined);
@@ -80,11 +78,11 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
if (closeMfa) {
closeMfa();
}
} catch (error) {
} catch {
if (triesLeft) {
setTriesLeft((left) => {
if (triesLeft === 1) {
router.push("/");
navigate({ to: "/" });
SecurityClient.setMfaToken("");
SecurityClient.setToken("");
@@ -134,7 +132,7 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
{!hideLogo && (
<Link href="/">
<div className="mb-4 flex justify-center">
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
<img src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
</div>
</Link>
)}
@@ -164,12 +162,12 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
type="text"
fields={6}
onChange={setMfaCode}
className="mt-6 mb-2"
className="mb-2 mt-6"
{...codeInputProps}
/>
)}
{method === MfaMethod.TOTP && (
<div className="mt-6 mb-4">
<div className="mb-4 mt-6">
<Input value={mfaCode} onChange={(e) => setMfaCode(e.target.value)} />
</div>
)}
@@ -194,7 +192,7 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
</div>
</form>
{method === MfaMethod.TOTP && (
<div className="mt-2 flex flex-row justify-center text-sm text-bunker-400 ">
<div className="mt-2 flex flex-row justify-center text-sm text-bunker-400">
<Link href="/verify-email">
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
Lost your recovery codes? Reset your account
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useRouter } from "next/router";
import { useNavigate } from "@tanstack/react-router";
import { useAddUsersToOrg } from "@app/hooks/api";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
@@ -14,7 +14,7 @@ import { Button, EmailServiceSetupModal } from "../v2";
*/
export default function TeamInviteStep(): JSX.Element {
const { t } = useTranslation();
const router = useRouter();
const navigate = useNavigate();
const [emails, setEmails] = useState("");
const { data: serverDetails } = useFetchServerStatus();
@@ -23,7 +23,7 @@ export default function TeamInviteStep(): JSX.Element {
// Redirect user to the getting started page
const redirectToHome = async () => {
router.push(`/org/${localStorage.getItem("orgData.id")}/${ProjectType.SecretManager}/overview`);
navigate({ to: `/organization/${ProjectType.SecretManager}/overview` as const });
};
const inviteUsers = async ({ emails: inviteEmails }: { emails: string }) => {
@@ -49,19 +49,19 @@ export default function TeamInviteStep(): JSX.Element {
<p className="mb-6 mt-4 flex justify-center text-center text-bunker-400 md:mx-8">
{t("signup.step5-subtitle")}
</p>
<div className="mx-auto mb-6 w-max rounded-xl border border-mineshaft-500 bg-mineshaft-800 px-8 pt-6 pb-4 drop-shadow-xl">
<div className="mx-auto mb-6 w-max rounded-xl border border-mineshaft-500 bg-mineshaft-800 px-8 pb-4 pt-6 drop-shadow-xl">
<div>
<div className="pl-1 pb-1 text-sm font-medium text-bunker-300">
<div className="pb-1 pl-1 text-sm font-medium text-bunker-300">
<span>Emails</span>
</div>
<textarea
className="h-20 w-full min-w-[30rem] rounded-md border border-mineshaft-500 bg-mineshaft-900/70 py-1 px-2 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2"
className="h-20 w-full min-w-[30rem] rounded-md border border-mineshaft-500 bg-mineshaft-900/70 px-2 py-1 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2"
value={emails}
onChange={(e) => setEmails(e.target.value)}
placeholder="[email protected], [email protected]..."
/>
</div>
<div className="mx-auto mt-2 flex w-full flex-row items-end justify-end text-sm md:mt-4 md:mb-2 md:min-w-[30rem] md:max-w-md">
<div className="mx-auto mt-2 flex w-full flex-row items-end justify-end text-sm md:mb-2 md:mt-4 md:min-w-[30rem] md:max-w-md">
<Button
onClick={() => {
if (serverDetails?.emailConfigured) {
@@ -85,7 +85,7 @@ export default function TeamInviteStep(): JSX.Element {
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
/>
</div>
<div className="min-w-28 mx-auto mt-4 mb-2 flex max-h-24 min-w-[20rem] max-w-max flex-row items-center justify-center px-4 text-lg md:p-2">
<div className="mx-auto mb-2 mt-4 flex max-h-24 min-w-[20rem] max-w-max flex-row items-center justify-center px-4 text-lg md:p-2">
<Button
onClick={redirectToHome}
size="sm"
@@ -1,6 +1,6 @@
import crypto from "crypto";
import React, { useState } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -8,9 +8,9 @@ import jsrp from "jsrp";
import nacl from "tweetnacl";
import { encodeBase64 } from "tweetnacl-util";
import { initProjectHelper } from "@app/helpers/project";
import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import ProjectService from "@app/services/ProjectService";
import InputField from "../basic/InputField";
import checkPassword from "../utilities/checks/password/checkPassword";
@@ -121,7 +121,7 @@ export default function UserInfoStep({
password
},
async () => {
client.createVerifier(async (err: any, result: { salt: string; verifier: string }) => {
client.createVerifier(async (_err: any, result: { salt: string; verifier: string }) => {
try {
// TODO: moduralize into KeyService
const derivedKey = await deriveArgonKey({
@@ -198,12 +198,11 @@ export default function UserInfoStep({
const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]?.id;
const project = await ProjectService.initProject({
await initProjectHelper({
projectName: "Example Project"
});
localStorage.setItem("orgData.id", orgId);
localStorage.setItem("projectData.id", project.id);
incrementStep();
} catch (error) {
@@ -224,7 +223,7 @@ export default function UserInfoStep({
{t("signup.step3-message")}
</p>
<div className="mx-auto mb-36 h-full w-max rounded-xl py-6 md:mb-16 md:border md:border-mineshaft-600 md:bg-mineshaft-800 md:px-8">
<div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<div className="relative z-0 flex w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
Your Name
</p>
@@ -237,12 +236,12 @@ export default function UserInfoStep({
className="h-12"
/>
{nameError && (
<p className="mt-1 ml-1 w-full text-left text-xs text-red-600">
<p className="ml-1 mt-1 w-full text-left text-xs text-red-600">
Please, specify your name
</p>
)}
</div>
<div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<div className="relative z-0 flex w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
Organization Name
</p>
@@ -255,12 +254,12 @@ export default function UserInfoStep({
className="h-12"
/>
{organizationNameError && (
<p className="mt-1 ml-1 w-full text-left text-xs text-red-600">
<p className="ml-1 mt-1 w-full text-left text-xs text-red-600">
Please, specify your organization name
</p>
)}
</div>
<div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<div className="relative z-0 flex w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
Where did you hear about us? <span className="font-light">(optional)</span>
</p>
@@ -271,7 +270,7 @@ export default function UserInfoStep({
className="h-12"
/>
</div>
<div className="mt-2 flex max-h-60 w-1/4 w-full min-w-[20rem] flex-col items-center justify-center rounded-lg py-2 lg:w-1/6">
<div className="mt-2 flex max-h-60 w-full min-w-[20rem] flex-col items-center justify-center rounded-lg py-2 lg:w-1/6">
<InputField
label={t("section.password.password")}
onChangeHandler={async (pass: string) => {
+1 -1
View File
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const Error = ({ text }: { text: string }): JSX.Element => {
return (
<div className="relative m-auto flex w-fit flex-row items-center justify-center rounded-full">
<FontAwesomeIcon icon={faExclamationTriangle} className="mx-2 mt-1.5 mb-2 text-red" />
<FontAwesomeIcon icon={faExclamationTriangle} className="mx-2 mb-2 mt-1.5 text-red" />
{text && <p className="relative top-0 mr-2 py-1 text-sm text-red">{text}</p>}
</div>
);
+3 -3
View File
@@ -48,7 +48,7 @@ const InputField = ({
placeholder={placeholder}
value={value}
required={isRequired}
className="text-md min-w-16 w-full rounded-md border border-gray-600 bg-bunker-800 p-2 text-gray-400 outline-none"
className="text-md w-full min-w-16 rounded-md border border-gray-600 bg-bunker-800 p-2 text-gray-400 outline-none"
name={name}
readOnly
autoComplete={autoComplete}
@@ -79,7 +79,7 @@ const InputField = ({
: ""
} ${
error ? "focus:ring-red/50" : "focus:ring-primary/50"
} text-md min-w-16 peer relative w-full rounded-md bg-mineshaft-900 p-2 text-gray-400 outline-none duration-200 focus:ring-4`}
} text-md peer relative w-full min-w-16 rounded-md bg-mineshaft-900 p-2 text-gray-400 outline-none duration-200 focus:ring-4`}
name={name}
spellCheck="false"
autoComplete={autoComplete}
@@ -121,7 +121,7 @@ const InputField = ({
</div>
)} */}
</div>
{error && <p className="mx-0 mt-0.5 mb-2 max-w-xs text-xs text-red">{errorText}</p>}
{error && <p className="mx-0 mb-2 mt-0.5 max-w-xs text-xs text-red">{errorText}</p>}
</div>
);
};
-112
View File
@@ -1,112 +0,0 @@
import React, { Fragment } from "react";
import { faAngleDown, faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Listbox, Transition } from "@headlessui/react";
interface ListBoxProps {
isSelected: string;
onChange: (arg: string) => void;
data: string[] | null;
text?: string;
buttonAction?: () => void;
isFull?: boolean;
}
/**
* This is the component that we use for drop down lists.
* @param {object} obj
* @param {string} obj.isSelected - the item that is currently selected
* @param {function} obj.onChange - what happends if you select the item inside a list
* @param {string[]} obj.data - all the options available
* @param {string} obj.text - the text that shows us in front of the select option
* @param {function} obj.buttonAction - if there is a button at the bottom of the list, this is the action that happens when you click the button
* @returns
*/
const ListBox = ({
isSelected,
onChange,
data,
text,
buttonAction,
isFull
}: ListBoxProps): JSX.Element => {
return (
<Listbox value={isSelected} onChange={onChange}>
<div className="relative">
<Listbox.Button
className={`relative text-gray-400 ${
isFull ? "w-full" : "w-52"
} focus-visible:ring-offset-orange-300 cursor-default rounded-md bg-white/[0.07] py-2.5 pl-3 pr-10 text-left shadow-md duration-200 hover:bg-white/[0.11] focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 sm:text-sm`}
>
<div className="flex flex-row">
{text}
<span className="ml-1 block cursor-pointer truncate font-semibold text-gray-300">
{" "}
{isSelected}
</span>
</div>
{data && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex cursor-pointer items-center pr-2">
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" />
</div>
)}
</Listbox.Button>
{data && (
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="no-scrollbar::-webkit-scrollbar absolute z-[70] mt-1 max-h-60 w-full overflow-auto rounded-md border border-mineshaft-700 bg-bunker p-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 no-scrollbar focus:outline-none sm:text-sm">
{data.map((person, personIdx) => (
<Listbox.Option
key={`${person}.${personIdx + 1}`}
className={({ active, selected }) =>
`relative my-0.5 cursor-default select-none rounded-md py-2 pl-10 pr-4 ${
selected ? "bg-white/10 font-bold text-gray-400" : ""
} ${
active && !selected
? "cursor-pointer bg-white/5 text-mineshaft-200"
: "text-gray-400"
} `
}
value={person}
>
{({ selected }) => (
<>
<span
className={`block truncate text-primary${
selected ? "font-medium" : "font-normal"
}`}
>
{person}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 text-primary">
<FontAwesomeIcon icon={faCheck} className="text-md ml-1" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
{buttonAction && (
<button type="button" onClick={buttonAction} className="w-full cursor-pointer">
<div className="relative my-0.5 mt-2 flex cursor-pointer select-none justify-start rounded-md py-2 pl-10 pr-4 text-gray-400 duration-200 hover:bg-lime-300 hover:font-semibold hover:text-black">
<span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 pr-4">
<FontAwesomeIcon icon={faPlus} className="text-lg" />
</span>
Add Project
</div>
</button>
)}
</Listbox.Options>
</Transition>
)}
</div>
</Listbox>
);
};
export default ListBox;
-45
View File
@@ -1,45 +0,0 @@
import { Switch } from "@headlessui/react";
interface ToggleProps {
enabled: boolean;
setEnabled: (value: boolean) => void;
addOverride: (value: string | undefined, id: string) => void;
id: string;
}
/**
* This is a typical 'iPhone' toggle (e.g., user for overriding secrets with personal values)
* @param obj
* @param {boolean} obj.enabled - whether the toggle is turned on or off
* @param {function} obj.setEnabled - change the state of the toggle
* @param {function} obj.addOverride - a function that adds an override to a certain secret
* @param {number} obj.id - id of a certain secret
* @returns
*/
const Toggle = ({ enabled, setEnabled, addOverride, id }: ToggleProps): JSX.Element => {
return (
<Switch
checked={enabled}
onChange={() => {
if (enabled === false) {
addOverride("", id);
} else {
addOverride(undefined, id);
}
setEnabled(!enabled);
}}
className={`${
enabled ? "bg-primary" : "bg-bunker-400"
} relative inline-flex h-5 w-9 items-center rounded-full`}
>
<span className="sr-only">Enable notifications</span>
<span
className={`${
enabled ? "translate-x-[1.26rem]" : "translate-x-0.5"
} inline-block h-3.5 w-3.5 transform rounded-full bg-bunker-800 transition`}
/>
</Switch>
);
};
export default Toggle;
@@ -1,148 +0,0 @@
/* eslint-disable react/button-has-type */
import React, { ButtonHTMLAttributes } from "react";
import Image from "next/image";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const classNames = require("classnames");
type ButtonProps = {
text?: string;
onButtonPressed: () => void;
loading?: boolean;
color?: string;
size: string;
icon?: IconProp;
active?: boolean;
iconDisabled?: IconProp;
textDisabled?: string;
type?: ButtonHTMLAttributes<any>["type"];
};
/**
* This is the main butto component in the app.
* @param {object} props
* @param {string} props.text - text inside the button
* @param {function} props.onButtonPressed - the action that happens when the button is clicked
* @param {boolean} props.loading - if a button is currently in the laoding state
* @param {string} props.color - button color
* @param {string} props.size - button size
* @param {FontAwesomeIconProps} props.icon - the icon inside the button
* @param {boolean} props.active - if the button is active or disabled
* @param {FontAwesomeIconProps} props.text - the icon inside the button when it is disabled
* @param {string} props.textDisable - text inside the button when it is disabled
* @returns
*/
const Button = ({
active,
text,
textDisabled,
color,
size,
onButtonPressed,
loading,
icon,
iconDisabled,
type = "button"
}: ButtonProps): JSX.Element => {
// Check if the button show always be 'active' - then true;
// or if it should switch between 'active' and 'disabled' - then give the status
const activityStatus = active || (text !== "" && textDisabled === undefined);
const styleButton = classNames(
"group m-auto md:m-0 inline-block rounded-md duration-200",
// Setting background colors and hover modes
color === "mineshaft" &&
activityStatus &&
"bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60",
color === "mineshaft" && !activityStatus && "bg-mineshaft",
(color === "primary" || !color) &&
activityStatus &&
"bg-primary border border-primary-400 opacity-80 hover:opacity-100",
(color === "primary" || !color) && !activityStatus && "bg-primary",
color === "red" && "bg-red-800 border border-red",
// Changing the opacity when active vs when not
activityStatus ? "opacity-100 cursor-pointer" : "opacity-40",
// Setting the button sizes
size === "md" && "h-10 w-full px-2 md:px-4",
size === "lg" && "h-12 w-full px-2 md:px-8",
!size && "md:py-1 px-3 md:px-8",
size === "icon-md" && "h-10 w-10 flex items-center justify-center",
size === "icon-sm" && "h-9 w-9 flex items-center justify-center"
);
const styleMainDiv = classNames(
"relative font-medium flex items-center",
// Setting the text color for the text and icon
color === "mineshaft" && "text-bunker-200",
color !== "mineshaft" && color !== "red" && color !== "none" && "text-black",
color === "red" && "text-gray-200",
color === "none" && "text-gray-200 text-xl",
activityStatus && color !== "red" && color !== "mineshaft" && color !== "none"
? "group-hover:text-black"
: "",
size === "icon" && "flex items-center justify-center"
);
const textStyle = classNames(
"relative duration-200 text-center w-full",
// Show the loading sign if the loading indicator is on
loading ? "opacity-0" : "opacity-100",
size === "md" && "text-sm",
size === "lg" && "text-lg"
);
const button = (
<button
disabled={!activityStatus}
type={type}
onClick={onButtonPressed}
className={styleButton}
>
<div className={styleMainDiv}>
<div
className={`${
loading === true ? "opacity-100" : "opacity-0"
} absolute flex w-full items-center bg-primary px-3 duration-200`}
>
<Image
src="/images/loading/loadingblack.gif"
height={25}
width={42}
alt="loading animation"
className="rounded-xl"
/>
</div>
{icon && (
<FontAwesomeIcon
icon={icon}
className={`my-auto flex font-extrabold ${size === "icon-sm" ? "text-sm" : "text-sm"} ${
(text || textDisabled) && "mr-2"
}`}
/>
)}
{iconDisabled && (
<FontAwesomeIcon
icon={iconDisabled as IconProp}
className={`my-auto flex font-extrabold ${size === "icon-sm" ? "text-sm" : "text-md"} ${
(text || textDisabled) && "mr-2"
}`}
/>
)}
{(text || textDisabled) && (
<p className={textStyle}>{activityStatus ? text : textDisabled}</p>
)}
</div>
</button>
);
return button;
};
export default Button;
@@ -1,100 +0,0 @@
import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import { Dialog, Transition } from "@headlessui/react";
import Button from "../buttons/Button";
interface IntegrationOption {
clientId: string;
clientSlug?: string; // vercel-integration specific
docsLink: string;
image: string;
isAvailable: boolean;
name: string;
slug: string;
type: string;
}
type Props = {
isOpen: boolean;
closeModal: () => void;
selectedIntegrationOption: IntegrationOption | null;
integrationOptionPress: (integrationOption: IntegrationOption) => void;
};
const ActivateBotDialog = ({
isOpen,
closeModal,
selectedIntegrationOption,
integrationOptionPress
}: Props) => {
const { t } = useTranslation();
const submit = async () => {
try {
// type check
if (!selectedIntegrationOption) return;
// start integration or probe for PAT
integrationOptionPress(selectedIntegrationOption);
} catch (err) {
console.log(err);
}
closeModal();
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
{t("integrations.grant-access-to-secrets")}
</Dialog.Title>
<div className="mt-2 mb-2">
<p className="text-sm text-gray-500">
{t("integrations.why-infisical-needs-access")}
</p>
</div>
<div className="mt-6 max-w-max">
<Button
onButtonPressed={submit}
color="mineshaft"
text={t("integrations.grant-access-button") as string}
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default ActivateBotDialog;
@@ -1,149 +0,0 @@
import { Fragment } from "react";
import { Trans, useTranslation } from "react-i18next";
import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
import Button from "../buttons/Button";
import ListBox from "../Listbox";
type Props = {
isOpen: boolean;
closeModal: () => void;
submitModal: () => void;
data: any;
email: string;
setEmail: (email: string) => void;
};
const AddProjectMemberDialog = ({
isOpen,
closeModal,
submitModal,
data,
email,
setEmail
}: Props) => {
const router = useRouter();
const { t } = useTranslation();
return (
<div className="z-50">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6 text-left align-middle shadow-xl transition-all">
{data?.length > 0 ? (
<Dialog.Title
as="h3"
className="z-50 text-lg font-medium leading-6 text-gray-400"
>
{t("section.members.add-dialog.add-member-to-project")}
</Dialog.Title>
) : (
<Dialog.Title
as="h3"
className="z-50 mb-4 text-lg font-medium text-mineshaft-300"
>
{t("section.members.add-dialog.already-all-invited")}
</Dialog.Title>
)}
<div className="mt-2 mb-4">
{data?.length > 0 ? (
<div className="flex flex-col">
<p className="text-sm text-gray-500">
{t("section.members.add-dialog.user-will-email")}
</p>
<div className="">
<Trans
i18nKey="section.members.add-dialog.looking-add"
components={[
// eslint-disable-next-line react/jsx-key
<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"
onClick={() => router.push(`/org/${router.query.id}/members`)}
aria-label="add member"
/>,
// eslint-disable-next-line react/jsx-key
<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={() =>
router.push(`/org/${router.query.id}/members?action=invite`)
}
aria-label="add member"
/>
]}
/>
</div>
</div>
) : (
<p className="text-sm text-gray-500">
{t("section.members.add-dialog.add-user-org-first")}
</p>
)}
</div>
<div className="max-h-28">
{data?.length > 0 && (
<ListBox
isSelected={email || data[0]}
onChange={setEmail}
data={data}
isFull
/>
)}
</div>
<div className="max-w-max">
{data?.length > 0 ? (
<div className="mt-6 flex w-max flex-col justify-start">
<Button
onButtonPressed={submitModal}
color="mineshaft"
text={t("section.members.add-member") as string}
size="md"
/>
</div>
) : (
<Button
onButtonPressed={() =>
router.push(`/org/${localStorage.getItem("orgData.id")}/members`)
}
color="mineshaft"
text={t("section.members.add-dialog.add-user-to-org") as string}
size="md"
/>
)}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddProjectMemberDialog;
@@ -1,140 +0,0 @@
import { FormEventHandler, Fragment, useEffect, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import Button from "../buttons/Button";
import InputField from "../InputField";
type FormFields = { name: string; slug: string };
type Props = {
isOpen?: boolean;
isEditMode?: boolean;
// on edit mode load up initial values
initialValues?: FormFields;
onClose: () => void;
onCreateSubmit: (data: FormFields) => void;
onEditSubmit: (data: FormFields) => void;
};
// TODO: Migrate to better form management and validation. Preferable react-hook-form + yup
/**
* The dialog modal for when the user wants to create a new workspace
* @param {*} param0
* @returns
*/
export const AddUpdateEnvironmentDialog = ({
isOpen,
onClose,
onCreateSubmit,
onEditSubmit,
initialValues,
isEditMode
}: Props) => {
const [formInput, setFormInput] = useState<FormFields>({
name: "",
slug: ""
});
// This use effect can be removed when the unmount is happening from outside the component
// When unmount happens outside state gets unmounted also
useEffect(() => {
setFormInput(initialValues || { name: "", slug: "" });
}, [isOpen]);
// REFACTOR: Move to react-hook-form with yup for better form management
const onInputChange = (fieldName: string, fieldValue: string) => {
setFormInput((state) => ({ ...state, [fieldName]: fieldValue }));
};
const onFormSubmit: FormEventHandler = (e) => {
e.preventDefault();
const data = {
name: formInput.name,
slug: formInput.slug.toLowerCase()
};
if (isEditMode) {
onEditSubmit(data);
return;
}
onCreateSubmit(data);
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-out duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
{isEditMode ? "Update environment" : "Create a new environment"}
</Dialog.Title>
<form onSubmit={onFormSubmit}>
<div className="mt-4 max-h-28">
<InputField
label="Environment Name"
onChangeHandler={(val) => onInputChange("name", val)}
type="varName"
value={formInput.name}
placeholder=""
isRequired
// error={error.length > 0}
// errorText={error}
/>
</div>
<div className="mt-4 max-h-28">
<InputField
label="Environment Slug"
onChangeHandler={(val) => onInputChange("slug", val)}
type="varName"
value={formInput.slug}
placeholder=""
isRequired
// error={error.length > 0}
// errorText={error}
/>
</div>
<p className="mt-2 text-xs text-gray-500">
Slugs are shorthands used in cli to access environment
</p>
<div className="mt-4 max-w-min">
<Button
onButtonPressed={() => null}
type="submit"
color="mineshaft"
text={isEditMode ? "Update" : "Create"}
active={formInput.name !== "" && formInput.slug !== ""}
size="md"
/>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
@@ -1,104 +0,0 @@
import { Fragment, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import Button from "../buttons/Button";
import InputField from "../InputField";
import { Checkbox } from "../table/Checkbox";
type Props = {
isOpen: boolean;
closeModal: () => void;
submitModal: (email: string, addAllUser: boolean) => void;
workspaceName: string;
setWorkspaceName: (workspaceName: string) => void;
error: boolean;
loading: boolean;
};
/**
* The dialog modal for when the user wants to create a new workspace
*/
const AddWorkspaceDialog = ({
isOpen,
closeModal,
submitModal,
workspaceName,
setWorkspaceName,
error,
loading
}: Props) => {
const [addAllUsers, setAddAllUsers] = useState(true);
const submit = () => {
submitModal(workspaceName, addAllUsers);
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-[100]" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
Create a new project
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
This project will contain your secrets and configs.
</p>
</div>
<div className="mt-4 max-h-28">
<InputField
label="Project Name"
onChangeHandler={setWorkspaceName}
type="varName"
value={workspaceName}
placeholder=""
isRequired
error={error}
/>
</div>
<div className="mt-4 ml-1">
<Checkbox addAllUsers={addAllUsers} setAddAllUsers={setAddAllUsers} />
</div>
<div className="mt-4 max-w-min">
<Button
onButtonPressed={submit}
loading={loading}
color="mineshaft"
text="Create"
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddWorkspaceDialog;
@@ -1,91 +0,0 @@
import { Fragment, useEffect, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import InputField from "../InputField";
// REFACTOR: Move all these modals into one reusable one
type Props = {
isOpen?: boolean;
onClose: () => void;
title: string;
onSubmit: () => void;
deleteKey?: string;
};
const DeleteActionModal = ({ isOpen, onClose, title, onSubmit, deleteKey }: Props) => {
const [deleteInputField, setDeleteInputField] = useState("");
useEffect(() => {
setDeleteInputField("");
}, [isOpen]);
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-grey p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
{title}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">This action is irrevertible.</p>
</div>
<div className="mt-2">
<InputField
isRequired
label={`Type ${deleteKey} to delete the resource`}
onChangeHandler={(val) => setDeleteInputField(val)}
value={deleteInputField}
type="text"
/>
</div>
<div className="mt-6">
<button
type="button"
className="hover:bg-alizarin hover:text-semibold inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit}
disabled={Boolean(deleteKey) && deleteInputField !== deleteKey}
>
Delete
</button>
<button
type="button"
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:border-white hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onClose}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default DeleteActionModal;
@@ -1,81 +0,0 @@
import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import { Dialog, Transition } from "@headlessui/react";
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
type Props = {
isOpen: boolean;
onClose: () => void;
onSubmit: () => void;
};
export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
const { t } = useTranslation();
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-[80]" onClose={() => {}}>
<div className="fixed inset-0 overflow-y-auto">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
className="fixed inset-0 bg-black bg-opacity-70"
onClick={onClose}
onKeyDown={onClose}
role="button"
tabIndex={0}
aria-label="Close"
/>
</Transition.Child>
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-mineshaft-600 bg-bunker p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-bunker-200">
{t("dashboard:sidebar.delete-key-dialog.title")}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-bunker-300">
{t("dashboard:sidebar.delete-key-dialog.confirm-delete-message")}
</p>
</div>
<div className="mt-6 flex justify-start">
<button
type="button"
className="text-semibold inline-flex justify-center rounded-md border border-transparent bg-red-500 px-4 py-2 text-sm font-medium text-bunker-100 opacity-80 duration-200 hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit}
>
Delete
</button>
<button
type="button"
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-bunker-500 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:bg-mineshaft-500 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onClose}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
@@ -1,77 +0,0 @@
import { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
type Props = {
isOpen: boolean;
closeModal: () => void;
submitModal: (userIdToBeDeleted: string) => void;
userIdToBeDeleted: string;
};
const DeleteUserDialog = ({ isOpen, closeModal, submitModal, userIdToBeDeleted }: Props) => {
const submit = () => {
submitModal(userIdToBeDeleted);
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl border border-gray-700 bg-grey p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
Are you sure you want to remove this user from the workspace?
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">This action is irrevertible.</p>
</div>
<div className="mt-6">
<button
type="button"
className="hover:bg-alizarin hover:text-semibold inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit}
>
Delete
</button>
<button
type="button"
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:border-white hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default DeleteUserDialog;
@@ -1,80 +0,0 @@
import { Fragment } from "react";
import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
import { useOrganization } from "@app/context";
// REFACTOR: Move all these modals into one reusable one
type Props = {
isOpen?: boolean;
onClose: () => void;
text: string;
};
const UpgradePlanModal = ({ isOpen, onClose, text }: Props) => {
const router = useRouter();
const { currentOrg } = useOrganization();
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-50 drop-shadow-xl" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-mineshaft-500 bg-bunker p-6 pt-5 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-xl font-medium leading-6 text-primary">
Unleash Infisical&apos;s Full Power
</Dialog.Title>
<div className="mt-2">
<p className="mb-1 text-sm text-bunker-300">{text}</p>
<p className="text-sm text-bunker-300">
Upgrade and get access to this, as well as to other powerful enhancements.
</p>
</div>
<div className="mt-4">
<button
type="button"
className="hover:text-semibold inline-flex justify-center rounded-md border border-transparent bg-primary px-4 py-2 text-sm font-medium text-black opacity-80 duration-200 hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() => router.push(`/org/${currentOrg?.id}/billing`)}
>
Upgrade Now
</button>
<button
type="button"
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:border-red hover:text-red focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onClose}
>
Maybe Later
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default UpgradePlanModal;
@@ -1,69 +0,0 @@
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
interface PopupProps {
buttonText: string;
buttonLink: string;
titleText: string;
emoji: string;
textLine1: string;
textLine2: string;
setCheckDocsPopUpVisible: (value: boolean) => void;
}
/**
* This is the notification that pops up at the bottom right when a user performs a certain action
* @param {object} org
* @param {string} org.buttonText - text of the button inside the notification
* @param {string} org.buttonLink - where the button leads to
* @param {string} org.titleText - the text at the top of a notification
* @param {string} org.emoji - the emoji in the notification title
* @param {string} org.textLine1 - first line of the text in the notification
* @param {string} org.textLine2 - second line of the text in the notification
* @param {string} org.setCheckDocsPopUpVisible - the functions that closes the popup
* @returns
*/
const BottonRightPopup = ({
buttonText,
buttonLink,
titleText,
emoji,
textLine1,
textLine2,
setCheckDocsPopUpVisible
}: PopupProps): JSX.Element => {
return (
<div
className="absolute bottom-0 right-0 z-[100] mr-6 mb-6 flex max-w-xl flex-col items-start rounded-md border border-gray-600/50 bg-bunker pt-3 pb-4 text-gray-200 drop-shadow-xl"
role="alert"
>
<div className="flex w-full flex-row items-center justify-between border-b border-gray-600/70 px-6 pb-3">
<div className="mr-2 mt-0.5 flex flex-row text-xl font-bold">
<div>{titleText}</div>
<div className="ml-2.5">{emoji}</div>
</div>
<button className="mt-1" onClick={() => setCheckDocsPopUpVisible(false)} type="button">
<FontAwesomeIcon
icon={faXmark}
className="cursor-pointer text-2xl text-gray-400 duration-200 hover:text-red"
/>
</button>
</div>
<div className="mt-4 mb-0.5 block px-6 text-gray-300 sm:inline">{textLine1}</div>
<div className="mb-4 block px-6 sm:inline">{textLine2}</div>
<div className="flex w-full flex-row px-6">
{/* eslint-disable-next-line react/jsx-no-target-blank */}
<a
className="flex w-full justify-center rounded-md bg-white/10 p-2 font-bold duration-200 hover:bg-primary hover:text-black"
href={buttonLink}
target="_blank"
rel="noopener"
>
{buttonText}
</a>
</div>
</div>
);
};
export default BottonRightPopup;
@@ -1,46 +0,0 @@
import { useState } from "react";
import { faInfo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@app/components/v2/Tooltip";
const GlobPatternExamples = () => {
const [showTip, setShowTip] = useState<boolean>(false);
return (
<Tooltip
isOpen={showTip}
onOpenChange={setShowTip}
content={
<div>
<h4 className="mb-2">Here are some examples of glob patterns:</h4>
<div className="ol-listStyleType">
<li>
<code className="text-primary">/</code> - Matches all files and directories in the
current directory
</li>
<li>
<code className="text-primary">**/*</code> - Matches all files and directories in the
current directory and its subdirectories
</li>
<li>
<code className="text-primary">{"/{dir1,dir2}"}</code> - Matches all files and
directories in dir1 and dir2
</li>
</div>
</div>
}
position="right"
className="text-xs"
>
<div
className="flex h-3.5 w-3.5 items-center justify-center rounded-full border border-[1px] border-mineshaft-300"
onMouseEnter={() => setShowTip(true)}
>
<FontAwesomeIcon icon={faInfo} className="h-2 w-2" />
</div>
</Tooltip>
);
};
export default GlobPatternExamples;
@@ -1,32 +0,0 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
type Props = {
addAllUsers: boolean;
setAddAllUsers: (arg: boolean) => void;
};
export const Checkbox = ({ addAllUsers, setAddAllUsers }: Props) => (
<div className="flex flex-row items-center">
{addAllUsers === true ? (
<input
type="checkbox"
className="h-4 w-4 accent-primary"
checked
readOnly
onClick={() => setAddAllUsers(!addAllUsers)}
/>
) : (
<div
onKeyDown={() => setAddAllUsers(!addAllUsers)}
role="button"
tabIndex={0}
aria-label="add all users"
className="h-4 w-4 rounded-sm border border-gray-600 bg-bunker"
onClick={() => setAddAllUsers(!addAllUsers)}
/>
)}
<label className="ml-2 text-sm text-gray-500">
Add all members of my organization to this project.
</label>
</div>
);
@@ -1,91 +0,0 @@
import { Fragment } from "react";
import { useRouter } from "next/router";
import { faCheckSquare, faPlus, faSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Menu, Transition } from "@headlessui/react";
import { Tag } from "public/data/frequentInterfaces";
import { ProjectType } from "@app/hooks/api/workspace/types";
/**
* This is the menu that is used to add more tags to a secret
* @param {object} obj
* @param {Tag[]} obj.allTags - all available tags for a vertain project
* @param {Tag[]} obj.currentTags - currently selected tags for a certain secret
* @param {function} obj.modifyTags - modify tags for a certain secret
* @param {Tag[]} obj.position - currently selected tags for a certain secret
*/
const AddTagsMenu = ({
allTags,
currentTags,
modifyTags,
id
}: {
allTags: Tag[];
currentTags: Tag[];
modifyTags: (value: Tag[], id: string) => void;
id: string;
}) => {
const router = useRouter();
return (
<Menu as="div" className="relative ml-2 inline-block text-left">
<Menu.Button
as="div"
className="flex items-center justify-center font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
>
<div className="flex cursor-pointer items-center rounded-sm bg-mineshaft/30 text-sm text-mineshaft-200/50 duration-200 hover:bg-mineshaft/70">
<FontAwesomeIcon icon={faPlus} className="p-[0.28rem]" />
{currentTags?.length > 2 && <span className="pr-2">{currentTags.length - 2}</span>}
</div>
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-[90] mt-0.5 w-[12rem] origin-top-right space-y-1 rounded-md border border-mineshaft-500 bg-mineshaft-600 p-1 text-sm shadow-lg ring-1 ring-black ring-opacity-5 drop-shadow-xl focus:outline-none">
{allTags?.map((tag) => {
return (
<Menu.Item key={tag.id}>
<button
type="button"
className={`${
currentTags?.map((currentTag) => currentTag.name).includes(tag.name)
? "cursor-default opacity-30"
: "hover:bg-mineshaft-700"
} flex w-full items-center rounded-sm bg-mineshaft-800 px-2 py-0.5 text-left text-bunker-200`}
onClick={() => {
if (!currentTags?.map((currentTag) => currentTag.name).includes(tag.name)) {
modifyTags(currentTags.concat([tag]), id);
}
}}
>
{currentTags?.map((currentTag) => currentTag.name).includes(tag.name) ? (
<FontAwesomeIcon icon={faCheckSquare} className="mr-2 text-xs text-primary" />
) : (
<FontAwesomeIcon icon={faSquare} className="mr-2 text-xs" />
)}{" "}
{tag.name}
</button>
</Menu.Item>
);
})}
<button
type="button"
className="w-full rounded-sm bg-mineshaft-800 px-2 py-0.5 text-left text-bunker-200 duration-200 hover:bg-primary hover:text-black"
onClick={() => router.push(`/${ProjectType.SecretManager}/${String(router.query.id)}/settings`)}
>
<FontAwesomeIcon icon={faPlus} className="mr-2 text-xs" />
Add more tags
</button>
</Menu.Items>
</Transition>
</Menu>
);
};
export default AddTagsMenu;
@@ -1,30 +0,0 @@
import { useTranslation } from "react-i18next";
/**
* This is the text field where people can add comments to particular secrets.
*/
const CommentField = ({
comment,
modifyComment,
id
}: {
comment: string;
modifyComment: (value: string, id: string) => void;
id: string;
}) => {
const { t } = useTranslation();
return (
<div className="relative mt-4 px-4 pt-6">
<p className="pl-0.5 text-sm text-bunker-300">{t("dashboard.sidebar.comments")}</p>
<textarea
className="h-32 w-full rounded-md border border-mineshaft-500 bg-bunker-800 px-2 py-1.5 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2 dark:[color-scheme:dark]"
value={comment}
onChange={(e) => modifyComment(e.target.value, id)}
placeholder="Leave any comments here..."
/>
</div>
);
};
export default CommentField;
@@ -1,42 +0,0 @@
import { Button, Modal, ModalContent } from "../v2";
type Props = {
isOpen: boolean;
onClose: () => void;
onOverwriteConfirm: (preserve: "old" | "new") => void;
duplicateKeys: string[];
};
const ConfirmEnvOverwriteModal = ({
isOpen,
onClose,
duplicateKeys,
onOverwriteConfirm
}: Props): JSX.Element => {
return (
<Modal isOpen={isOpen}>
<ModalContent
title="Duplicate Secrets"
footerContent={
<div className="flex items-center gap-4">
<Button colorSchema="primary" onClick={() => onOverwriteConfirm("old")}>
Keep Old
</Button>
<Button colorSchema="danger" onClick={() => onOverwriteConfirm("new")}>
Overwrite
</Button>
</div>
}
onClose={onClose}
>
<div className="flex flex-col gap-2">
<p className="text-gray-400">Your file contains the following duplicate secrets:</p>
<p className="text-sm text-gray-500">{duplicateKeys.join(", ")}</p>
<p className="text-md text-gray-400">Are you sure you want to overwrite these secrets?</p>
</div>
</ModalContent>
</Modal>
);
};
export default ConfirmEnvOverwriteModal;
@@ -1,274 +0,0 @@
import { memo, SyntheticEvent, useRef } from "react";
import {
faCircle,
faCodeBranch,
faExclamationCircle,
faEye
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import guidGenerator from "../utilities/randomId";
import { HoverObject } from "../v2/HoverCard";
import { PopoverObject } from "../v2/Popover/Popover";
const REGEX = /([$]{.*?})/g;
interface DashboardInputFieldProps {
id: string;
onChangeHandler: (value: string, id: string) => void;
value: string | undefined;
type: "varName" | "value" | "comment";
blurred?: boolean;
isDuplicate?: boolean;
isCapitalized?: boolean;
overrideEnabled?: boolean;
modifyValueOverride?: (value: string | undefined, id: string) => void;
isSideBarOpen?: boolean;
}
/**
* This component renders the input fields on the dashboard
* @param {object} obj - the order number of a keyPair
* @param {number} obj.position - the order number of a keyPair
* @param {function} obj.onChangeHandler - what happens when the input is modified
* @param {string} obj.type - whether the input field is for a Key Name or for a Key Value
* @param {string} obj.value - value of the InputField
* @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard
* @param {boolean} obj.isDuplicate - if the key name is duplicated
* @param {boolean} obj.override - whether a secret/row should be displalyed as overriden
*
*
* @returns
*/
const DashboardInputField = ({
id,
onChangeHandler,
type,
value,
blurred,
isDuplicate,
isCapitalized,
overrideEnabled,
modifyValueOverride,
isSideBarOpen
}: DashboardInputFieldProps) => {
const ref = useRef<HTMLDivElement | null>(null);
const syncScroll = (e: SyntheticEvent<HTMLDivElement>) => {
if (ref.current === null) return;
ref.current.scrollTop = e.currentTarget.scrollTop;
ref.current.scrollLeft = e.currentTarget.scrollLeft;
};
if (type === "varName") {
const startsWithNumber = !Number.isNaN(Number(value?.charAt(0))) && value !== "";
const error = startsWithNumber || isDuplicate;
return (
<div
className={`relative h-10 w-full flex-col ${error && value !== "" ? "bg-red/[0.15]" : ""} ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}
>
<div
className={`group relative flex h-full flex-col items-center justify-center ${
error ? "w-max" : "w-full"
}`}
>
<input
onChange={(e) =>
onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)
}
type={type}
value={value}
className={`ph-no-capture min-w-16 peer z-10 h-full w-full bg-transparent px-2 font-mono text-sm caret-bunker-200 outline-none ${
error ? "text-red-600 focus:text-red-500" : "text-bunker-300 focus:text-bunker-100"
} duration-200`}
spellCheck="false"
/>
</div>
{startsWithNumber && (
<div className="absolute right-2 top-2 z-50 text-red">
<HoverObject
text="Secret names should not start with a number"
icon={faExclamationCircle}
color="red"
/>
</div>
)}
{isDuplicate && value !== "" && !startsWithNumber && (
<div className="absolute right-2 top-2 z-50 text-red">
<HoverObject
text="Secret names should be unique"
icon={faExclamationCircle}
color="red"
/>
</div>
)}
{!error && (
<div
className={`absolute right-0 top-0 z-50 bg-mineshaft-800 text-red group-hover:bg-mineshaft-700 ${
overrideEnabled ? "visible" : "invisible group-hover:visible"
} duration-0 flex h-10 cursor-pointer items-center px-2`}
>
<button
type="button"
onClick={() => {
if (modifyValueOverride) {
if (overrideEnabled === false) {
modifyValueOverride("", id);
} else {
modifyValueOverride(undefined, id);
}
}
}}
>
<HoverObject
text={
overrideEnabled
? "This secret is overriden with your personal value"
: "You can override this secret with a personal value"
}
icon={faCodeBranch}
color={overrideEnabled ? "primary" : "bunker-400"}
/>
</button>
</div>
)}
</div>
);
}
if (type === "comment") {
const startsWithNumber = !Number.isNaN(Number(value?.charAt(0))) && value !== "";
const error = startsWithNumber || isDuplicate;
return (
<PopoverObject text={value || ""} onChangeHandler={onChangeHandler} id={id}>
<div
title={value}
className={`relative h-10 w-full flex-col overflow-hidden ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}
>
<div
className={`group relative flex h-full flex-col items-center justify-center ${
error ? "w-max" : "w-full"
}`}
>
{value?.split("\n")[0] ? (
<span className="ph-no-capture min-w-16 placeholder w-full truncate break-all bg-transparent px-2 text-xs leading-tight text-bunker-300 outline-none duration-200 placeholder:text-bunker-400 focus:text-bunker-100 placeholder:focus:text-transparent">
{value?.split("\n")[0]}
</span>
) : (
<span className="text-bunker-400">-</span>
)}
{value?.split("\n")[1] && (
<span className="ph-no-capture min-w-16 placeholder w-full truncate break-all bg-transparent px-2 text-xs leading-tight text-bunker-300 outline-none duration-200 placeholder:text-bunker-400 focus:text-bunker-100 placeholder:focus:text-transparent">
{value?.split("\n")[1]}
</span>
)}
</div>
</div>
</PopoverObject>
);
}
if (type === "value") {
return (
<div className="w-full flex-col">
<div className="group relative flex w-full flex-col justify-center whitespace-pre">
{overrideEnabled === true && (
<div className="absolute top-[0.1rem] right-[0.1rem] z-0 w-min rounded-sm bg-primary-500 px-1 text-xxs text-black opacity-80">
Override enabled
</div>
)}
<input
value={value}
onChange={(e) => onChangeHandler(e.target.value, id)}
onScroll={syncScroll}
className={`${
blurred ? "text-transparent focus:text-transparent active:text-transparent" : ""
} ph-no-capture min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full bg-transparent px-2 py-2 font-mono text-sm text-transparent caret-white outline-none duration-200 no-scrollbar`}
spellCheck="false"
/>
<div
ref={ref}
className={`${
blurred && !overrideEnabled
? "text-bunker-800 duration-200 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400"
: ""
} ${overrideEnabled ? "text-primary-300" : "text-gray-400"}
absolute z-0 flex flex-row whitespace-pre font-mono ${
blurred ? "invisible" : "visible"
} ph-no-capture min-w-16 no-scrollbar::-webkit-scrollbar mt-0.5 h-10 w-full overflow-x-scroll bg-transparent px-2 py-2 text-sm outline-none duration-100 no-scrollbar peer-focus:visible`}
>
{value?.split(REGEX).map((word) => {
if (word.match(REGEX) !== null) {
return (
<span className="ph-no-capture text-yellow" key={id}>
{word.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{word.slice(2, word.length - 1)}
</span>
{word.slice(word.length - 1, word.length) === "}" ? (
<span className="ph-no-capture text-yellow">
{word.slice(word.length - 1, word.length)}
</span>
) : (
<span className="ph-no-capture text-yellow-400">
{word.slice(word.length - 1, word.length)}
</span>
)}
</span>
);
}
return (
<span key={`${word}_${id + 1}`} className="ph-no-capture">
{word}
</span>
);
})}
</div>
{blurred && (
<div
className={`peer absolute z-0 flex flex-row items-center justify-between pr-2 ${
isSideBarOpen ? "bg-mineshaft-700 duration-200" : "bg-mineshaft-800"
} h-10 w-full text-clip text-bunker-400 duration-100 group-hover:bg-white/[0.00] peer-focus:hidden peer-active:hidden`}
>
<div className="no-scrollbar::-webkit-scrollbar flex flex-row items-center overflow-x-scroll px-2 no-scrollbar">
{value?.split("").map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="mr-0.5 text-xxs"
icon={faCircle}
/>
))}
{value?.split("").length === 0 && <span className="text-bunker-400/80">EMPTY</span>}
</div>
<div className="invisible z-[100] cursor-default group-hover:visible">
<FontAwesomeIcon icon={faEye} />
</div>
</div>
)}
</div>
</div>
);
}
return <>Something Wrong</>;
};
function inputPropsAreEqual(prev: DashboardInputFieldProps, next: DashboardInputFieldProps) {
return (
prev.value === next.value &&
prev.type === next.type &&
prev.id === next.id &&
prev.blurred === next.blurred &&
prev.isCapitalized === next.isCapitalized &&
prev.overrideEnabled === next.overrideEnabled &&
prev.isDuplicate === next.isDuplicate &&
prev.isSideBarOpen === next.isSideBarOpen
);
}
export default memo(DashboardInputField, inputPropsAreEqual);
@@ -1,42 +0,0 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "../basic/buttons/Button";
type Props = {
onSubmit: () => void;
isPlain?: boolean;
};
export const DeleteActionButton = ({ onSubmit, isPlain }: Props) => {
const { t } = useTranslation();
return (
<div
className={`${
!isPlain
? "ml-2 h-[2.5rem] w-[4.5rem] rounded-md bg-[#9B3535] opacity-70 duration-200 hover:opacity-100"
: "justfy-center mr-2 flex h-[2.35rem] w-[1.5rem] cursor-pointer items-center"
}`}
>
{isPlain ? (
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={onSubmit}
className="invisible group-hover:visible"
>
<FontAwesomeIcon
className="mt-0.5 pl-2 pr-6 text-lg text-bunker-300 hover:text-red"
icon={faXmark}
/>
</div>
) : (
<Button text={String(t("Delete"))} color="red" size="md" onButtonPressed={onSubmit} />
)}
</div>
);
};
@@ -1,57 +0,0 @@
import { Fragment } from "react";
import { faDownload } from "@fortawesome/free-solid-svg-icons";
import { Menu, Transition } from "@headlessui/react";
import { SecretDataProps } from "public/data/frequentInterfaces";
import Button from "../basic/buttons/Button";
import downloadDotEnv from "../utilities/secrets/downloadDotEnv";
import downloadYaml from "../utilities/secrets/downloadYaml";
/**
* This is the menu that is used to download secrets as .env ad .yml files (in future we may have more options)
* @param {object} obj
* @param {SecretDataProps[]} obj.data - secrets that we want to downlaod
* @param {string} obj.env - the environment which we're downloading (used for naming the file)
*/
const DownloadSecretMenu = ({ data, env }: { data: SecretDataProps[]; env: string }) => {
return (
<Menu as="div" className="relative inline-block text-left">
<Menu.Button
as="div"
className="inline-flex w-full justify-center rounded-md text-sm font-medium text-gray-200 duration-200 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
>
<Button color="mineshaft" size="icon-md" icon={faDownload} onButtonPressed={() => {}} />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-[90] mt-0.5 w-[12rem] origin-top-right space-y-2 rounded-md border border-mineshaft-500 bg-bunker p-2 shadow-lg ring-1 ring-black ring-opacity-5 drop-shadow-xl focus:outline-none">
<Menu.Item>
<Button
color="mineshaft"
onButtonPressed={() => downloadDotEnv({ data, env })}
size="md"
text="Download as .env"
/>
</Menu.Item>
<Menu.Item>
<Button
color="mineshaft"
onButtonPressed={() => downloadYaml({ data, env })}
size="md"
text="Download as .yml"
/>
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
);
};
export default DownloadSecretMenu;
@@ -1,256 +0,0 @@
/* eslint-disable no-nested-ternary */
import { type ChangeEvent, type DragEvent, useState } from "react";
import { useTranslation } from "react-i18next";
import Image from "next/image";
import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { parseDocument, Scalar, YAMLMap } from "yaml";
import { SecretType } from "@app/hooks/api/types";
import Button from "../basic/buttons/Button";
import Error from "../basic/Error";
import { createNotification } from "../notifications";
import { parseDotEnv } from "../utilities/parseSecrets";
import guidGenerator from "../utilities/randomId";
interface DropZoneProps {
// TODO: change Data type from any
setData: (data: any) => void;
setErrorDragAndDrop: (hasError: boolean) => void;
createNewFile: () => void;
errorDragAndDrop: boolean;
setButtonReady: (isReady: boolean) => void;
keysExist: boolean;
numCurrentRows: number;
}
const DropZone = ({
setData,
setErrorDragAndDrop,
createNewFile,
errorDragAndDrop,
setButtonReady,
keysExist,
numCurrentRows
}: DropZoneProps) => {
const { t } = useTranslation();
const handleDragEnter = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragLeave = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragOver = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
// set dropEffect to copy i.e copy of the source item
e.dataTransfer.dropEffect = "copy";
};
const [loading, setLoading] = useState(false);
const getSecrets = (file: ArrayBuffer, fileType: string) => {
let secrets;
switch (fileType) {
case "env": {
const keyPairs = parseDotEnv(file);
secrets = Object.keys(keyPairs).map((key, index) => ({
id: guidGenerator(),
pos: numCurrentRows + index,
key,
value: keyPairs[key as keyof typeof keyPairs].value,
comment: keyPairs[key as keyof typeof keyPairs].comments.join("\n"),
type: SecretType.Shared,
tags: []
}));
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: SecretType.Shared,
tags: []
}));
break;
}
case "yml": {
const parsedFile = parseDocument(file.toString());
const keyPairs = parsedFile.contents!.toJSON();
secrets = Object.keys(keyPairs).map((key, index) => {
const fileContent = parsedFile.contents as YAMLMap<Scalar, Scalar>;
const comment =
fileContent!.items
.find((item) => item.key.value === key)
?.key?.commentBefore?.split("\n")
.map((cmnt) => cmnt.trim())
.join("\n") ?? "";
return {
id: guidGenerator(),
pos: numCurrentRows + index,
key,
value: keyPairs[key as keyof typeof keyPairs]?.toString() ?? "",
comment,
type: SecretType.Shared,
tags: []
};
});
break;
}
default:
secrets = "";
createNotification({
text: "The file you are dropping should have one of the following extensions: .env, .yml, .json.",
type: "error"
});
break;
}
return secrets;
};
// This function function immediately parses the file after it is dropped
const handleDrop = async (e: DragEvent) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = "copy";
const file = e.dataTransfer.files[0];
const reader = new FileReader();
if (file === undefined) {
createNotification({
text: "You can't inject files from VS Code. Click 'Reveal in finder', and drag your file directly from the directory where it's located.",
type: "error"
});
setLoading(false);
return;
}
const fileType = file.name.split(".")[1];
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
// parse function's argument looks like to be ArrayBuffer
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
try {
reader.readAsText(file);
setLoading(false);
} catch (error) {
setErrorDragAndDrop(true);
setLoading(false);
}
};
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
const handleFileSelect = (e: ChangeEvent<HTMLInputElement>) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
if (e.currentTarget.files === null) return;
const file = e.currentTarget.files[0];
const fileType = file.name.split(".")[1];
const reader = new FileReader();
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
const { result } = event.target;
const newData = getSecrets(result as ArrayBuffer, fileType);
setData(newData);
setButtonReady(true);
};
reader.readAsText(file);
};
return loading ? (
<div className="mb-16 flex items-center justify-center pt-16">
<Image src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
</div>
) : keysExist ? (
<div
className="relative mx-auto mb-4 mt-1 flex w-full max-w-[calc(100%-1rem)] flex-col items-center justify-center rounded-md bg-mineshaft-900 py-8 px-2 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100"
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<input
id="fileSelect"
type="file"
className="absolute h-full w-full opacity-0"
accept=""
onChange={handleFileSelect}
/>
{errorDragAndDrop ? <div className="my-3 max-w-xl opacity-80" /> : <div className="" />}
<div className="flex flex-row">
<FontAwesomeIcon icon={faUpload} className="mr-6 text-3xl text-bunker-300" />
<p className="mt-1 text-bunker-300">{t("common.drop-zone-keys")}</p>
</div>
{errorDragAndDrop && (
<div className="mt-8 max-w-xl opacity-80">
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
</div>
)}
</div>
) : (
<div
className="relative mb-16 flex w-full max-w-2xl flex-col items-center justify-center rounded-md bg-bunker px-4 pt-16 opacity-80 outline-dashed outline-2 outline-gray-700 duration-200 hover:opacity-100"
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<FontAwesomeIcon icon={faUpload} className="mb-8 text-7xl" />
<p className="">{t("common.drop-zone")}</p>
<input
id="fileSelect"
type="file"
className="absolute h-full w-full opacity-0"
accept=".txt,.env,.yml"
onChange={handleFileSelect}
/>
<div className="mb-6 mt-5 flex w-full flex-row items-center justify-center">
<div className="w-1/5 border-t border-gray-700" />
<p className="mx-4 text-xs text-gray-400">OR</p>
<div className="w-1/5 border-t border-gray-700" />
</div>
<div className="z-10 mb-6">
<Button
color="mineshaft"
text={String(t("dashboard.add-secret"))}
onButtonPressed={createNewFile}
size="md"
/>
</div>
{errorDragAndDrop ? (
<div className="opacity-80">
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
</div>
) : (
<div className="py-3">
{/* <p className="text-xs text-gray-500"> If you are expecting to see a file here, contact your administrator for permission. </p> */}
</div>
)}
</div>
);
};
export default DropZone;
@@ -1,106 +0,0 @@
import { Fragment, useState } from "react";
import { useTranslation } from "react-i18next";
import { faShuffle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Menu, Transition } from "@headlessui/react";
/**
* This is the menu that is used to (re)generate secrets (currently we only have ranom hex, in future we will have more options)
* @returns the popup-menu for randomly generating secrets
*/
const GenerateSecretMenu = ({
modifyValue,
id
}: {
modifyValue: (value: string, id: string) => void;
id: string;
}) => {
const [randomStringLength, setRandomStringLength] = useState(32);
const { t } = useTranslation();
return (
<Menu as="div" className="relative inline-block text-left">
<div>
<Menu.Button className="inline-flex w-full justify-center rounded-md text-sm font-medium text-gray-200 duration-200 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
<div className="rounded-md bg-bunker-800 py-1 px-2 hover:bg-bunker-500">
<FontAwesomeIcon icon={faShuffle} className="text-bunker-300" />
</div>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-50 mt-0.5 w-[20rem] origin-top-right rounded-md border border-mineshaft-500 bg-bunker px-1 py-1 shadow-lg ring-1 ring-black ring-opacity-5 drop-shadow-xl focus:outline-none">
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={() => {
if (randomStringLength > 32) {
setRandomStringLength(32);
} else if (randomStringLength < 2) {
setRandomStringLength(2);
} else {
modifyValue(
[...Array(randomStringLength)]
.map(() => Math.floor(Math.random() * 16).toString(16))
.join(""),
id
);
}
}}
className="relative flex w-full cursor-pointer select-none flex-row items-center justify-start rounded-md py-2 px-2 text-gray-400 duration-200 hover:bg-white/10 hover:text-gray-200"
>
<FontAwesomeIcon className="pl-1.5 pr-3 text-lg" icon={faShuffle} />
<div className="flex w-full flex-row justify-between text-sm">
<p>{t("dashboard.sidebar.generate-random-hex")}</p>
<p>{t("dashboard.sidebar.digits")}</p>
</div>
</div>
<div className="absolute bottom-[0.4rem] right-[3.3rem] flex w-16 flex-row rounded-md border border-chicago-700 bg-bunker-800 text-bunker-200 ">
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
className="m-0.5 cursor-pointer rounded-md px-1 hover:bg-chicago-700"
onClick={() => {
if (randomStringLength > 1) {
setRandomStringLength(randomStringLength - 1);
}
}}
>
-
</div>
<input
onChange={(e) => setRandomStringLength(parseInt(e.target.value, 10))}
value={randomStringLength}
className="peer z-20 w-full bg-transparent text-center text-sm outline-none"
spellCheck="false"
/>
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
className="m-0.5 cursor-pointer rounded-md px-1 pb-0.5 hover:bg-chicago-700"
onClick={() => {
if (randomStringLength < 32) {
setRandomStringLength(randomStringLength + 1);
}
}}
>
+
</div>
</div>
</Menu.Items>
</Transition>
</Menu>
);
};
export default GenerateSecretMenu;
@@ -1,241 +0,0 @@
import { faEllipsis, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretDataProps, Tag } from "public/data/frequentInterfaces";
import AddTagsMenu from "./AddTagsMenu";
import DashboardInputField from "./DashboardInputField";
import { DeleteActionButton } from "./DeleteActionButton";
interface KeyPairProps {
keyPair: SecretDataProps;
modifyKey: (value: string, id: string) => void;
modifyValue: (value: string, id: string) => void;
modifyValueOverride: (value: string | undefined, id: string) => void;
modifyComment: (value: string, id: string) => void;
modifyTags: (value: Tag[], id: string) => void;
isBlurred: boolean;
isDuplicate: boolean;
toggleSidebar: (id: string) => void;
sidebarSecretId: string;
isSnapshot: boolean;
isCapitalized: boolean;
deleteRow?: (props: DeleteRowFunctionProps) => void;
tags: Tag[];
togglePITSidebar?: (value: boolean) => void;
}
export interface DeleteRowFunctionProps {
ids: string[];
secretName: string;
}
const colors = [
"bg-[#f1c40f]/40",
"bg-[#cb1c8d]/40",
"bg-[#badc58]/40",
"bg-[#ff5400]/40",
"bg-[#3AB0FF]/40",
"bg-[#6F1AB6]/40",
"bg-[#C40B13]/40",
"bg-[#332FD0]/40"
];
const colorsText = [
"text-[#fcf0c3]/70",
"text-[#f2c6e3]/70",
"text-[#eef6d5]/70",
"text-[#ffddcc]/70",
"text-[#f0fffd]/70",
"text-[#FFE5F1]/70",
"text-[#FFDEDE]/70",
"text-[#DFF6FF]/70"
];
/**
* This component represent a single row for an environemnt variable on the dashboard
* @param {object} obj
* @param {SecretDataProps[]} obj.keyPair - data related to the environment variable (id, pos, key, value, public/private)
* @param {function} obj.modifyKey - modify the key of a certain environment variable
* @param {function} obj.modifyValue - modify the value of a certain environment variable
* @param {function} obj.modifyValueOverride - modify the value of a certain environment variable if it is overriden
* @param {function} obj.modifyComment - modify the comment of a certain environment variable
* @param {function} obj.modifyTags - modify the tags of a certain environment variable
* @param {boolean} obj.isBlurred - if the blurring setting is turned on
* @param {boolean} obj.isDuplicate - list of all the duplicates secret names on the dashboard
* @param {function} obj.toggleSidebar - open/close/switch sidebar
* @param {string} obj.sidebarSecretId - the id of a secret for the side bar is displayed
* @param {boolean} obj.isSnapshot - whether this keyPair is in a snapshot. If so, it won't have some features like sidebar
* @param {function} obj.deleteRow - a function to delete a certain keyPair
* @param {function} obj.togglePITSidebar - open or close the Point-in-time recovery sidebar
* @param {Tag[]} obj.tags - tags for a certain secret
* @returns
*/
const KeyPair = ({
keyPair,
modifyKey,
modifyValue,
modifyValueOverride,
modifyComment,
modifyTags,
isBlurred,
isDuplicate,
toggleSidebar,
sidebarSecretId,
isCapitalized,
isSnapshot,
deleteRow,
togglePITSidebar,
tags
}: KeyPairProps) => {
const tagData = tags.map((tag, index) => {
return {
...tag,
color: colors[index % colors.length],
colorText: colorsText[index % colorsText.length]
};
});
return (
<div
className={`group flex flex-col items-center border-b border-mineshaft-500 duration-100 hover:bg-white/[0.03] ${
isSnapshot && "pointer-events-none"
} ${keyPair.id === sidebarSecretId && "bg-mineshaft-700 duration-200"}`}
>
<div className="relative mr-auto flex max-h-14 w-full flex-row items-center justify-between">
<div className="flex w-[23%] flex-row items-center border-r border-mineshaft-600">
<div className="flex h-10 w-14 cursor-default items-center justify-center text-xs text-bunker-400">
{keyPair.pos + 1}
</div>
<div className="flex max-h-16 w-full items-center">
<DashboardInputField
isCapitalized={isCapitalized}
onChangeHandler={modifyKey}
type="varName"
id={keyPair.id ? keyPair.id : keyPair.idOverride || ""}
value={keyPair.key}
isDuplicate={isDuplicate}
overrideEnabled={keyPair.valueOverride !== undefined}
modifyValueOverride={modifyValueOverride}
isSideBarOpen={keyPair.id === sidebarSecretId}
/>
</div>
</div>
<div className="w-5/12 border-r border-mineshaft-600">
<div className="mt-4 flex max-h-10 items-center rounded-lg md:mt-0">
<DashboardInputField
onChangeHandler={
keyPair.valueOverride !== undefined ? modifyValueOverride : modifyValue
}
type="value"
id={keyPair.id ? keyPair.id : keyPair.idOverride || ""}
value={keyPair.valueOverride !== undefined ? keyPair.valueOverride : keyPair.value}
blurred={isBlurred}
overrideEnabled={keyPair.valueOverride !== undefined}
isSideBarOpen={keyPair.id === sidebarSecretId}
/>
</div>
</div>
<div className="w-[calc(10%)] border-r border-mineshaft-600">
<div className="flex max-h-16 items-center">
<DashboardInputField
onChangeHandler={modifyComment}
type="comment"
id={keyPair.id ? keyPair.id : keyPair.idOverride || ""}
value={keyPair.comment}
isDuplicate={isDuplicate}
isSideBarOpen={keyPair.id === sidebarSecretId}
/>
</div>
</div>
</div>
<div className="overflow-r-scroll no-scrollbar::-webkit-scrollbar flex h-10 w-2/12 items-center overflow-visible no-scrollbar">
<div className="flex max-h-16 items-center">
{keyPair.tags?.map(
(tag, index) =>
index < 2 && (
<div
key={keyPair.pos}
className={`ml-2 px-1.5 ${
tagData.filter((tagDp) => tagDp.id === tag.id)[0]?.color
} rounded-sm text-sm ${
tagData.filter((tagDp) => tagDp.id === tag.id)[0]?.colorText
} flex items-center`}
>
<span className="mb-0.5 cursor-default">{tag.name}</span>
<FontAwesomeIcon
icon={faXmark}
className="ml-1 cursor-pointer p-1"
onClick={() =>
modifyTags(
keyPair.tags.filter((ttag) => ttag.id !== tag.id),
keyPair.id
)
}
/>
</div>
)
)}
<AddTagsMenu
allTags={tags}
currentTags={keyPair.tags}
modifyTags={modifyTags}
id={keyPair.id ? keyPair.id : keyPair.idOverride || ""}
/>
</div>
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={() => {
if (togglePITSidebar) {
togglePITSidebar(false);
}
toggleSidebar(keyPair.id);
}}
className={`invisible z-50 ml-auto flex h-[2.35rem] w-[1.5rem] cursor-pointer flex-row items-center justify-center rounded-md group-hover:visible group-hover:bg-mineshaft-700 ${
isSnapshot ?? "invisible"
}`}
>
<FontAwesomeIcon
className="text-lg text-bunker-300 hover:text-primary"
icon={faEllipsis}
/>
</div>
<div className={`z-50 group-hover:bg-mineshaft-700 ${isSnapshot ?? "invisible"}`}>
{keyPair.key || keyPair.value ? (
<DeleteActionButton
onSubmit={() => {
if (deleteRow) {
deleteRow({ ids: [keyPair.id], secretName: keyPair?.key });
}
}}
isPlain
/>
) : (
<div className="justfy-center mr-2 flex h-[2.35rem] w-[1.5rem] cursor-pointer items-center">
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={() => {
if (deleteRow) {
deleteRow({ ids: [keyPair.id], secretName: keyPair?.key });
}
}}
className="invisible group-hover:visible"
>
<FontAwesomeIcon
className="mt-0.5 pl-2 pr-6 text-lg text-bunker-300 hover:text-red"
icon={faXmark}
/>
</div>
</div>
)}
</div>
</div>
</div>
);
};
export default KeyPair;
@@ -1,36 +0,0 @@
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { FormLabel, Tooltip } from "../v2";
// To give users example of possible values of TTL
export const FormLabelToolTip = ({ label, linkToMore, content }: { label: string, linkToMore: string, content: string }) => (
<div>
<FormLabel
label={label}
icon={
<Tooltip
content={
<span>
{content}{" "}
<a
href={linkToMore}
target="_blank"
rel="noopener noreferrer"
className="text-primary-700"
>
More
</a>
</span>
}
>
<FontAwesomeIcon
icon={faQuestionCircle}
size="sm"
className="relative bottom-1 right-1"
/>
</Tooltip>
}
/>
</div>
);
@@ -1,12 +1,35 @@
import { FormLabelToolTip } from "./FormLabelToolTip";
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { FormLabel, Tooltip } from "../v2";
// To give users example of possible values of TTL
export const TtlFormLabel = ({ label }: { label: string }) => (
<div>
<FormLabelToolTip
<FormLabel
label={label}
content="1m, 2h, 3d. "
linkToMore="https://github.com/vercel/ms?tab=readme-ov-file#examples"
icon={
<Tooltip
content={
<span>
<a
href="https://github.com/vercel/ms?tab=readme-ov-file#examples"
target="_blank"
rel="noopener noreferrer"
className="text-primary-700"
>
More
</a>
</span>
}
>
<FontAwesomeIcon
icon={faQuestionCircle}
size="sm"
className="relative bottom-1 right-1"
/>
</Tooltip>
}
/>
</div>
);
@@ -46,7 +46,7 @@ export const WishForm = () => {
});
setIsOpen.off();
} catch (err) {
} catch {
createNotification({
text: "An error occured while sending your wish to the Infisical team.",
type: "error"
@@ -1,6 +1,4 @@
import { useRouter } from "next/router";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { useNavigate } from "@tanstack/react-router";
import { Button } from "../v2";
@@ -9,10 +7,10 @@ interface IProps {
}
export const NoEnvironmentsBanner = ({ projectId }: IProps) => {
const router = useRouter();
const navigate = useNavigate();
return (
<div className="mt-4 flex w-full flex-row items-center rounded-md border border-primary-600/70 bg-primary/[.07] p-4 text-base text-white">
<div className="mt-4 flex w-full flex-row items-center rounded-md border border-primary-600/70 bg-primary/[.07] p-4 text-base text-white">
<div className="flex w-full flex-col text-sm">
<span className="mb-2 text-lg font-semibold">
No environments in your project was found
@@ -22,7 +20,7 @@ export const NoEnvironmentsBanner = ({ projectId }: IProps) => {
</p>
</div>
<div className="my-2">
<Button onClick={() => router.push(`/${ProjectType.SecretManager}/${projectId}/settings#environments`)}>
<Button onClick={() => navigate({ to: `/project/${projectId}/settings#environments` })}>
Add environments
</Button>
</div>
@@ -1,8 +1,8 @@
import { useOrganization, useSubscription } from "@app/context";
import { useGetOrgTrialUrl } from "@app/hooks/api";
import { Button } from "../Button";
import { Modal, ModalContent } from "../Modal";
import { Button } from "../../v2/Button";
import { Modal, ModalContent } from "../../v2/Modal";
type Props = {
isOpen?: boolean;
@@ -13,10 +13,10 @@ type Props = {
export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Element => {
const { subscription } = useSubscription();
const { currentOrg } = useOrganization();
const { mutateAsync, isLoading } = useGetOrgTrialUrl();
const { mutateAsync, isPending } = useGetOrgTrialUrl();
const link =
subscription && subscription.slug !== null
? `/org/${currentOrg?.id}/billing`
? ("/organization/billing" as const)
: "https://infisical.com/scheduledemo";
const handleUpgradeBtnClick = async () => {
@@ -50,7 +50,7 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele
</p>
<div className="mt-8 flex items-center">
<Button
isLoading={isLoading}
isLoading={isPending}
colorSchema="primary"
onClick={handleUpgradeBtnClick}
className="mr-4"
@@ -14,8 +14,8 @@ type Props = {
};
const TotpRegistration = ({ onComplete, shouldCenterQr }: Props) => {
const { data: registration, isLoading } = useGetUserTotpRegistration();
const { mutateAsync: verifyUserTotp, isLoading: isVerifyLoading } =
const { data: registration, isPending } = useGetUserTotpRegistration();
const { mutateAsync: verifyUserTotp, isPending: isVerifyLoading } =
useVerifyUserTotpRegistration();
const [qrCodeUrl, setQrCodeUrl] = useState("");
const [totp, setTotp] = useState("");
@@ -47,7 +47,7 @@ const TotpRegistration = ({ onComplete, shouldCenterQr }: Props) => {
generateQRCode();
}, [registration]);
if (isLoading) {
if (isPending) {
return <ContentLoader />;
}
@@ -1,10 +1,7 @@
import { ParsedUrlQuery } from "querystring";
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { faAngleRight, faCheck, faCopy, faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { useOrganization, useWorkspace } from "@app/context";
@@ -61,7 +58,10 @@ export default function NavHeader({
const [isCopied, { timedToggle: toggleIsCopied }] = useToggle(false);
const [isHoveringCopyButton, setIsHoveringCopyButton] = useState(false);
const router = useRouter();
const routerEnvSlug = useParams({
strict: false,
select: (el) => el.envSlug
});
const secretPathSegments = secretPath.split("/").filter(Boolean);
@@ -71,13 +71,10 @@ export default function NavHeader({
{currentOrg?.name?.charAt(0)}
</div>
<Link
passHref
legacyBehavior
href={`/org/${currentOrg?.id}/${ProjectType.SecretManager}/overview`}
to="/organization/secret-manager/overview"
className="truncate pl-0.5 text-sm font-semibold text-primary/80 hover:text-primary"
>
<a className="truncate pl-0.5 text-sm font-semibold text-primary/80 hover:text-primary">
{currentOrg?.name}
</a>
{currentOrg?.name}
</Link>
{isProjectRelated && (
<>
@@ -96,14 +93,11 @@ export default function NavHeader({
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
{pageName === "Secrets" ? (
<Link
passHref
legacyBehavior
href={{
pathname: `/${ProjectType.SecretManager}/[id]/secrets/overview`,
query: { id: router.query.id }
}}
to={`/${ProjectType.SecretManager}/$projectId/overview` as const}
params={{ projectId: currentWorkspace.id }}
className="text-sm font-semibold text-primary/80 hover:text-primary"
>
<a className="text-sm font-semibold text-primary/80 hover:text-primary">{pageName}</a>
{pageName}
</Link>
) : (
<div className="text-sm text-gray-400">{pageName}</div>
@@ -131,29 +125,21 @@ export default function NavHeader({
</div>
</>
)}
{isFolderMode && Boolean(secretPathSegments.length) && (
{isFolderMode && routerEnvSlug && Boolean(secretPathSegments.length) && (
<div className="flex items-center space-x-3">
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
<Link
passHref
legacyBehavior
href={{
pathname: `/${ProjectType.SecretManager}/[id]/secrets/[env]`,
query: { id: router.query.id, env: router.query.env }
}}
to={`/${ProjectType.SecretManager}/$projectId/secrets/$envSlug` as const}
params={{ projectId: currentWorkspace.id, envSlug: routerEnvSlug }}
className="text-sm font-semibold text-primary/80 hover:text-primary"
>
<a className="text-sm font-semibold text-primary/80 hover:text-primary">
{userAvailableEnvs?.find(({ slug }) => slug === currentEnv)?.name}
</a>
{userAvailableEnvs?.find(({ slug }) => slug === currentEnv)?.name}
</Link>
</div>
)}
{isFolderMode &&
secretPathSegments?.map((folderName, index) => {
const query: ParsedUrlQuery & { secretPath: string } = {
...router.query,
secretPath: `/${secretPathSegments.slice(0, index + 1).join("/")}`
};
const newSecretPath = `/${secretPathSegments.slice(0, index + 1).join("/")}`;
return (
<div
@@ -184,7 +170,7 @@ export default function NavHeader({
onClick={() => {
if (isCopied) return;
navigator.clipboard.writeText(query.secretPath);
navigator.clipboard.writeText(newSecretPath);
createNotification({
text: "Copied secret path to clipboard",
@@ -205,21 +191,18 @@ export default function NavHeader({
</div>
) : (
<Link
passHref
legacyBehavior
href={{
pathname: `/${ProjectType.SecretManager}/[id]/secrets/[env]`,
query
to={`/${ProjectType.SecretManager}/$projectId/secrets/$envSlug` as const}
params={{
projectId: currentWorkspace.id,
envSlug: routerEnvSlug || ""
}}
search={(query) => ({ ...query, secretPath: newSecretPath })}
className={twMerge(
"text-sm font-semibold transition-all hover:text-primary",
isHoveringCopyButton ? "text-primary" : "text-primary/80"
)}
>
<a
className={twMerge(
"text-sm font-semibold transition-all hover:text-primary",
isHoveringCopyButton ? "text-primary" : "text-primary/80"
)}
>
{folderName}
</a>
{folderName}
</Link>
)}
</div>
@@ -1,99 +0,0 @@
import { useRouter } from "next/router";
import { faAngleRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useOrganization, useWorkspace } from "@app/context";
import { Select, SelectItem, Tooltip } from "../v2";
/**
* This is the component at the top of almost every page.
* It shows how to navigate to a certain page.
* It future these links should also be clickable and hoverable
* @param {object} obj
* @param {string} obj.pageName - Name of the page
* @param {boolean} obj.isProjectRelated - whether or not this page is related to project (determine if it's 2 or 3 navigation steps)
* @param {boolean} obj.isOrganizationRelated - whether or not this page is related to organization (determine if it's 2 or 3 navigation steps)
* @param {string} obj.currentEnv - current environment inside a project
* @param {string} obj.userAvailableEnvs - environments that are available to a user in this project (used for the dropdown)
* @param {string} obj.onEnvChange - the action that happens when an env is changed
* @returns
*/
export default function NavHeaderSecrets({
pageName,
isProjectRelated,
isOrganizationRelated,
isSnapshot,
currentEnv,
userAvailableEnvs,
onEnvChange
}: {
pageName: string;
isProjectRelated?: boolean;
isOrganizationRelated?: boolean;
isSnapshot: boolean;
currentEnv?: string;
userAvailableEnvs?: any[];
onEnvChange?: (slug: string) => void;
}): JSX.Element {
const { currentWorkspace } = useWorkspace();
const { currentOrg } = useOrganization();
const router = useRouter();
return (
<div
className={`${!isSnapshot && "absolute"} ml-6 flex cursor-default flex-row items-center pt-6`}
>
<div className="mr-3 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
{currentOrg?.name?.charAt(0)}
</div>
<div className="text-md font-medium text-bunker-300">{currentOrg?.name}</div>
{isProjectRelated && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
<div className="text-md font-medium text-bunker-300">{currentWorkspace?.name}</div>
</>
)}
{isOrganizationRelated && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
<div className="text-md font-medium text-bunker-300">Organization Settings</div>
</>
)}
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
{pageName === "Secrets" ? (
<a
className="text-md font-medium text-primary/80 hover:text-primary"
href={`${router.asPath.split("?")[0]}`}
>
{pageName}
</a>
) : (
<div className="text-md text-gray-400">{pageName}</div>
)}
{currentEnv && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-sm text-gray-400" />
<div className="rounded-md pl-3 hover:bg-bunker-100/10">
<Tooltip content="Select environment">
<Select
value={userAvailableEnvs?.filter((uae) => uae.name === currentEnv)[0]?.slug}
onValueChange={(value) => {
if (value && onEnvChange) onEnvChange(value);
}}
className="text-md bg-transparent pl-0 font-medium text-primary/80 hover:text-primary"
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
>
{userAvailableEnvs?.map(({ name, slug }) => (
<SelectItem value={slug} key={slug}>
{name}
</SelectItem>
))}
</Select>
</Tooltip>
</div>
</>
)}
</div>
);
}
@@ -1,6 +1,6 @@
import { useRouter } from "next/router";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useLocation } from "@tanstack/react-router";
import { Modal, ModalContent, ModalTrigger, Select, SelectItem } from "@app/components/v2";
import { isInfisicalCloud } from "@app/helpers/platform";
@@ -73,10 +73,10 @@ const regions = [
];
export const RegionSelect = () => {
const router = useRouter();
const location = useLocation();
const handleRegionSelect = (value: Region) => {
router.push(`https://${value}.infisical.com/${router.pathname}`);
window.location.assign(`https://${value}.infisical.com/${location.pathname}`);
};
const shouldDisplay =
@@ -116,7 +116,7 @@ export const RegionSelect = () => {
title="Infisical Cloud data regions"
subTitle="Select the closest region to you and your team. Contact Infisical if you need to migrate regions."
>
{regions.map(({ value, label, location, flag }) => (
{regions.map(({ value, label, location: regionLocation, flag }) => (
<div className="mb-6" key={value}>
<p className="font-medium">
<span className="mr-2 inline-block w-4">{flag}</span>
@@ -133,7 +133,7 @@ export const RegionSelect = () => {
</li>
<li>
<FontAwesomeIcon size="xs" className="mr-0.5 text-green" icon={faCheck} /> Hosted
in {location}
in {regionLocation}
</li>
</ul>
</div>
@@ -33,7 +33,6 @@ export const NotificationContent = ({
)}
>
{callToAction}
{copyActions && (
<div className="flex h-7 flex-row items-center gap-2">
{copyActions.map((action) => (
@@ -1,12 +1,12 @@
import { FC } from "react";
import { Controller, useForm } from "react-hook-form";
import { useRouter } from "next/router";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate } from "@tanstack/react-router";
import z from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
import { useCreateOrg, useGetOrganizations, useSelectOrganization } from "@app/hooks/api";
import { useCreateOrg, useSelectOrganization } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
const schema = z
@@ -23,9 +23,7 @@ interface CreateOrgModalProps {
}
export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) => {
const router = useRouter();
const { refetch: refetchOrganizations } = useGetOrganizations();
const navigate = useNavigate();
const {
control,
@@ -51,20 +49,22 @@ export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) =>
});
await selectOrg({
organizationId: organization.id,
forceSetCredentials: true
organizationId: organization.id
});
await refetchOrganizations();
createNotification({
text: "Successfully created organization",
type: "success"
});
if (router.isReady)
router.push(`/org/${organization.id}/${ProjectType.SecretManager}/overview`);
else window.location.href = `/org/${organization.id}/${ProjectType.SecretManager}/overview`;
navigate({
to: `/organization/${ProjectType.SecretManager}/overview` as const,
params: {
organizationId: organization.id
}
});
localStorage.setItem("orgData.id", organization.id);
reset();
onClose();
@@ -1,16 +1,37 @@
import { FunctionComponent, ReactNode } from "react";
import { BoundCanProps, Can } from "@casl/react";
import { faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { TOrgPermission, useOrgPermission } from "@app/context/OrgPermissionContext";
import { Tooltip } from "../v2";
export const OrgPermissionGuardBanner = () => {
return (
<div className="container mx-auto flex h-full items-center justify-center">
<div className="flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300">
<div>
<FontAwesomeIcon icon={faLock} size="6x" />
</div>
<div>
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
<div className="text-sm">
Your role has limited permissions, please <br /> contact your admin to gain access
</div>
</div>
</div>
</div>
);
};
type Props = {
label?: ReactNode;
// this prop is used when there exist already a tooltip as helper text for users
// so when permission is allowed same tooltip will be reused to show helpertext
renderTooltip?: boolean;
allowedLabel?: string;
renderGuardBanner?: boolean;
} & BoundCanProps<TOrgPermission>;
export const OrgPermissionCan: FunctionComponent<Props> = ({
@@ -19,6 +40,7 @@ export const OrgPermissionCan: FunctionComponent<Props> = ({
passThrough = true,
renderTooltip,
allowedLabel,
renderGuardBanner,
...props
}) => {
const { permission } = useOrgPermission();
@@ -36,10 +58,14 @@ export const OrgPermissionCan: FunctionComponent<Props> = ({
return <Tooltip content={label}>{finalChild}</Tooltip>;
}
if (isAllowed && renderTooltip) {
if (isAllowed && renderTooltip && allowedLabel) {
return <Tooltip content={allowedLabel}>{finalChild}</Tooltip>;
}
if (!isAllowed && renderGuardBanner) {
return <OrgPermissionGuardBanner />;
}
if (!isAllowed) return null;
return finalChild;
@@ -1,11 +1,31 @@
import { FunctionComponent, ReactNode } from "react";
import { AbilityTuple, MongoAbility } from "@casl/ability";
import { Can } from "@casl/react";
import { faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ProjectPermissionSet, useProjectPermission } from "@app/context/ProjectPermissionContext";
import { Tooltip } from "../v2/Tooltip";
export const ProjectPermissionGuardBanner = () => {
return (
<div className="container mx-auto flex h-full items-center justify-center">
<div className="flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300">
<div>
<FontAwesomeIcon icon={faLock} size="6x" />
</div>
<div>
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
<div className="text-sm">
Your role has limited permissions, please <br /> contact your admin to gain access
</div>
</div>
</div>
</div>
);
};
type Props<T extends AbilityTuple> = {
label?: ReactNode;
// this prop is used when there exist already a tooltip as helper text for users
@@ -17,6 +37,7 @@ type Props<T extends AbilityTuple> = {
I: T[0];
a: T[1];
ability?: MongoAbility<T>;
renderGuardBanner?: boolean;
};
export const ProjectPermissionCan: FunctionComponent<Props<ProjectPermissionSet>> = ({
@@ -25,6 +46,7 @@ export const ProjectPermissionCan: FunctionComponent<Props<ProjectPermissionSet>
passThrough = true,
renderTooltip,
allowedLabel,
renderGuardBanner,
...props
}) => {
const { permission } = useProjectPermission();
@@ -39,10 +61,14 @@ export const ProjectPermissionCan: FunctionComponent<Props<ProjectPermissionSet>
return <Tooltip content={label}>{finalChild}</Tooltip>;
}
if (isAllowed && renderTooltip) {
if (isAllowed && renderTooltip && allowedLabel) {
return <Tooltip content={allowedLabel}>{finalChild}</Tooltip>;
}
if (!isAllowed && renderGuardBanner) {
return <ProjectPermissionGuardBanner />;
}
if (!isAllowed) return null;
return finalChild;
@@ -1,9 +1,9 @@
import { FC, useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { useRouter } from "next/router";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate } from "@tanstack/react-router";
import z from "zod";
import { createNotification } from "@app/components/notifications";
@@ -67,7 +67,7 @@ interface NewProjectModalProps {
type NewProjectFormProps = Pick<NewProjectModalProps, "onOpenChange" | "projectType">;
const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
const router = useRouter();
const navigate = useNavigate();
const { currentOrg } = useOrganization();
const { permission } = useOrgPermission();
const { user } = useUser();
@@ -85,7 +85,7 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates)
});
const { data: externalKmsList } = useGetExternalKmsList(currentOrg?.id!, {
const { data: externalKmsList } = useGetExternalKmsList(currentOrg.id, {
enabled: permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.Kms)
});
@@ -148,7 +148,7 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
createNotification({ text: "Project created", type: "success" });
reset();
onOpenChange(false);
router.push(getProjectHomePage(project));
navigate({ to: getProjectHomePage(project), params: { projectId: project.id } });
} catch (err) {
console.error(err);
createNotification({ text: "Failed to create project", type: "error" });
@@ -304,7 +304,7 @@ const NewProjectForm = ({ onOpenChange, projectType }: NewProjectFormProps) => {
</AccordionContent>
</AccordionItem>
</Accordion>
<div className="absolute right-0 bottom-0 mr-6 mb-6 flex items-start justify-end">
<div className="absolute bottom-0 right-0 mb-6 mr-6 flex items-start justify-end">
<ModalClose>
<Button colorSchema="secondary" variant="plain" className="py-2">
Cancel
@@ -39,12 +39,18 @@
}
.details[open] summary ~ * {
animation: sweep .5s ease-in-out;
animation: sweep 0.5s ease-in-out;
}
@keyframes sweep {
0% {opacity: 0; margin-left: -10px}
100% {opacity: 1; margin-left: 0px}
0% {
opacity: 0;
margin-left: -10px;
}
100% {
opacity: 1;
margin-left: 0px;
}
}
.tree summary {

Some files were not shown because too many files have changed in this diff Show More