mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Merge pull request #222 from akhilmhdh/feat/migration-ts
migration(frontend): migrated frontend files to ts execpt dialog component
This commit is contained in:
@ -1,14 +1,17 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { publicPaths } from '~/const';
|
||||
import checkAuth from '~/pages/api/auth/CheckAuth';
|
||||
import { publicPaths } from "~/const";
|
||||
import checkAuth from "~/pages/api/auth/CheckAuth";
|
||||
|
||||
// #TODO: finish spinner only when the data loads fully
|
||||
// #TODO: Redirect somewhere if the page does not exist
|
||||
|
||||
export default function RouteGuard({ children }) {
|
||||
type Prop = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function RouteGuard({ children }: Prop) {
|
||||
const router = useRouter();
|
||||
const [authorized, setAuthorized] = useState(false);
|
||||
|
||||
@ -22,16 +25,16 @@ export default function RouteGuard({ children }) {
|
||||
// #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("routeChangeStart", hideContent);
|
||||
// router.events.on("routeChangeError", onError);
|
||||
|
||||
// on route change complete - run auth check
|
||||
router.events.on('routeChangeComplete', authCheck);
|
||||
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("routeChangeStart", hideContent);
|
||||
router.events.off("routeChangeComplete", authCheck);
|
||||
// router.events.off("routeChangeError", onError);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -39,11 +42,10 @@ export default function RouteGuard({ children }) {
|
||||
|
||||
/**
|
||||
* redirect to login page if accessing a private page and not logged in
|
||||
* @param {*} url - the url of the page we are trying to go to
|
||||
*/
|
||||
async function authCheck(url) {
|
||||
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];
|
||||
const path = "/" + url.split("?")[0].split("/")[1];
|
||||
|
||||
// Check if the user is authenticated
|
||||
const response = await checkAuth();
|
||||
@ -52,16 +54,16 @@ export default function RouteGuard({ children }) {
|
||||
if (!publicPaths.includes(path)) {
|
||||
try {
|
||||
if (response.status !== 200) {
|
||||
router.push('/login');
|
||||
console.log('Unauthorized to access.');
|
||||
router.push("/login");
|
||||
console.log("Unauthorized to access.");
|
||||
setAuthorized(false);
|
||||
} else {
|
||||
setAuthorized(true);
|
||||
console.log('Authorized to access.');
|
||||
console.log("Authorized to access.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
'Error (probably the authCheck route is stuck again...):',
|
||||
"Error (probably the authCheck route is stuck again...):",
|
||||
error
|
||||
);
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
import React from "react";
|
||||
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
export default function Error({ text }: { text: string }): JSX.Element {
|
||||
return (
|
||||
<div className="relative flex flex-row justify-center m-auto items-center w-fit rounded-full">
|
||||
<div className='relative flex flex-row justify-center m-auto items-center w-fit rounded-full'>
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationTriangle}
|
||||
className="text-red mt-1.5 mb-2 mx-2"
|
||||
className='text-red mt-1.5 mb-2 mx-2'
|
||||
/>
|
||||
{text && (
|
||||
<p className="relative top-0 text-red mr-2 text-sm py-1">{text}</p>
|
||||
<p className='relative top-0 text-red mr-2 text-sm py-1'>{text}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { memo,useState } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
@ -28,12 +28,12 @@ const InputField = (
|
||||
|
||||
if (props.static === true) {
|
||||
return (
|
||||
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md">
|
||||
<p className="text-sm font-semibold text-gray-400 mb-0.5">
|
||||
<div className='flex flex-col my-2 md:my-4 justify-center w-full max-w-md'>
|
||||
<p className='text-sm font-semibold text-gray-400 mb-0.5'>
|
||||
{props.label}
|
||||
</p>
|
||||
{props.text && (
|
||||
<p className="text-xs text-gray-400 mb-2">{props.text}</p>
|
||||
<p className='text-xs text-gray-400 mb-2'>{props.text}</p>
|
||||
)}
|
||||
<input
|
||||
onChange={(e) => props.onChangeHandler(e.target.value)}
|
||||
@ -41,7 +41,7 @@ const InputField = (
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
required={props.isRequired}
|
||||
className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none"
|
||||
className='bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none'
|
||||
name={props.name}
|
||||
readOnly
|
||||
autoComplete={props.autoComplete}
|
||||
@ -51,9 +51,9 @@ const InputField = (
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5">
|
||||
<p className="text-sm font-semibold mr-1">{props.label}</p>
|
||||
<div className='flex-col w-full'>
|
||||
<div className='flex flex-row text-mineshaft-300 items-center mb-0.5'>
|
||||
<p className='text-sm font-semibold mr-1'>{props.label}</p>
|
||||
{/* {props.label == "Password" && router.asPath != "/login" && (
|
||||
<div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<FontAwesomeIcon
|
||||
@ -90,17 +90,17 @@ const InputField = (
|
||||
props.error ? 'focus:ring-red/50' : 'focus:ring-primary/50'
|
||||
} relative peer bg-bunker-800 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
|
||||
name={props.name}
|
||||
spellCheck="false"
|
||||
spellCheck='false'
|
||||
autoComplete={props.autoComplete}
|
||||
id={props.id}
|
||||
/>
|
||||
{props.label?.includes('Password') && (
|
||||
<button
|
||||
type="button"
|
||||
type='button'
|
||||
onClick={() => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
}}
|
||||
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
|
||||
className='absolute self-end mr-3 text-gray-400 cursor-pointer'
|
||||
>
|
||||
{passwordVisible ? (
|
||||
<FontAwesomeIcon icon={faEyeSlash} />
|
||||
@ -110,15 +110,15 @@ const InputField = (
|
||||
</button>
|
||||
)}
|
||||
{props.blurred && (
|
||||
<div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
|
||||
<p className="ml-2"></p>
|
||||
<div className='peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden'>
|
||||
<p className='ml-2'></p>
|
||||
{props.value
|
||||
.split('')
|
||||
.slice(0, 54)
|
||||
.map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
className='text-xxs mx-0.5'
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
@ -131,7 +131,7 @@ const InputField = (
|
||||
)} */}
|
||||
</div>
|
||||
{props.error && (
|
||||
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">
|
||||
<p className='text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs'>
|
||||
{props.errorText}
|
||||
</p>
|
||||
)}
|
||||
|
@ -3,7 +3,7 @@ import { Fragment } from 'react';
|
||||
import {
|
||||
faAngleDown,
|
||||
faCheck,
|
||||
faPlus
|
||||
faPlus,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Listbox, Transition } from '@headlessui/react';
|
||||
@ -25,7 +25,6 @@ interface ListBoxProps {
|
||||
* @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
|
||||
* @param {string} obj.width - button width
|
||||
* @returns
|
||||
*/
|
||||
export default function ListBox({
|
||||
@ -34,37 +33,37 @@ export default function ListBox({
|
||||
data,
|
||||
text,
|
||||
buttonAction,
|
||||
isFull
|
||||
isFull,
|
||||
}: ListBoxProps): JSX.Element {
|
||||
return (
|
||||
<Listbox value={selected} onChange={onChange}>
|
||||
<div className="relative">
|
||||
<div className='relative'>
|
||||
<Listbox.Button
|
||||
className={`text-gray-400 relative ${
|
||||
isFull ? 'w-full' : 'w-52'
|
||||
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md 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 focus-visible:ring-offset-orange-300 sm:text-sm`}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<div className='flex flex-row'>
|
||||
{text}
|
||||
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300 capitalize">
|
||||
<span className='ml-1 cursor-pointer block truncate font-semibold text-gray-300 capitalize'>
|
||||
{' '}
|
||||
{selected}
|
||||
</span>
|
||||
</div>
|
||||
{data && (
|
||||
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" />
|
||||
<div className='cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex 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"
|
||||
leave='transition ease-in duration-100'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
>
|
||||
<Listbox.Options className="border border-mineshaft-700 z-50 p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||
<Listbox.Options className='border border-mineshaft-700 z-50 p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm'>
|
||||
{data.map((person, personIdx) => (
|
||||
<Listbox.Option
|
||||
key={personIdx}
|
||||
@ -89,10 +88,10 @@ export default function ListBox({
|
||||
{person}
|
||||
</span>
|
||||
{selected ? (
|
||||
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<span className='text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3'>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md ml-1"
|
||||
className='text-md ml-1'
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
@ -103,11 +102,11 @@ export default function ListBox({
|
||||
{buttonAction && (
|
||||
<button
|
||||
onClick={buttonAction}
|
||||
className="cursor-pointer w-full"
|
||||
className='cursor-pointer w-full'
|
||||
>
|
||||
<div className="my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2">
|
||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg" />
|
||||
<div className='my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2'>
|
||||
<span className='rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4'>
|
||||
<FontAwesomeIcon icon={faPlus} className='text-lg' />
|
||||
</span>
|
||||
Add Project
|
||||
</div>
|
||||
|
@ -1,27 +1,26 @@
|
||||
import React from "react";
|
||||
import { Switch } from "@headlessui/react";
|
||||
import { Switch } from '@headlessui/react';
|
||||
|
||||
interface ToggleProps {
|
||||
enabled: boolean;
|
||||
setEnabled: (value: boolean) => void;
|
||||
addOverride: (value: string | undefined, pos: number) => void;
|
||||
interface ToggleProps {
|
||||
enabled: boolean;
|
||||
setEnabled: (value: boolean) => void;
|
||||
addOverride: (value: string | undefined, pos: number) => void;
|
||||
pos: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a typical 'iPhone' toggle (e.g., user for overriding secrets with personal values)
|
||||
* @param obj
|
||||
* @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.pos - position of a certain secret
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export default function Toggle ({
|
||||
enabled,
|
||||
setEnabled,
|
||||
addOverride,
|
||||
pos
|
||||
export default function Toggle({
|
||||
enabled,
|
||||
setEnabled,
|
||||
addOverride,
|
||||
pos,
|
||||
}: ToggleProps): JSX.Element {
|
||||
return (
|
||||
<Switch
|
||||
@ -38,12 +37,12 @@ export default function Toggle ({
|
||||
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='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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import React from "react";
|
||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
interface PopupProps {
|
||||
buttonText: string;
|
||||
@ -15,7 +13,7 @@ interface PopupProps {
|
||||
|
||||
/**
|
||||
* This is the notification that pops up at the bottom right when a user performs a certain action
|
||||
* @param {object} org
|
||||
* @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
|
||||
@ -23,7 +21,7 @@ interface PopupProps {
|
||||
* @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
|
||||
* @returns
|
||||
*/
|
||||
export default function BottonRightPopup({
|
||||
buttonText,
|
||||
@ -36,35 +34,35 @@ export default function BottonRightPopup({
|
||||
}: PopupProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className="z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-md absolute bottom-0 right-0 mr-6 mb-6"
|
||||
role="alert"
|
||||
className='z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-md absolute bottom-0 right-0 mr-6 mb-6'
|
||||
role='alert'
|
||||
>
|
||||
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6">
|
||||
<div className="font-bold text-xl mr-2 mt-0.5 flex flex-row">
|
||||
<div className='flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6'>
|
||||
<div className='font-bold text-xl mr-2 mt-0.5 flex flex-row'>
|
||||
<div>{titleText}</div>
|
||||
<div className="ml-2.5">{emoji}</div>
|
||||
<div className='ml-2.5'>{emoji}</div>
|
||||
</div>
|
||||
<button
|
||||
className="mt-1"
|
||||
className='mt-1'
|
||||
onClick={() => setCheckDocsPopUpVisible(false)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
|
||||
className='text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
|
||||
<div className='block sm:inline px-6 mt-4 mb-0.5 text-gray-300'>
|
||||
{textLine1}
|
||||
</div>
|
||||
<div className="block sm:inline mb-4 px-6">{textLine2}</div>
|
||||
<div className="flex flex-row px-6 w-full">
|
||||
<div className='block sm:inline mb-4 px-6'>{textLine2}</div>
|
||||
<div className='flex flex-row px-6 w-full'>
|
||||
{/*eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a
|
||||
className="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center"
|
||||
className='font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center'
|
||||
href={buttonLink}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
target='_blank'
|
||||
rel='noopener'
|
||||
>
|
||||
{buttonText}
|
||||
</a>
|
||||
|
@ -1,28 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export const Checkbox = ({ addAllUsers, setAddAllUsers }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row items-center">
|
||||
{addAllUsers == true ? (
|
||||
<input
|
||||
type="checkbox"
|
||||
className="accent-primary h-4 w-4"
|
||||
checked
|
||||
readOnly
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
></div>
|
||||
)}
|
||||
|
||||
<label className="ml-2 text-gray-500 text-sm">
|
||||
Add all members of my organization to this project.
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
31
frontend/components/basic/table/Checkbox.tsx
Normal file
31
frontend/components/basic/table/Checkbox.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
type Props = {
|
||||
addAllUsers: boolean;
|
||||
setAddAllUsers: (arg: boolean) => void;
|
||||
};
|
||||
|
||||
export const Checkbox = ({ addAllUsers, setAddAllUsers }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-row items-center'>
|
||||
{addAllUsers == true ? (
|
||||
<input
|
||||
type='checkbox'
|
||||
className='accent-primary h-4 w-4'
|
||||
checked
|
||||
readOnly
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className='h-4 w-4 bg-bunker border border-gray-600 rounded-sm'
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
></div>
|
||||
)}
|
||||
|
||||
<label className='ml-2 text-gray-500 text-sm'>
|
||||
Add all members of my organization to this project.
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -8,9 +8,9 @@ interface Props {
|
||||
plan: {
|
||||
name: string;
|
||||
price: string;
|
||||
priceExplanation: string;
|
||||
priceExplanation?: string;
|
||||
text: string;
|
||||
subtext: string;
|
||||
subtext?: string;
|
||||
buttonTextMain: string;
|
||||
buttonTextSecondary: string;
|
||||
current: boolean;
|
||||
@ -27,33 +27,33 @@ export default function Plan({ plan }: Props) {
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row justify-between items-center relative z-10">
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex flex-row justify-between items-center relative z-10'>
|
||||
<p className={`px-6 py-4 text-3xl font-semibold text-gray-400`}>
|
||||
{plan.name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flwx-row items-end justify-start mb-4">
|
||||
<p className="pl-6 text-3xl font-semibold text-primary">
|
||||
<div className='flex flwx-row items-end justify-start mb-4'>
|
||||
<p className='pl-6 text-3xl font-semibold text-primary'>
|
||||
{plan.price}
|
||||
</p>
|
||||
<p className="pl-3 mb-1 text-lg text-gray-400">
|
||||
<p className='pl-3 mb-1 text-lg text-gray-400'>
|
||||
{plan.priceExplanation}
|
||||
</p>
|
||||
</div>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
<p className='relative z-10 max-w-fit px-6 text-base text-gray-400'>
|
||||
{plan.text}
|
||||
</p>
|
||||
<p className="relative z-10 max-w-fit px-6 text-base text-gray-400">
|
||||
<p className='relative z-10 max-w-fit px-6 text-base text-gray-400'>
|
||||
{plan.subtext}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className='flex flex-row items-center'>
|
||||
{plan.current == false ? (
|
||||
<>
|
||||
{plan.buttonTextMain == 'Schedule a Demo' ? (
|
||||
<a href="/scheduledemo" target='_blank rel="noopener"'>
|
||||
<div className="relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max">
|
||||
<a href='/scheduledemo' target='_blank rel="noopener"'>
|
||||
<div className='relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 hover:text-black hover:border-primary text-gray-400 font-semibold hover:bg-primary bg-bunker duration-200 cursor-pointer rounded-md flex w-max'>
|
||||
{plan.buttonTextMain}
|
||||
</div>
|
||||
</a>
|
||||
@ -68,7 +68,7 @@ export default function Plan({ plan }: Props) {
|
||||
<button
|
||||
onClick={() =>
|
||||
StripeRedirect({
|
||||
orgId: tempLocalStorage('orgData.id')
|
||||
orgId: tempLocalStorage('orgData.id'),
|
||||
})
|
||||
}
|
||||
>
|
||||
@ -77,10 +77,10 @@ export default function Plan({ plan }: Props) {
|
||||
</div>
|
||||
)}
|
||||
<a
|
||||
href="https://infisical.com/pricing"
|
||||
href='https://infisical.com/pricing'
|
||||
target='_blank rel="noopener"'
|
||||
>
|
||||
<div className="relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5">
|
||||
<div className='relative z-10 text-gray-400 font-semibold hover:text-primary duration-200 cursor-pointer mb-0.5'>
|
||||
{plan.buttonTextSecondary}
|
||||
</div>
|
||||
</a>
|
||||
@ -93,7 +93,7 @@ export default function Plan({ plan }: Props) {
|
||||
: 'bg-chicago-700'
|
||||
}`}
|
||||
>
|
||||
<p className="text-xs text-black font-semibold">CURRENT PLAN</p>
|
||||
<p className='text-xs text-black font-semibold'>CURRENT PLAN</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,121 +1,117 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faCheck,
|
||||
faX,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import deleteIntegrationAuth from "../../pages/api/integrations/DeleteIntegrationAuth";
|
||||
import deleteIntegrationAuth from '../../pages/api/integrations/DeleteIntegrationAuth';
|
||||
|
||||
interface CloudIntegrationOption {
|
||||
isAvailable: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
clientId: string;
|
||||
docsLink: string;
|
||||
slug: string;
|
||||
isAvailable: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
clientId: string;
|
||||
docsLink: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface IntegrationAuth {
|
||||
_id: string;
|
||||
integration: string;
|
||||
_id: string;
|
||||
integration: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
cloudIntegrationOption: CloudIntegrationOption;
|
||||
setSelectedIntegrationOption: (cloudIntegration: CloudIntegrationOption) => void;
|
||||
integrationOptionPress: (cloudIntegrationOption: CloudIntegrationOption) => void;
|
||||
integrationAuths: IntegrationAuth[];
|
||||
cloudIntegrationOption: CloudIntegrationOption;
|
||||
setSelectedIntegrationOption: (
|
||||
cloudIntegration: CloudIntegrationOption
|
||||
) => void;
|
||||
integrationOptionPress: (
|
||||
cloudIntegrationOption: CloudIntegrationOption
|
||||
) => void;
|
||||
integrationAuths: IntegrationAuth[];
|
||||
}
|
||||
|
||||
const CloudIntegration = ({
|
||||
cloudIntegrationOption,
|
||||
setSelectedIntegrationOption,
|
||||
integrationOptionPress,
|
||||
integrationAuths
|
||||
cloudIntegrationOption,
|
||||
setSelectedIntegrationOption,
|
||||
integrationOptionPress,
|
||||
integrationAuths,
|
||||
}: Props) => {
|
||||
const router = useRouter();
|
||||
return integrationAuths ? (
|
||||
<div
|
||||
className={`relative ${
|
||||
cloudIntegrationOption.isAvailable
|
||||
? "hover:bg-white/10 duration-200 cursor-pointer"
|
||||
: "opacity-50"
|
||||
} flex flex-row bg-white/5 h-32 rounded-md p-4 items-center`}
|
||||
onClick={() => {
|
||||
if (!cloudIntegrationOption.isAvailable) return;
|
||||
setSelectedIntegrationOption(cloudIntegrationOption);
|
||||
integrationOptionPress(cloudIntegrationOption);
|
||||
}}
|
||||
key={cloudIntegrationOption.name}
|
||||
>
|
||||
<Image
|
||||
src={`/images/integrations/${cloudIntegrationOption.name}.png`}
|
||||
height={70}
|
||||
width={70}
|
||||
alt="integration logo"
|
||||
/>
|
||||
{cloudIntegrationOption.name.split(" ").length > 2 ? (
|
||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-4 max-w-xs">
|
||||
<div>{cloudIntegrationOption.name.split(" ")[0]}</div>
|
||||
<div className="text-base">
|
||||
{cloudIntegrationOption.name.split(" ")[1]}{" "}
|
||||
{cloudIntegrationOption.name.split(" ")[2]}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-xl ml-4 max-w-xs">
|
||||
{cloudIntegrationOption.name}
|
||||
</div>
|
||||
)}
|
||||
{cloudIntegrationOption.isAvailable &&
|
||||
integrationAuths
|
||||
.map((authorization) => authorization.integration)
|
||||
.includes(cloudIntegrationOption.name.toLowerCase()) && (
|
||||
<div className="absolute group z-40 top-0 right-0 flex flex-row">
|
||||
<div
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
deleteIntegrationAuth({
|
||||
integrationAuthId: integrationAuths
|
||||
.filter(
|
||||
(authorization) =>
|
||||
authorization.integration ==
|
||||
cloudIntegrationOption.name.toLowerCase()
|
||||
)
|
||||
.map((authorization) => authorization._id)[0],
|
||||
});
|
||||
|
||||
router.reload();
|
||||
}}
|
||||
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-xs mr-2 py-px"
|
||||
/>
|
||||
Revoke
|
||||
</div>
|
||||
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-xs mr-2"
|
||||
/>
|
||||
Authorized
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!cloudIntegrationOption.isAvailable && (
|
||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
||||
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
|
||||
Coming Soon
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
const router = useRouter();
|
||||
return integrationAuths ? (
|
||||
<div
|
||||
className={`relative ${
|
||||
cloudIntegrationOption.isAvailable
|
||||
? 'hover:bg-white/10 duration-200 cursor-pointer'
|
||||
: 'opacity-50'
|
||||
} flex flex-row bg-white/5 h-32 rounded-md p-4 items-center`}
|
||||
onClick={() => {
|
||||
if (!cloudIntegrationOption.isAvailable) return;
|
||||
setSelectedIntegrationOption(cloudIntegrationOption);
|
||||
integrationOptionPress(cloudIntegrationOption);
|
||||
}}
|
||||
key={cloudIntegrationOption.name}
|
||||
>
|
||||
<Image
|
||||
src={`/images/integrations/${cloudIntegrationOption.name}.png`}
|
||||
height={70}
|
||||
width={70}
|
||||
alt='integration logo'
|
||||
/>
|
||||
{cloudIntegrationOption.name.split(' ').length > 2 ? (
|
||||
<div className='font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-4 max-w-xs'>
|
||||
<div>{cloudIntegrationOption.name.split(' ')[0]}</div>
|
||||
<div className='text-base'>
|
||||
{cloudIntegrationOption.name.split(' ')[1]}{' '}
|
||||
{cloudIntegrationOption.name.split(' ')[2]}
|
||||
</div>
|
||||
</div>
|
||||
) : <div></div>
|
||||
}
|
||||
) : (
|
||||
<div className='font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-xl ml-4 max-w-xs'>
|
||||
{cloudIntegrationOption.name}
|
||||
</div>
|
||||
)}
|
||||
{cloudIntegrationOption.isAvailable &&
|
||||
integrationAuths
|
||||
.map((authorization) => authorization.integration)
|
||||
.includes(cloudIntegrationOption.name.toLowerCase()) && (
|
||||
<div className='absolute group z-40 top-0 right-0 flex flex-row'>
|
||||
<div
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
deleteIntegrationAuth({
|
||||
integrationAuthId: integrationAuths
|
||||
.filter(
|
||||
(authorization) =>
|
||||
authorization.integration ==
|
||||
cloudIntegrationOption.name.toLowerCase()
|
||||
)
|
||||
.map((authorization) => authorization._id)[0],
|
||||
});
|
||||
|
||||
export default CloudIntegration;
|
||||
router.reload();
|
||||
}}
|
||||
className='cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200'
|
||||
>
|
||||
<FontAwesomeIcon icon={faX} className='text-xs mr-2 py-px' />
|
||||
Revoke
|
||||
</div>
|
||||
<div className='w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200'>
|
||||
<FontAwesomeIcon icon={faCheck} className='text-xs mr-2' />
|
||||
Authorized
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!cloudIntegrationOption.isAvailable && (
|
||||
<div className='absolute group z-50 top-0 right-0 flex flex-row'>
|
||||
<div className='w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90'>
|
||||
Coming Soon
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloudIntegration;
|
||||
|
@ -1,53 +1,56 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import CloudIntegration from "./CloudIntegration";
|
||||
import CloudIntegration from './CloudIntegration';
|
||||
|
||||
interface CloudIntegrationOption {
|
||||
isAvailable: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
clientId: string;
|
||||
docsLink: string;
|
||||
slug: string;
|
||||
isAvailable: boolean;
|
||||
name: string;
|
||||
type: string;
|
||||
clientId: string;
|
||||
docsLink: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
cloudIntegrationOptions: CloudIntegrationOption[];
|
||||
setSelectedIntegrationOption: () => void;
|
||||
integrationOptionPress: () => void;
|
||||
integrationAuths: any;
|
||||
cloudIntegrationOptions: CloudIntegrationOption[];
|
||||
setSelectedIntegrationOption: () => void;
|
||||
integrationOptionPress: () => void;
|
||||
integrationAuths: any;
|
||||
}
|
||||
|
||||
const CloudIntegrationSection = ({
|
||||
cloudIntegrationOptions,
|
||||
setSelectedIntegrationOption,
|
||||
integrationOptionPress,
|
||||
integrationAuths
|
||||
const CloudIntegrationSection = ({
|
||||
cloudIntegrationOptions,
|
||||
setSelectedIntegrationOption,
|
||||
integrationOptionPress,
|
||||
integrationAuths,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`flex flex-col justify-between items-start m-4 mt-7 text-xl max-w-5xl px-2`}>
|
||||
<h1 className="font-semibold text-3xl">{t("integrations:cloud-integrations")}</h1>
|
||||
<p className="text-base text-gray-400">
|
||||
{t("integrations:click-to-start")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4 grid-cols-4 grid-rows-2 mx-6 max-w-5xl">
|
||||
{cloudIntegrationOptions.map((cloudIntegrationOption) => (
|
||||
<CloudIntegration
|
||||
cloudIntegrationOption={cloudIntegrationOption}
|
||||
setSelectedIntegrationOption={setSelectedIntegrationOption}
|
||||
integrationOptionPress={integrationOptionPress}
|
||||
integrationAuths={integrationAuths}
|
||||
key={`cloud-integration-${cloudIntegrationOption.slug}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-col justify-between items-start m-4 mt-7 text-xl max-w-5xl px-2`}
|
||||
>
|
||||
<h1 className='font-semibold text-3xl'>
|
||||
{t('integrations:cloud-integrations')}
|
||||
</h1>
|
||||
<p className='text-base text-gray-400'>
|
||||
{t('integrations:click-to-start')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='grid gap-4 grid-cols-4 grid-rows-2 mx-6 max-w-5xl'>
|
||||
{cloudIntegrationOptions.map((cloudIntegrationOption) => (
|
||||
<CloudIntegration
|
||||
cloudIntegrationOption={cloudIntegrationOption}
|
||||
setSelectedIntegrationOption={setSelectedIntegrationOption}
|
||||
integrationOptionPress={integrationOptionPress}
|
||||
integrationAuths={integrationAuths}
|
||||
key={`cloud-integration-${cloudIntegrationOption.slug}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloudIntegrationSection;
|
||||
export default CloudIntegrationSection;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import Image from 'next/image';
|
||||
|
||||
interface Framework {
|
||||
name: string;
|
||||
@ -8,29 +7,33 @@ interface Framework {
|
||||
docsLink: string;
|
||||
}
|
||||
|
||||
const FrameworkIntegration = ({
|
||||
framework
|
||||
}: {
|
||||
framework: Framework;
|
||||
}) => {
|
||||
const FrameworkIntegration = ({ framework }: { framework: Framework }) => {
|
||||
return (
|
||||
<a
|
||||
href={framework.docsLink}
|
||||
rel="noopener"
|
||||
className={`relative flex flex-row items-center justify-center bg-bunker-500 hover:bg-gradient-to-tr duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer`}
|
||||
<a
|
||||
href={framework.docsLink}
|
||||
rel='noopener'
|
||||
className={`relative flex flex-row items-center justify-center bg-bunker-500 hover:bg-gradient-to-tr duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer`}
|
||||
>
|
||||
<div
|
||||
className={`hover:bg-white/10 duration-200 cursor-pointer font-semibold bg-bunker-500 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${
|
||||
framework?.name?.split(' ').length > 1
|
||||
? 'text-sm px-1'
|
||||
: 'text-xl px-2'
|
||||
} text-center w-full max-w-xs`}
|
||||
>
|
||||
<div className={`hover:bg-white/10 duration-200 cursor-pointer font-semibold bg-bunker-500 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${framework?.name?.split(" ").length > 1 ? "text-sm px-1" : "text-xl px-2"} text-center w-full max-w-xs`}>
|
||||
{framework?.image && <Image
|
||||
{framework?.image && (
|
||||
<Image
|
||||
src={`/images/integrations/${framework.image}.png`}
|
||||
height={framework?.name ? 60 : 90}
|
||||
width={framework?.name ? 60 : 90}
|
||||
alt="integration logo"
|
||||
></Image>}
|
||||
{framework?.name && framework?.image && <div className="h-2"></div>}
|
||||
{framework?.name && framework.name}
|
||||
</div>
|
||||
</a>
|
||||
alt='integration logo'
|
||||
></Image>
|
||||
)}
|
||||
{framework?.name && framework?.image && <div className='h-2'></div>}
|
||||
{framework?.name && framework.name}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default FrameworkIntegration;
|
||||
|
@ -1,18 +1,17 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import FrameworkIntegration from "./FrameworkIntegration";
|
||||
import FrameworkIntegration from './FrameworkIntegration';
|
||||
|
||||
interface Framework {
|
||||
name: string;
|
||||
image: string;
|
||||
link: string;
|
||||
slug: string;
|
||||
docsLink: string;
|
||||
name: string;
|
||||
image: string;
|
||||
link: string;
|
||||
slug: string;
|
||||
docsLink: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
frameworks: [Framework]
|
||||
frameworks: [Framework];
|
||||
}
|
||||
|
||||
const FrameworkIntegrationSection = ({ frameworks }: Props) => {
|
||||
@ -20,15 +19,17 @@ const FrameworkIntegrationSection = ({ frameworks }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
|
||||
<h1 className="font-semibold text-3xl">{t("integrations:framework-integrations")}</h1>
|
||||
<p className="text-base text-gray-400">
|
||||
{t("integrations:click-to-setup")}
|
||||
<div className='flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2'>
|
||||
<h1 className='font-semibold text-3xl'>
|
||||
{t('integrations:framework-integrations')}
|
||||
</h1>
|
||||
<p className='text-base text-gray-400'>
|
||||
{t('integrations:click-to-setup')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl">
|
||||
<div className='grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl'>
|
||||
{frameworks.map((framework) => (
|
||||
<FrameworkIntegration
|
||||
<FrameworkIntegration
|
||||
framework={framework}
|
||||
key={`framework-integration-${framework.slug}`}
|
||||
/>
|
||||
@ -36,7 +37,6 @@ const FrameworkIntegrationSection = ({ frameworks }: Props) => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default FrameworkIntegrationSection;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React from "react";
|
||||
import guidGenerator from '~/utilities/randomId';
|
||||
|
||||
import guidGenerator from "~/utilities/randomId";
|
||||
|
||||
import Integration from "./Integration";
|
||||
import Integration from './Integration';
|
||||
|
||||
interface Props {
|
||||
integrations: any;
|
||||
@ -23,23 +21,25 @@ const ProjectIntegrationSection = ({
|
||||
integrations,
|
||||
environments = [],
|
||||
}: Props) => {
|
||||
return integrations.length > 0 ? (
|
||||
<div className="mb-12">
|
||||
<div className="flex flex-col justify-between items-start mx-4 mb-4 mt-6 text-xl max-w-5xl px-2">
|
||||
<h1 className="font-semibold text-3xl">Current Integrations</h1>
|
||||
<p className="text-base text-gray-400">
|
||||
Manage your integrations of Infisical with third-party services.
|
||||
</p>
|
||||
</div>
|
||||
{integrations.map((integration: IntegrationType) => (
|
||||
<Integration
|
||||
key={guidGenerator()}
|
||||
integration={integration}
|
||||
environments={environments}
|
||||
/>
|
||||
))}
|
||||
return integrations.length > 0 ? (
|
||||
<div className='mb-12'>
|
||||
<div className='flex flex-col justify-between items-start mx-4 mb-4 mt-6 text-xl max-w-5xl px-2'>
|
||||
<h1 className='font-semibold text-3xl'>Current Integrations</h1>
|
||||
<p className='text-base text-gray-400'>
|
||||
Manage your integrations of Infisical with third-party services.
|
||||
</p>
|
||||
</div>
|
||||
) : <div></div>
|
||||
}
|
||||
{integrations.map((integration: IntegrationType) => (
|
||||
<Integration
|
||||
key={guidGenerator()}
|
||||
integration={integration}
|
||||
environments={environments}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectIntegrationSection;
|
||||
export default ProjectIntegrationSection;
|
||||
|
@ -1,32 +1,36 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faAngleRight,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import getOrganization from "~/pages/api/organization/GetOrg";
|
||||
import getProjectInfo from "~/pages/api/workspace/getProjectInfo";
|
||||
import getOrganization from '~/pages/api/organization/GetOrg';
|
||||
import getProjectInfo from '~/pages/api/workspace/getProjectInfo';
|
||||
|
||||
/**
|
||||
* 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 obj
|
||||
* @param obj
|
||||
* @param obj.pageName - Name of the page
|
||||
* @param obj.isProjectRelated - whether this page is related to project or now (determine if it's 2 or 3 navigation steps)
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export default function NavHeader({ pageName, isProjectRelated } : { pageName: string; isProjectRelated: boolean; }): JSX.Element {
|
||||
const [orgName, setOrgName] = useState("");
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
export default function NavHeader({
|
||||
pageName,
|
||||
isProjectRelated,
|
||||
}: {
|
||||
pageName: string;
|
||||
isProjectRelated?: boolean;
|
||||
}): JSX.Element {
|
||||
const [orgName, setOrgName] = useState('');
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const orgId = localStorage.getItem("orgData.id")
|
||||
const orgId = localStorage.getItem('orgData.id');
|
||||
const org = await getOrganization({
|
||||
orgId: orgId ? orgId : "",
|
||||
orgId: orgId ? orgId : '',
|
||||
});
|
||||
setOrgName(org.name);
|
||||
|
||||
@ -39,27 +43,27 @@ export default function NavHeader({ pageName, isProjectRelated } : { pageName: s
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="pt-20 ml-6 flex flex-row items-center">
|
||||
<div className="bg-primary-900 h-6 w-6 rounded-md flex items-center justify-center text-mineshaft-100 mr-2">
|
||||
<div className='pt-20 ml-6 flex flex-row items-center'>
|
||||
<div className='bg-primary-900 h-6 w-6 rounded-md flex items-center justify-center text-mineshaft-100 mr-2'>
|
||||
{orgName?.charAt(0)}
|
||||
</div>
|
||||
<div className="text-primary text-sm font-semibold">{orgName}</div>
|
||||
<div className='text-primary text-sm font-semibold'>{orgName}</div>
|
||||
{isProjectRelated && (
|
||||
<>
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleRight}
|
||||
className="ml-3 text-sm text-gray-400 mr-3"
|
||||
className='ml-3 text-sm text-gray-400 mr-3'
|
||||
/>
|
||||
<div className="font-semibold text-primary text-sm">
|
||||
<div className='font-semibold text-primary text-sm'>
|
||||
{workspaceName}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
icon={faAngleRight}
|
||||
className="ml-3 text-sm text-gray-400 mr-3"
|
||||
className='ml-3 text-sm text-gray-400 mr-3'
|
||||
/>
|
||||
<div className="text-gray-400 text-sm">{pageName}</div>
|
||||
<div className='text-gray-400 text-sm'>{pageName}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,47 +1,50 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import Button from "../basic/buttons/Button";
|
||||
import issueBackupKey from "../utilities/cryptography/issueBackupKey";
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { faWarning } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import Button from '../basic/buttons/Button';
|
||||
import issueBackupKey from '../utilities/cryptography/issueBackupKey';
|
||||
|
||||
interface DownloadBackupPDFStepProps {
|
||||
incrementStep: () => void;
|
||||
email: string;
|
||||
email: string;
|
||||
password: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the step of the signup flow where the user downloads the backup pdf
|
||||
* @param {object} obj
|
||||
* @param {object} obj
|
||||
* @param {function} obj.incrementStep - function that moves the user on to the next stage of signup
|
||||
* @param {string} obj.email - user's email
|
||||
* @param {string} obj.password - user's password
|
||||
* @param {string} obj.name - user's name
|
||||
* @returns
|
||||
* @param {string} obj.name - user's name
|
||||
* @returns
|
||||
*/
|
||||
export default function DonwloadBackupPDFStep({ incrementStep, email, password, name }: DownloadBackupPDFStepProps): JSX.Element {
|
||||
export default function DonwloadBackupPDFStep({
|
||||
incrementStep,
|
||||
email,
|
||||
password,
|
||||
name,
|
||||
}: DownloadBackupPDFStepProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
{t("signup:step4-message")}
|
||||
<div className='bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl'>
|
||||
<p className='text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
|
||||
{t('signup:step4-message')}
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
|
||||
<div>{t("signup:step4-description1")}</div>
|
||||
<div className="mt-3">{t("signup:step4-description2")}</div>
|
||||
<div className='flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2'>
|
||||
<div>{t('signup:step4-description1')}</div>
|
||||
<div className='mt-3'>{t('signup:step4-description2')}</div>
|
||||
</div>
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
|
||||
{t("signup:step4-description3")}
|
||||
<div className='w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4'>
|
||||
<FontAwesomeIcon icon={faWarning} className='ml-2 mr-4 text-4xl' />
|
||||
{t('signup:step4-description3')}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
|
||||
<div className='flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg'>
|
||||
<Button
|
||||
text="Download PDF"
|
||||
text='Download PDF'
|
||||
onButtonPressed={async () => {
|
||||
await issueBackupKey({
|
||||
email,
|
||||
@ -52,7 +55,7 @@ export default function DonwloadBackupPDFStep({ incrementStep, email, password,
|
||||
});
|
||||
incrementStep();
|
||||
}}
|
||||
size="lg"
|
||||
size='lg'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,20 +1,20 @@
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import completeAccountInformationSignup from "~/pages/api/auth/CompleteAccountInformationSignup";
|
||||
import completeAccountInformationSignup from '~/pages/api/auth/CompleteAccountInformationSignup';
|
||||
|
||||
import Button from "../basic/buttons/Button";
|
||||
import InputField from "../basic/InputField";
|
||||
import attemptLogin from "../utilities/attemptLogin";
|
||||
import passwordCheck from "../utilities/checks/PasswordCheck";
|
||||
import Aes256Gcm from "../utilities/cryptography/aes-256-gcm";
|
||||
import Button from '../basic/buttons/Button';
|
||||
import InputField from '../basic/InputField';
|
||||
import attemptLogin from '../utilities/attemptLogin';
|
||||
import passwordCheck from '../utilities/checks/PasswordCheck';
|
||||
import Aes256Gcm from '../utilities/cryptography/aes-256-gcm';
|
||||
|
||||
const nacl = require("tweetnacl");
|
||||
const jsrp = require("jsrp");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
const nacl = require('tweetnacl');
|
||||
const jsrp = require('jsrp');
|
||||
nacl.util = require('tweetnacl-util');
|
||||
const client = new jsrp.client();
|
||||
|
||||
interface UserInfoStepProps {
|
||||
@ -42,16 +42,16 @@ interface UserInfoStepProps {
|
||||
* @param {string} obj.lastName - user's lastName
|
||||
* @param {string} obj.setLastName - function managing the state of user's last name
|
||||
*/
|
||||
export default function UserInfoStep({
|
||||
verificationToken,
|
||||
incrementStep,
|
||||
export default function UserInfoStep({
|
||||
verificationToken,
|
||||
incrementStep,
|
||||
email,
|
||||
password,
|
||||
setPassword,
|
||||
firstName,
|
||||
setPassword,
|
||||
firstName,
|
||||
setFirstName,
|
||||
lastName,
|
||||
setLastName
|
||||
setLastName,
|
||||
}: UserInfoStepProps): JSX.Element {
|
||||
const [firstNameError, setFirstNameError] = useState(false);
|
||||
const [lastNameError, setLastNameError] = useState(false);
|
||||
@ -63,7 +63,7 @@ export default function UserInfoStep({
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
|
||||
// Verifies if the information that the users entered (name, workspace)
|
||||
// Verifies if the information that the users entered (name, workspace)
|
||||
// is there, and if the password matches the criteria.
|
||||
const signupErrorCheck = async () => {
|
||||
setIsLoading(true);
|
||||
@ -85,7 +85,7 @@ export default function UserInfoStep({
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck: errorCheck,
|
||||
errorCheck,
|
||||
});
|
||||
|
||||
if (!errorCheck) {
|
||||
@ -102,11 +102,11 @@ export default function UserInfoStep({
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
32 + (password.slice(0, 32).length - new Blob([password]).size),
|
||||
"0"
|
||||
'0'
|
||||
),
|
||||
}) as { ciphertext: string; iv: string; tag: string };
|
||||
|
||||
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
|
||||
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
|
||||
|
||||
client.init(
|
||||
{
|
||||
@ -134,10 +134,10 @@ export default function UserInfoStep({
|
||||
if (response.status === 200) {
|
||||
// response = await response.json();
|
||||
|
||||
localStorage.setItem("publicKey", PUBLIC_KEY);
|
||||
localStorage.setItem("encryptedPrivateKey", ciphertext);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
localStorage.setItem('publicKey', PUBLIC_KEY);
|
||||
localStorage.setItem('encryptedPrivateKey', ciphertext);
|
||||
localStorage.setItem('iv', iv);
|
||||
localStorage.setItem('tag', tag);
|
||||
|
||||
try {
|
||||
await attemptLogin(
|
||||
@ -163,45 +163,45 @@ export default function UserInfoStep({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-36 md:mb-16">
|
||||
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
{t("signup:step3-message")}
|
||||
<div className='bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-36 md:mb-16'>
|
||||
<p className='text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
|
||||
{t('signup:step3-message')}
|
||||
</p>
|
||||
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
|
||||
<div className='relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24'>
|
||||
<InputField
|
||||
label={t("common:first-name")}
|
||||
label={t('common:first-name')}
|
||||
onChangeHandler={setFirstName}
|
||||
type="name"
|
||||
type='name'
|
||||
value={firstName}
|
||||
isRequired
|
||||
errorText={
|
||||
t("common:validate-required", {
|
||||
name: t("common:first-name"),
|
||||
t('common:validate-required', {
|
||||
name: t('common:first-name'),
|
||||
}) as string
|
||||
}
|
||||
error={firstNameError}
|
||||
autoComplete="given-name"
|
||||
autoComplete='given-name'
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-center w-full md:p-2 rounded-lg max-h-24">
|
||||
<div className='mt-2 flex items-center justify-center w-full md:p-2 rounded-lg max-h-24'>
|
||||
<InputField
|
||||
label={t("common:last-name")}
|
||||
label={t('common:last-name')}
|
||||
onChangeHandler={setLastName}
|
||||
type="name"
|
||||
type='name'
|
||||
value={lastName}
|
||||
isRequired
|
||||
errorText={
|
||||
t("common:validate-required", {
|
||||
name: t("common:last-name"),
|
||||
t('common:validate-required', {
|
||||
name: t('common:last-name'),
|
||||
}) as string
|
||||
}
|
||||
error={lastNameError}
|
||||
autoComplete="family-name"
|
||||
autoComplete='family-name'
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
|
||||
<div className='mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60'>
|
||||
<InputField
|
||||
label={t("section-password:password")}
|
||||
label={t('section-password:password')}
|
||||
onChangeHandler={(password: string) => {
|
||||
setPassword(password);
|
||||
passwordCheck({
|
||||
@ -209,98 +209,98 @@ export default function UserInfoStep({
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck: false,
|
||||
errorCheck: false,
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
type='password'
|
||||
value={password}
|
||||
isRequired
|
||||
error={
|
||||
passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase
|
||||
}
|
||||
autoComplete="new-password"
|
||||
id="new-password"
|
||||
autoComplete='new-password'
|
||||
id='new-password'
|
||||
/>
|
||||
{passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber ? (
|
||||
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
|
||||
<div className='w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md'>
|
||||
<div className={`text-gray-400 text-sm mb-1`}>
|
||||
{t("section-password:validate-base")}
|
||||
{t('section-password:validate-base')}
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
className='text-md text-red mr-2.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLength ? "text-gray-400" : "text-gray-600"
|
||||
passwordErrorLength ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
{t("section-password:validate-length")}
|
||||
{t('section-password:validate-length')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
className='text-md text-red mr-2.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"
|
||||
passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
{t("section-password:validate-case")}
|
||||
{t('section-password:validate-case')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
className='text-md text-red mr-2.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
|
||||
passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
{t("section-password:validate-number")}
|
||||
{t('section-password:validate-number')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2"></div>
|
||||
<div className='py-2'></div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center md:p-2 max-h-48 max-w-max mx-auto text-lg px-2 py-3">
|
||||
<div className='flex flex-col items-center justify-center md:p-2 max-h-48 max-w-max mx-auto text-lg px-2 py-3'>
|
||||
<Button
|
||||
text={t("signup:signup") ?? ""}
|
||||
text={t('signup:signup') ?? ''}
|
||||
loading={isLoading}
|
||||
onButtonPressed={signupErrorCheck}
|
||||
size="lg"
|
||||
size='lg'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
interface PasswordCheckProps {
|
||||
password: string;
|
||||
currentErrorCheck: boolean;
|
||||
errorCheck: boolean;
|
||||
setPasswordErrorLength: (value: boolean) => void;
|
||||
setPasswordErrorNumber: (value: boolean) => void;
|
||||
setPasswordErrorLowerCase: (value: boolean) => void;
|
||||
@ -14,9 +14,8 @@ const passwordCheck = ({
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck,
|
||||
errorCheck,
|
||||
}: PasswordCheckProps) => {
|
||||
let errorCheck = currentErrorCheck;
|
||||
if (!password || password.length < 14) {
|
||||
setPasswordErrorLength(true);
|
||||
errorCheck = true;
|
||||
|
@ -1,11 +1,10 @@
|
||||
import jsrp from 'jsrp';
|
||||
|
||||
import changePassword2 from '~/pages/api/auth/ChangePassword2';
|
||||
import SRP1 from '~/pages/api/auth/SRP1';
|
||||
|
||||
import Aes256Gcm from './aes-256-gcm';
|
||||
|
||||
const nacl = require('tweetnacl');
|
||||
nacl.util = require('tweetnacl-util');
|
||||
const jsrp = require('jsrp');
|
||||
const clientOldPassword = new jsrp.client();
|
||||
const clientNewPassword = new jsrp.client();
|
||||
|
||||
@ -19,13 +18,13 @@ const clientNewPassword = new jsrp.client();
|
||||
* @returns
|
||||
*/
|
||||
const changePassword = async (
|
||||
email,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
setCurrentPasswordError,
|
||||
setPasswordChanged,
|
||||
setCurrentPassword,
|
||||
setNewPassword
|
||||
email: string,
|
||||
currentPassword: string,
|
||||
newPassword: string,
|
||||
setCurrentPasswordError: (arg: boolean) => void,
|
||||
setPasswordChanged: (arg: boolean) => void,
|
||||
setCurrentPassword: (arg: string) => void,
|
||||
setNewPassword: (arg: string) => void
|
||||
) => {
|
||||
try {
|
||||
setPasswordChanged(false);
|
||||
@ -34,7 +33,7 @@ const changePassword = async (
|
||||
clientOldPassword.init(
|
||||
{
|
||||
username: email,
|
||||
password: currentPassword
|
||||
password: currentPassword,
|
||||
},
|
||||
async () => {
|
||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
||||
@ -42,7 +41,7 @@ const changePassword = async (
|
||||
let serverPublicKey, salt;
|
||||
try {
|
||||
const res = await SRP1({
|
||||
clientPublicKey: clientPublicKey
|
||||
clientPublicKey: clientPublicKey,
|
||||
});
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
@ -58,13 +57,13 @@ const changePassword = async (
|
||||
clientNewPassword.init(
|
||||
{
|
||||
username: email,
|
||||
password: newPassword
|
||||
password: newPassword,
|
||||
},
|
||||
async () => {
|
||||
clientNewPassword.createVerifier(async (err, result) => {
|
||||
// The Blob part here is needed to account for symbols that count as 2+ bytes (e.g., é, å, ø)
|
||||
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
|
||||
text: localStorage.getItem('PRIVATE_KEY'),
|
||||
text: localStorage.getItem('PRIVATE_KEY') as string,
|
||||
secret: newPassword
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
@ -72,7 +71,7 @@ const changePassword = async (
|
||||
(newPassword.slice(0, 32).length -
|
||||
new Blob([newPassword]).size),
|
||||
'0'
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
if (ciphertext) {
|
||||
@ -88,11 +87,11 @@ const changePassword = async (
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
clientProof
|
||||
clientProof,
|
||||
});
|
||||
if (res.status == 400) {
|
||||
if (res && res.status == 400) {
|
||||
setCurrentPasswordError(true);
|
||||
} else if (res.status == 200) {
|
||||
} else if (res && res.status == 200) {
|
||||
setPasswordChanged(true);
|
||||
setCurrentPassword('');
|
||||
setNewPassword('');
|
@ -22,8 +22,11 @@ type encryptAsymmetricProps = {
|
||||
const encryptAssymmetric = ({
|
||||
plaintext,
|
||||
publicKey,
|
||||
privateKey
|
||||
}: encryptAsymmetricProps): object => {
|
||||
privateKey,
|
||||
}: encryptAsymmetricProps): {
|
||||
ciphertext: string;
|
||||
nonce: string;
|
||||
} => {
|
||||
const nonce = nacl.randomBytes(24);
|
||||
const ciphertext = nacl.box(
|
||||
nacl.util.decodeUTF8(plaintext),
|
||||
@ -34,7 +37,7 @@ const encryptAssymmetric = ({
|
||||
|
||||
return {
|
||||
ciphertext: nacl.util.encodeBase64(ciphertext),
|
||||
nonce: nacl.util.encodeBase64(nonce)
|
||||
nonce: nacl.util.encodeBase64(nonce),
|
||||
};
|
||||
};
|
||||
|
||||
@ -58,7 +61,7 @@ const decryptAssymmetric = ({
|
||||
ciphertext,
|
||||
nonce,
|
||||
publicKey,
|
||||
privateKey
|
||||
privateKey,
|
||||
}: decryptAsymmetricProps): string => {
|
||||
const plaintext = nacl.box.open(
|
||||
nacl.util.decodeBase64(ciphertext),
|
||||
@ -83,7 +86,7 @@ type encryptSymmetricProps = {
|
||||
*/
|
||||
const encryptSymmetric = ({
|
||||
plaintext,
|
||||
key
|
||||
key,
|
||||
}: encryptSymmetricProps): object => {
|
||||
let ciphertext, iv, tag;
|
||||
try {
|
||||
@ -100,7 +103,7 @@ const encryptSymmetric = ({
|
||||
return {
|
||||
ciphertext,
|
||||
iv,
|
||||
tag
|
||||
tag,
|
||||
};
|
||||
};
|
||||
|
||||
@ -125,7 +128,7 @@ const decryptSymmetric = ({
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
key
|
||||
key,
|
||||
}: decryptSymmetricProps): string => {
|
||||
let plaintext;
|
||||
try {
|
||||
@ -142,5 +145,5 @@ export {
|
||||
decryptAssymmetric,
|
||||
decryptSymmetric,
|
||||
encryptAssymmetric,
|
||||
encryptSymmetric
|
||||
encryptSymmetric,
|
||||
};
|
||||
|
@ -1,42 +0,0 @@
|
||||
/* eslint-disable */
|
||||
import { initPostHog } from "~/components/analytics/posthog";
|
||||
import { ENV } from "~/components/utilities/config";
|
||||
|
||||
class Capturer {
|
||||
constructor() {
|
||||
this.api = initPostHog();
|
||||
}
|
||||
|
||||
capture(item) {
|
||||
if (ENV == "production" && TELEMETRY_CAPTURING_ENABLED) {
|
||||
try {
|
||||
this.api.capture(item);
|
||||
} catch (error) {
|
||||
console.error("PostHog", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
identify(id) {
|
||||
if (ENV == "production" && TELEMETRY_CAPTURING_ENABLED) {
|
||||
try {
|
||||
this.api.identify(id);
|
||||
} catch (error) {
|
||||
console.error("PostHog", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default class Telemetry {
|
||||
constructor() {
|
||||
if (!Telemetry.instance) {
|
||||
Telemetry.instance = new Capturer();
|
||||
}
|
||||
}
|
||||
|
||||
getInstance() {
|
||||
return Telemetry.instance;
|
||||
}
|
||||
}
|
48
frontend/components/utilities/telemetry/Telemetry.ts
Normal file
48
frontend/components/utilities/telemetry/Telemetry.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/* eslint-disable */
|
||||
import { PostHog } from 'posthog-js';
|
||||
import { initPostHog } from '~/components/analytics/posthog';
|
||||
import { ENV } from '~/components/utilities/config';
|
||||
|
||||
declare let TELEMETRY_CAPTURING_ENABLED: any;
|
||||
|
||||
class Capturer {
|
||||
api: PostHog;
|
||||
|
||||
constructor() {
|
||||
this.api = initPostHog()!;
|
||||
}
|
||||
|
||||
capture(item: string) {
|
||||
if (ENV == 'production' && TELEMETRY_CAPTURING_ENABLED) {
|
||||
try {
|
||||
this.api.capture(item);
|
||||
} catch (error) {
|
||||
console.error('PostHog', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
identify(id: string) {
|
||||
if (ENV == 'production' && TELEMETRY_CAPTURING_ENABLED) {
|
||||
try {
|
||||
this.api.identify(id);
|
||||
} catch (error) {
|
||||
console.error('PostHog', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class Telemetry {
|
||||
static instance: Capturer;
|
||||
|
||||
constructor() {
|
||||
if (!Telemetry.instance) {
|
||||
Telemetry.instance = new Capturer();
|
||||
}
|
||||
}
|
||||
|
||||
getInstance() {
|
||||
return Telemetry.instance;
|
||||
}
|
||||
}
|
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@ -4,7 +4,6 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"dependencies": {
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/server": "^11.10.0",
|
||||
@ -55,6 +54,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/jsrp": "^0.2.4",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/react": "^18.0.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
@ -1105,6 +1105,12 @@
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/jsrp": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsrp/-/jsrp-0.2.4.tgz",
|
||||
"integrity": "sha512-9RerZiVclCJNQrAaUH71s7tQpIlvyrM+1oWYwaRW+9xYYG0p2rnqSZMS0j+W+bL1tkBRUQUyXiSEUTnbv8HQjA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
|
||||
@ -8675,6 +8681,12 @@
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/jsrp": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsrp/-/jsrp-0.2.4.tgz",
|
||||
"integrity": "sha512-9RerZiVclCJNQrAaUH71s7tQpIlvyrM+1oWYwaRW+9xYYG0p2rnqSZMS0j+W+bL1tkBRUQUyXiSEUTnbv8HQjA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mdast": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
|
||||
|
@ -6,7 +6,8 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"start:docker": "next build && next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"type-check": "tsc --project tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "^11.10.0",
|
||||
@ -58,6 +59,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/jsrp": "^0.2.4",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/react": "^18.0.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
|
@ -1,26 +1,37 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Head from 'next/head';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Custom404() {
|
||||
return (
|
||||
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
|
||||
<div className='bg-bunker-800 md:h-screen flex flex-col justify-between'>
|
||||
<Head>
|
||||
<title>Infisical | Page Not Found</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<div className="flex flex-col items-center justify-center text-gray-200 h-screen w-screen">
|
||||
<p className="text-4xl mt-32">Oops, something went wrong</p>
|
||||
<p className="mt-2 mb-1 text-lg">Think this is a mistake? Email <a className="text-primary underline underline-offset-4" href="mailto:team@infisical.com">team@infisical.com</a> and we`ll fix it! </p>
|
||||
<Link href="/dashboard" className="bg-mineshaft-500 mt-8 py-2 px-4 rounded-md hover:bg-primary diration-200 hover:text-black cursor-pointer font-semibold">
|
||||
<div className='flex flex-col items-center justify-center text-gray-200 h-screen w-screen'>
|
||||
<p className='text-4xl mt-32'>Oops, something went wrong</p>
|
||||
<p className='mt-2 mb-1 text-lg'>
|
||||
Think this is a mistake? Email{' '}
|
||||
<a
|
||||
className='text-primary underline underline-offset-4'
|
||||
href='mailto:team@infisical.com'
|
||||
>
|
||||
team@infisical.com
|
||||
</a>{' '}
|
||||
and we`ll fix it!{' '}
|
||||
</p>
|
||||
<Link
|
||||
href='/dashboard'
|
||||
className='bg-mineshaft-500 mt-8 py-2 px-4 rounded-md hover:bg-primary diration-200 hover:text-black cursor-pointer font-semibold'
|
||||
>
|
||||
Go to Dashboard
|
||||
</Link>
|
||||
<Image
|
||||
src="/images/dragon-404.svg"
|
||||
src='/images/dragon-404.svg'
|
||||
height={554}
|
||||
width={942}
|
||||
alt="infisical dragon - page not found"
|
||||
alt='infisical dragon - page not found'
|
||||
></Image>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,17 +1,35 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
|
||||
export interface IMembershipOrg {
|
||||
_id: string;
|
||||
user: {
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
_id: string;
|
||||
publicKey: string;
|
||||
};
|
||||
inviteEmail: string;
|
||||
organization: string;
|
||||
role: 'owner' | 'admin' | 'member';
|
||||
status: 'invited' | 'accepted';
|
||||
}
|
||||
/**
|
||||
* This route lets us get all the users in an org.
|
||||
* @param {object} obj
|
||||
* @param {string} obj.orgId - organization Id
|
||||
* @returns
|
||||
*/
|
||||
const getOrganizationUsers = ({ orgId }: { orgId: string }) => {
|
||||
const getOrganizationUsers = ({
|
||||
orgId,
|
||||
}: {
|
||||
orgId: string;
|
||||
}): Promise<IMembershipOrg[]> => {
|
||||
return SecurityClient.fetchCall('/api/v1/organization/' + orgId + '/users', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res?.status == 200) {
|
||||
return (await res.json()).users;
|
||||
|
@ -1,18 +1,25 @@
|
||||
import SecurityClient from '~/utilities/SecurityClient';
|
||||
|
||||
export interface IIncidentContactOrg {
|
||||
_id: string;
|
||||
email: string;
|
||||
organization: string;
|
||||
}
|
||||
/**
|
||||
* This routes gets all the incident contacts of a certain organization
|
||||
* @param {*} workspaceId
|
||||
* @returns
|
||||
*/
|
||||
const getIncidentContacts = (organizationId: string) => {
|
||||
const getIncidentContacts = (
|
||||
organizationId: string
|
||||
): Promise<IIncidentContactOrg[]> => {
|
||||
return SecurityClient.fetchCall(
|
||||
'/api/v1/organization/' + organizationId + '/incidentContactOrg',
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res && res.status == 200) {
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
|
||||
export default function DashboardRedirect() {
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
if (localStorage.getItem("projectData.id")) {
|
||||
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
|
||||
} else {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
DashboardRedirect.requireAuth = true;
|
34
frontend/pages/dashboard.tsx
Normal file
34
frontend/pages/dashboard.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import getWorkspaces from './api/workspace/getWorkspaces';
|
||||
|
||||
export default function DashboardRedirect() {
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
if (localStorage.getItem('projectData.id')) {
|
||||
router.push('/dashboard/' + localStorage.getItem('projectData.id'));
|
||||
} else {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push('/dashboard/' + userWorkspace);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error - Not logged in yet');
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
DashboardRedirect.requireAuth = true;
|
@ -1,37 +0,0 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
const queryString = require("query-string");
|
||||
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
|
||||
|
||||
export default function Github() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
try {
|
||||
if (state === localStorage.getItem('latestCSRFToken')) {
|
||||
localStorage.removeItem('latestCSRFToken');
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem('projectData.id'),
|
||||
code,
|
||||
integration: "github",
|
||||
});
|
||||
router.push("/integrations/" + localStorage.getItem("projectData.id"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Github integration error: ', error);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Github.requireAuth = true;
|
41
frontend/pages/github.tsx
Normal file
41
frontend/pages/github.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import AuthorizeIntegration from './api/integrations/authorizeIntegration';
|
||||
|
||||
export default function Github() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
if (state === localStorage.getItem('latestCSRFToken')) {
|
||||
localStorage.removeItem('latestCSRFToken');
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem('projectData.id') as string,
|
||||
code: code as string,
|
||||
integration: 'github',
|
||||
});
|
||||
router.push(
|
||||
'/integrations/' + localStorage.getItem('projectData.id')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Github integration error: ', error);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Github.requireAuth = true;
|
@ -1,37 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
const queryString = require("query-string");
|
||||
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
|
||||
|
||||
export default function Heroku() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
try {
|
||||
if (state === localStorage.getItem('latestCSRFToken')) {
|
||||
localStorage.removeItem('latestCSRFToken');
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem('projectData.id'),
|
||||
code,
|
||||
integration: "heroku",
|
||||
});
|
||||
router.push("/integrations/" + localStorage.getItem("projectData.id"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Heroku integration error: ', error);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Heroku.requireAuth = true;
|
41
frontend/pages/heroku.tsx
Normal file
41
frontend/pages/heroku.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import AuthorizeIntegration from './api/integrations/authorizeIntegration';
|
||||
|
||||
export default function Heroku() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
if (state === localStorage.getItem('latestCSRFToken')) {
|
||||
localStorage.removeItem('latestCSRFToken');
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem('projectData.id') as string,
|
||||
code: code as string,
|
||||
integration: 'heroku',
|
||||
});
|
||||
router.push(
|
||||
'/integrations/' + localStorage.getItem('projectData.id')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Heroku integration error: ', error);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Heroku.requireAuth = true;
|
@ -1,242 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import {
|
||||
faArrowRight,
|
||||
faCheck,
|
||||
faRotate,
|
||||
faX,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import ActivateBotDialog from "~/components/basic/dialog/ActivateBotDialog";
|
||||
import CloudIntegrationSection from "~/components/integrations/CloudIntegrationSection";
|
||||
import FrameworkIntegrationSection from "~/components/integrations/FrameworkIntegrationSection";
|
||||
import IntegrationSection from "~/components/integrations/IntegrationSection";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
||||
|
||||
import frameworkIntegrationOptions from "../../public/json/frameworkIntegrations.json";
|
||||
import getBot from "../api/bot/getBot";
|
||||
import setBotActiveStatus from "../api/bot/setBotActiveStatus";
|
||||
import getIntegrationOptions from "../api/integrations/GetIntegrationOptions";
|
||||
import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations";
|
||||
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations";
|
||||
import getAWorkspace from "../api/workspace/getAWorkspace";
|
||||
import getLatestFileKey from "../api/workspace/getLatestFileKey";
|
||||
const {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric
|
||||
} = require('../../components/utilities/cryptography/crypto');
|
||||
const crypto = require("crypto");
|
||||
|
||||
export default function Integrations() {
|
||||
const [cloudIntegrationOptions, setCloudIntegrationOptions] = useState([]);
|
||||
const [integrationAuths, setIntegrationAuths] = useState([]);
|
||||
const [environments,setEnvironments] = useState([])
|
||||
const [integrations, setIntegrations] = useState([]);
|
||||
const [bot, setBot] = useState(null);
|
||||
const [isActivateBotDialogOpen, setIsActivateBotDialogOpen] = useState(false);
|
||||
// const [isIntegrationAccessTokenDialogOpen, setIntegrationAccessTokenDialogOpen] = useState(true);
|
||||
const [selectedIntegrationOption, setSelectedIntegrationOption] = useState(null);
|
||||
|
||||
const router = useRouter();
|
||||
const workspaceId = router.query.id;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(async () => {
|
||||
try {
|
||||
const workspace = await getAWorkspace(workspaceId);
|
||||
setEnvironments(workspace.environments);
|
||||
|
||||
// get cloud integration options
|
||||
setCloudIntegrationOptions(
|
||||
await getIntegrationOptions()
|
||||
);
|
||||
|
||||
// get project integration authorizations
|
||||
setIntegrationAuths(
|
||||
await getWorkspaceAuthorizations({
|
||||
workspaceId
|
||||
})
|
||||
);
|
||||
|
||||
// get project integrations
|
||||
setIntegrations(
|
||||
await getWorkspaceIntegrations({
|
||||
workspaceId,
|
||||
})
|
||||
);
|
||||
|
||||
// get project bot
|
||||
setBot(await getBot({ workspaceId }));
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Activate bot for project by performing the following steps:
|
||||
* 1. Get the (encrypted) project key
|
||||
* 2. Decrypt project key with user's private key
|
||||
* 3. Encrypt project key with bot's public key
|
||||
* 4. Send encrypted project key to backend and set bot status to active
|
||||
*/
|
||||
const handleBotActivate = async () => {
|
||||
let botKey;
|
||||
try {
|
||||
|
||||
if (bot) {
|
||||
// case: there is a bot
|
||||
const key = await getLatestFileKey({ workspaceId });
|
||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
||||
|
||||
const WORKSPACE_KEY = decryptAssymmetric({
|
||||
ciphertext: key.latestKey.encryptedKey,
|
||||
nonce: key.latestKey.nonce,
|
||||
publicKey: key.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: WORKSPACE_KEY,
|
||||
publicKey: bot.publicKey,
|
||||
privateKey: PRIVATE_KEY
|
||||
});
|
||||
|
||||
botKey = {
|
||||
encryptedKey: ciphertext,
|
||||
nonce
|
||||
}
|
||||
|
||||
setBot((await setBotActiveStatus({
|
||||
botId: bot._id,
|
||||
isActive: bot.isActive ? false : true,
|
||||
botKey
|
||||
})).bot);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start integration for a given integration option [integrationOption]
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.integrationOption - an integration option
|
||||
* @param {String} obj.name
|
||||
* @param {String} obj.type
|
||||
* @param {String} obj.docsLink
|
||||
* @returns
|
||||
*/
|
||||
const handleIntegrationOption = async ({ integrationOption }) => {
|
||||
try {
|
||||
// generate CSRF token for OAuth2 code-token exchange integrations
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem('latestCSRFToken', state);
|
||||
|
||||
switch (integrationOption.name) {
|
||||
case 'Heroku':
|
||||
window.location = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
|
||||
break;
|
||||
case 'Vercel':
|
||||
window.location = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`;
|
||||
break;
|
||||
case 'Netlify':
|
||||
window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/netlify`;
|
||||
break;
|
||||
case 'GitHub':
|
||||
window.location = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/github&state=${state}`;
|
||||
break;
|
||||
// case 'Fly.io':
|
||||
// console.log('fly.io');
|
||||
// setIntegrationAccessTokenDialogOpen(true);
|
||||
// break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open dialog to activate bot if bot is not active.
|
||||
* Otherwise, start integration [integrationOption]
|
||||
* @param {Object} integrationOption - an integration option
|
||||
* @param {String} integrationOption.name
|
||||
* @param {String} integrationOption.type
|
||||
* @param {String} integrationOption.docsLink
|
||||
* @returns
|
||||
*/
|
||||
const integrationOptionPress = (integrationOption) => {
|
||||
try {
|
||||
if (bot.isActive) {
|
||||
// case: bot is active -> proceed with integration
|
||||
handleIntegrationOption({ integrationOption });
|
||||
return;
|
||||
}
|
||||
|
||||
// case: bot is not active -> open modal to activate bot
|
||||
setIsActivateBotDialogOpen(true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>
|
||||
{t("common:head-title", { title: t("integrations:title") })}
|
||||
</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||
<meta name="og:description" content={t("integrations:description")} />
|
||||
</Head>
|
||||
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||
<NavHeader
|
||||
pageName={t("integrations:title")}
|
||||
isProjectRelated={true}
|
||||
/>
|
||||
<ActivateBotDialog
|
||||
isOpen={isActivateBotDialogOpen}
|
||||
closeModal={() => setIsActivateBotDialogOpen(false)}
|
||||
selectedIntegrationOption={selectedIntegrationOption}
|
||||
handleBotActivate={handleBotActivate}
|
||||
handleIntegrationOption={handleIntegrationOption}
|
||||
/>
|
||||
{/* <IntegrationAccessTokenDialog
|
||||
isOpen={isIntegrationAccessTokenDialogOpen}
|
||||
closeModal={() => setIntegrationAccessTokenDialogOpen(false)}
|
||||
selectedIntegrationOption={selectedIntegrationOption}
|
||||
handleBotActivate={handleBotActivate}
|
||||
handleIntegrationOption={handleIntegrationOption}
|
||||
/> */}
|
||||
<IntegrationSection integrations={integrations} environments={environments} />
|
||||
{(cloudIntegrationOptions.length > 0 && bot) ? (
|
||||
<CloudIntegrationSection
|
||||
cloudIntegrationOptions={cloudIntegrationOptions}
|
||||
setSelectedIntegrationOption={setSelectedIntegrationOption}
|
||||
integrationOptionPress={integrationOptionPress}
|
||||
integrationAuths={integrationAuths}
|
||||
/>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
<FrameworkIntegrationSection
|
||||
frameworks={frameworkIntegrationOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Integrations.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps([
|
||||
"integrations",
|
||||
]);
|
263
frontend/pages/integrations/[id].tsx
Normal file
263
frontend/pages/integrations/[id].tsx
Normal file
@ -0,0 +1,263 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import ActivateBotDialog from '~/components/basic/dialog/ActivateBotDialog';
|
||||
import CloudIntegrationSection from '~/components/integrations/CloudIntegrationSection';
|
||||
import FrameworkIntegrationSection from '~/components/integrations/FrameworkIntegrationSection';
|
||||
import IntegrationSection from '~/components/integrations/IntegrationSection';
|
||||
import NavHeader from '~/components/navigation/NavHeader';
|
||||
import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
|
||||
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
} from '../../components/utilities/cryptography/crypto';
|
||||
import frameworkIntegrationOptions from '../../public/json/frameworkIntegrations.json';
|
||||
import getBot from '../api/bot/getBot';
|
||||
import setBotActiveStatus from '../api/bot/setBotActiveStatus';
|
||||
import getIntegrationOptions from '../api/integrations/GetIntegrationOptions';
|
||||
import getWorkspaceAuthorizations from '../api/integrations/getWorkspaceAuthorizations';
|
||||
import getWorkspaceIntegrations from '../api/integrations/getWorkspaceIntegrations';
|
||||
import getAWorkspace from '../api/workspace/getAWorkspace';
|
||||
import getLatestFileKey from '../api/workspace/getLatestFileKey';
|
||||
|
||||
export default function Integrations() {
|
||||
const [cloudIntegrationOptions, setCloudIntegrationOptions] = useState([]);
|
||||
const [integrationAuths, setIntegrationAuths] = useState([]);
|
||||
const [environments, setEnvironments] = useState<
|
||||
{
|
||||
name: string;
|
||||
slug: string;
|
||||
}[]
|
||||
>([]);
|
||||
const [integrations, setIntegrations] = useState([]);
|
||||
// TODO: These will have its type when migratiing towards react-query
|
||||
const [bot, setBot] = useState<any>(null);
|
||||
const [isActivateBotDialogOpen, setIsActivateBotDialogOpen] = useState(false);
|
||||
// const [isIntegrationAccessTokenDialogOpen, setIntegrationAccessTokenDialogOpen] = useState(true);
|
||||
const [selectedIntegrationOption, setSelectedIntegrationOption] =
|
||||
useState(null);
|
||||
|
||||
const router = useRouter();
|
||||
const workspaceId = router.query.id as string;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const workspace = await getAWorkspace(workspaceId);
|
||||
setEnvironments(workspace.environments);
|
||||
|
||||
// get cloud integration options
|
||||
setCloudIntegrationOptions(await getIntegrationOptions());
|
||||
|
||||
// get project integration authorizations
|
||||
setIntegrationAuths(
|
||||
await getWorkspaceAuthorizations({
|
||||
workspaceId,
|
||||
})
|
||||
);
|
||||
|
||||
// get project integrations
|
||||
setIntegrations(
|
||||
await getWorkspaceIntegrations({
|
||||
workspaceId,
|
||||
})
|
||||
);
|
||||
|
||||
// get project bot
|
||||
setBot(await getBot({ workspaceId }));
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Activate bot for project by performing the following steps:
|
||||
* 1. Get the (encrypted) project key
|
||||
* 2. Decrypt project key with user's private key
|
||||
* 3. Encrypt project key with bot's public key
|
||||
* 4. Send encrypted project key to backend and set bot status to active
|
||||
*/
|
||||
const handleBotActivate = async () => {
|
||||
let botKey;
|
||||
try {
|
||||
if (bot) {
|
||||
// case: there is a bot
|
||||
const key = await getLatestFileKey({ workspaceId });
|
||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
||||
|
||||
if (!PRIVATE_KEY) {
|
||||
throw new Error('Private Key missing');
|
||||
}
|
||||
|
||||
const WORKSPACE_KEY = decryptAssymmetric({
|
||||
ciphertext: key.latestKey.encryptedKey,
|
||||
nonce: key.latestKey.nonce,
|
||||
publicKey: key.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: WORKSPACE_KEY,
|
||||
publicKey: bot.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
botKey = {
|
||||
encryptedKey: ciphertext,
|
||||
nonce,
|
||||
};
|
||||
|
||||
setBot(
|
||||
(
|
||||
await setBotActiveStatus({
|
||||
botId: bot._id,
|
||||
isActive: bot.isActive ? false : true,
|
||||
botKey,
|
||||
})
|
||||
).bot
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start integration for a given integration option [integrationOption]
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.integrationOption - an integration option
|
||||
* @param {String} obj.name
|
||||
* @param {String} obj.type
|
||||
* @param {String} obj.docsLink
|
||||
* @returns
|
||||
*/
|
||||
const handleIntegrationOption = async ({
|
||||
integrationOption,
|
||||
}: {
|
||||
integrationOption: any;
|
||||
}) => {
|
||||
try {
|
||||
// generate CSRF token for OAuth2 code-token exchange integrations
|
||||
const state = crypto.randomBytes(16).toString('hex');
|
||||
localStorage.setItem('latestCSRFToken', state);
|
||||
|
||||
switch (integrationOption.name) {
|
||||
case 'Heroku':
|
||||
window.location.assign(
|
||||
`https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`
|
||||
);
|
||||
break;
|
||||
case 'Vercel':
|
||||
window.location.assign(
|
||||
`https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`
|
||||
);
|
||||
break;
|
||||
case 'Netlify':
|
||||
window.location.assign(
|
||||
`https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/netlify`
|
||||
);
|
||||
break;
|
||||
case 'GitHub':
|
||||
window.location.assign(
|
||||
`https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/github&state=${state}`
|
||||
);
|
||||
break;
|
||||
// case 'Fly.io':
|
||||
// console.log('fly.io');
|
||||
// setIntegrationAccessTokenDialogOpen(true);
|
||||
// break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Open dialog to activate bot if bot is not active.
|
||||
* Otherwise, start integration [integrationOption]
|
||||
* @param {Object} integrationOption - an integration option
|
||||
* @param {String} integrationOption.name
|
||||
* @param {String} integrationOption.type
|
||||
* @param {String} integrationOption.docsLink
|
||||
* @returns
|
||||
*/
|
||||
const integrationOptionPress = (integrationOption: any) => {
|
||||
try {
|
||||
if (bot.isActive) {
|
||||
// case: bot is active -> proceed with integration
|
||||
handleIntegrationOption({ integrationOption });
|
||||
return;
|
||||
}
|
||||
|
||||
// case: bot is not active -> open modal to activate bot
|
||||
setIsActivateBotDialogOpen(true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='bg-bunker-800 max-h-screen flex flex-col justify-between text-white'>
|
||||
<Head>
|
||||
<title>
|
||||
{t('common:head-title', { title: t('integrations:title') })}
|
||||
</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
<meta property='og:image' content='/images/message.png' />
|
||||
<meta property='og:title' content='Manage your .env files in seconds' />
|
||||
<meta
|
||||
name='og:description'
|
||||
content={t('integrations:description') as string}
|
||||
/>
|
||||
</Head>
|
||||
<div className='w-full pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar'>
|
||||
<NavHeader pageName={t('integrations:title')} isProjectRelated={true} />
|
||||
<ActivateBotDialog
|
||||
isOpen={isActivateBotDialogOpen}
|
||||
closeModal={() => setIsActivateBotDialogOpen(false)}
|
||||
selectedIntegrationOption={selectedIntegrationOption}
|
||||
handleBotActivate={handleBotActivate}
|
||||
handleIntegrationOption={handleIntegrationOption}
|
||||
/>
|
||||
{/* <IntegrationAccessTokenDialog
|
||||
isOpen={isIntegrationAccessTokenDialogOpen}
|
||||
closeModal={() => setIntegrationAccessTokenDialogOpen(false)}
|
||||
selectedIntegrationOption={selectedIntegrationOption}
|
||||
handleBotActivate={handleBotActivate}
|
||||
handleIntegrationOption={handleIntegrationOption}
|
||||
/> */}
|
||||
<IntegrationSection
|
||||
integrations={integrations}
|
||||
environments={environments}
|
||||
/>
|
||||
{cloudIntegrationOptions.length > 0 && bot ? (
|
||||
<CloudIntegrationSection
|
||||
cloudIntegrationOptions={cloudIntegrationOptions}
|
||||
setSelectedIntegrationOption={setSelectedIntegrationOption as any}
|
||||
integrationOptionPress={integrationOptionPress as any}
|
||||
integrationAuths={integrationAuths}
|
||||
/>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
<FrameworkIntegrationSection
|
||||
frameworks={frameworkIntegrationOptions as any}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Integrations.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps([
|
||||
'integrations',
|
||||
]);
|
@ -1,41 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
const queryString = require("query-string");
|
||||
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
|
||||
|
||||
export default function Netlify() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
// modify comment here
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
try {
|
||||
if (state === localStorage.getItem('latestCSRFToken')) {
|
||||
localStorage.removeItem('latestCSRFToken');
|
||||
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem('projectData.id'),
|
||||
code,
|
||||
integration: "netlify"
|
||||
});
|
||||
|
||||
router.push("/integrations/" + localStorage.getItem("projectData.id"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Netlify integration error: ', err);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Netlify.requireAuth = true;
|
46
frontend/pages/netlify.tsx
Normal file
46
frontend/pages/netlify.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import AuthorizeIntegration from './api/integrations/authorizeIntegration';
|
||||
|
||||
export default function Netlify() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
// modify comment here
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
if (!code) throw new Error('Code not found');
|
||||
|
||||
if (state === localStorage.getItem('latestCSRFToken')) {
|
||||
localStorage.removeItem('latestCSRFToken');
|
||||
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem('projectData.id') as string,
|
||||
code: code as string,
|
||||
integration: 'netlify',
|
||||
});
|
||||
|
||||
router.push(
|
||||
'/integrations/' + localStorage.getItem('projectData.id')
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Netlify integration error: ', err);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Netlify.requireAuth = true;
|
@ -1,35 +0,0 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
||||
|
||||
export default function NoProjects() {
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
|
||||
<div
|
||||
className="mb-4 mr-16"
|
||||
>
|
||||
<Image
|
||||
src="/images/dragon-cant-find.png"
|
||||
height={270}
|
||||
width={436}
|
||||
alt="google logo"
|
||||
></Image>
|
||||
</div>
|
||||
<div className="p-4 rounded-md bg-bunker-500 mb-8 text-bunker-300 shadow-xl">
|
||||
<div className="max-w-md">
|
||||
You are not part of any projects in this organization yet. When you do,
|
||||
they will appear here.
|
||||
</div>
|
||||
<div className="max-w-md mt-4">
|
||||
Create a new project, or ask other organization members to give you
|
||||
neccessary permissions.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
NoProjects.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps(["noprojects"]);
|
32
frontend/pages/noprojects.tsx
Normal file
32
frontend/pages/noprojects.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
|
||||
|
||||
export default function NoProjects() {
|
||||
return (
|
||||
<div className='h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto'>
|
||||
<div className='mb-4 mr-16'>
|
||||
<Image
|
||||
src='/images/dragon-cant-find.png'
|
||||
height={270}
|
||||
width={436}
|
||||
alt='google logo'
|
||||
></Image>
|
||||
</div>
|
||||
<div className='p-4 rounded-md bg-bunker-500 mb-8 text-bunker-300 shadow-xl'>
|
||||
<div className='max-w-md'>
|
||||
You are not part of any projects in this organization yet. When you
|
||||
do, they will appear here.
|
||||
</div>
|
||||
<div className='max-w-md mt-4'>
|
||||
Create a new project, or ask other organization members to give you
|
||||
neccessary permissions.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
NoProjects.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps(['noprojects']);
|
@ -1,40 +1,39 @@
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import jsrp from 'jsrp';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import InputField from "~/components/basic/InputField";
|
||||
import passwordCheck from "~/components/utilities/checks/PasswordCheck";
|
||||
import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm";
|
||||
import { getTranslatedStaticProps } from "~/components/utilities/withTranslateProps";
|
||||
import Button from '~/components/basic/buttons/Button';
|
||||
import InputField from '~/components/basic/InputField';
|
||||
import passwordCheck from '~/components/utilities/checks/PasswordCheck';
|
||||
import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
|
||||
import { getTranslatedStaticProps } from '~/components/utilities/withTranslateProps';
|
||||
|
||||
import EmailVerifyOnPasswordReset from "./api/auth/EmailVerifyOnPasswordReset";
|
||||
import getBackupEncryptedPrivateKey from "./api/auth/getBackupEncryptedPrivateKey";
|
||||
import resetPasswordOnAccountRecovery from "./api/auth/resetPasswordOnAccountRecovery";
|
||||
import EmailVerifyOnPasswordReset from './api/auth/EmailVerifyOnPasswordReset';
|
||||
import getBackupEncryptedPrivateKey from './api/auth/getBackupEncryptedPrivateKey';
|
||||
import resetPasswordOnAccountRecovery from './api/auth/resetPasswordOnAccountRecovery';
|
||||
|
||||
const queryString = require("query-string");
|
||||
const nacl = require("tweetnacl");
|
||||
const jsrp = require("jsrp");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
const client = new jsrp.client();
|
||||
|
||||
export default function PasswordReset() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const token = parsedUrl.token;
|
||||
const email = parsedUrl.to?.replace(" ", "+").trim();
|
||||
const [verificationToken, setVerificationToken] = useState("");
|
||||
const [verificationToken, setVerificationToken] = useState('');
|
||||
const [step, setStep] = useState(1);
|
||||
const [backupKey, setBackupKey] = useState("");
|
||||
const [privateKey, setPrivateKey] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [backupKey, setBackupKey] = useState('');
|
||||
const [privateKey, setPrivateKey] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
||||
const token = parsedUrl.token as string;
|
||||
const email = (parsedUrl.to as string)?.replace(' ', '+').trim();
|
||||
|
||||
// Unencrypt the private key with a backup key
|
||||
const getEncryptedKeyHandler = async () => {
|
||||
try {
|
||||
@ -55,13 +54,12 @@ export default function PasswordReset() {
|
||||
|
||||
// If everything is correct, reset the password
|
||||
const resetPasswordHandler = async () => {
|
||||
let errorCheck = false;
|
||||
errorCheck = passwordCheck({
|
||||
const errorCheck = passwordCheck({
|
||||
password: newPassword,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck: errorCheck,
|
||||
errorCheck: false,
|
||||
});
|
||||
|
||||
if (!errorCheck) {
|
||||
@ -73,7 +71,7 @@ export default function PasswordReset() {
|
||||
.padStart(
|
||||
32 +
|
||||
(newPassword.slice(0, 32).length - new Blob([newPassword]).size),
|
||||
"0"
|
||||
'0'
|
||||
),
|
||||
}) as { ciphertext: string; iv: string; tag: string };
|
||||
|
||||
@ -96,7 +94,7 @@ export default function PasswordReset() {
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
if (response?.status === 200) {
|
||||
router.push("/login");
|
||||
router.push('/login');
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -107,19 +105,19 @@ export default function PasswordReset() {
|
||||
|
||||
// Click a button to confirm email
|
||||
const stepConfirmEmail = (
|
||||
<div className="bg-bunker flex flex-col items-center w-full py-6 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl text-center font-semibold mb-8 flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
<div className='bg-bunker flex flex-col items-center w-full py-6 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl'>
|
||||
<p className='text-4xl text-center font-semibold mb-8 flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
|
||||
Confirm your email
|
||||
</p>
|
||||
<Image
|
||||
src="/images/envelope.svg"
|
||||
src='/images/envelope.svg'
|
||||
height={262}
|
||||
width={410}
|
||||
alt="verify email"
|
||||
alt='verify email'
|
||||
></Image>
|
||||
<div className="flex max-w-max flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-4 mb-2">
|
||||
<div className='flex flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-4 mb-2'>
|
||||
<Button
|
||||
text="Confirm Email"
|
||||
text='Confirm Email'
|
||||
onButtonPressed={async () => {
|
||||
const response = await EmailVerifyOnPasswordReset({
|
||||
email,
|
||||
@ -129,11 +127,11 @@ export default function PasswordReset() {
|
||||
setVerificationToken((await response.json()).token);
|
||||
setStep(2);
|
||||
} else {
|
||||
console.log("ERROR", response);
|
||||
router.push("/email-not-verified");
|
||||
console.log('ERROR', response);
|
||||
router.push('/email-not-verified');
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
size='lg'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -141,34 +139,34 @@ export default function PasswordReset() {
|
||||
|
||||
// Input backup key
|
||||
const stepInputBackupKey = (
|
||||
<div className="bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl">
|
||||
<p className="text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-4">
|
||||
<div className='bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl'>
|
||||
<p className='text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-4'>
|
||||
Enter your backup key
|
||||
</p>
|
||||
<div className="flex flex-row items-center justify-center md:pb-4 mt-4 md:mx-2">
|
||||
<p className="text-sm flex justify-center text-gray-400 w-max max-w-md">
|
||||
<div className='flex flex-row items-center justify-center md:pb-4 mt-4 md:mx-2'>
|
||||
<p className='text-sm flex justify-center text-gray-400 w-max max-w-md'>
|
||||
You can find it in your emrgency kit. You had to download the enrgency
|
||||
kit during signup.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
|
||||
<div className='flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28'>
|
||||
<InputField
|
||||
label="Backup Key"
|
||||
label='Backup Key'
|
||||
onChangeHandler={setBackupKey}
|
||||
type="password"
|
||||
type='password'
|
||||
value={backupKey}
|
||||
placeholder=""
|
||||
placeholder=''
|
||||
isRequired
|
||||
error={backupKeyError}
|
||||
errorText="Something is wrong with the backup key"
|
||||
errorText='Something is wrong with the backup key'
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
|
||||
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
|
||||
<div className='flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm'>
|
||||
<div className='text-l mt-6 m-8 px-8 py-3 text-lg'>
|
||||
<Button
|
||||
text="Submit Backup Key"
|
||||
text='Submit Backup Key'
|
||||
onButtonPressed={() => getEncryptedKeyHandler()}
|
||||
size="lg"
|
||||
size='lg'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -177,18 +175,18 @@ export default function PasswordReset() {
|
||||
|
||||
// Enter new password
|
||||
const stepEnterNewPassword = (
|
||||
<div className="bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl">
|
||||
<p className="text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100">
|
||||
<div className='bg-bunker flex flex-col items-center w-full pt-6 pb-3 max-w-xs md:max-w-lg mx-auto my-32 px-4 md:px-6 mx-1 rounded-xl drop-shadow-xl'>
|
||||
<p className='text-2xl md:text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100'>
|
||||
Enter new password
|
||||
</p>
|
||||
<div className="flex flex-row items-center justify-center md:pb-4 mt-1 md:mx-2">
|
||||
<p className="text-sm flex justify-center text-gray-400 w-max max-w-md">
|
||||
<div className='flex flex-row items-center justify-center md:pb-4 mt-1 md:mx-2'>
|
||||
<p className='text-sm flex justify-center text-gray-400 w-max max-w-md'>
|
||||
Make sure you save it somewhere save.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
|
||||
<div className='flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28'>
|
||||
<InputField
|
||||
label="New Password"
|
||||
label='New Password'
|
||||
onChangeHandler={(password) => {
|
||||
setNewPassword(password);
|
||||
passwordCheck({
|
||||
@ -196,70 +194,70 @@ export default function PasswordReset() {
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck: false,
|
||||
errorCheck: false,
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
type='password'
|
||||
value={newPassword}
|
||||
isRequired
|
||||
error={
|
||||
passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber
|
||||
}
|
||||
autoComplete="new-password"
|
||||
id="new-password"
|
||||
autoComplete='new-password'
|
||||
id='new-password'
|
||||
/>
|
||||
</div>
|
||||
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
|
||||
<div className="w-full mt-3 bg-white/5 px-2 mx-2 flex flex-col items-start py-2 rounded-md max-w-md mb-2">
|
||||
<div className='w-full mt-3 bg-white/5 px-2 mx-2 flex flex-col items-start py-2 rounded-md max-w-md mb-2'>
|
||||
<div className={`text-gray-400 text-sm mb-1`}>
|
||||
Password should contain at least:
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" />
|
||||
<FontAwesomeIcon icon={faX} className='text-md text-red mr-2.5' />
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLength ? "text-gray-400" : "text-gray-600"
|
||||
passwordErrorLength ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
14 characters
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" />
|
||||
<FontAwesomeIcon icon={faX} className='text-md text-red mr-2.5' />
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"
|
||||
passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
1 lowercase character
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" />
|
||||
<FontAwesomeIcon icon={faX} className='text-md text-red mr-2.5' />
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
|
||||
passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
1 number
|
||||
@ -267,14 +265,14 @@ export default function PasswordReset() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2"></div>
|
||||
<div className='py-2'></div>
|
||||
)}
|
||||
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
|
||||
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
|
||||
<div className='flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm'>
|
||||
<div className='text-l mt-6 m-8 px-8 py-3 text-lg'>
|
||||
<Button
|
||||
text="Submit New Password"
|
||||
text='Submit New Password'
|
||||
onButtonPressed={() => resetPasswordHandler()}
|
||||
size="lg"
|
||||
size='lg'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -282,7 +280,7 @@ export default function PasswordReset() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 h-screen w-full flex flex-col items-center justify-center">
|
||||
<div className='bg-bunker-800 h-screen w-full flex flex-col items-center justify-center'>
|
||||
{step === 1 && stepConfirmEmail}
|
||||
{step === 2 && stepInputBackupKey}
|
||||
{step === 3 && stepEnterNewPassword}
|
||||
|
@ -1,31 +1,35 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Head from 'next/head';
|
||||
import Image from 'next/image';
|
||||
|
||||
/**
|
||||
* This is the page that shows up when a user's invitation
|
||||
* This is the page that shows up when a user's invitation
|
||||
* to join a project/organization on Infisical has expired
|
||||
*/
|
||||
export default function RequestNewInvite() {
|
||||
return (
|
||||
<div className="bg-bunker-700 md:h-screen flex flex-col justify-between">
|
||||
<div className='bg-bunker-700 md:h-screen flex flex-col justify-between'>
|
||||
<Head>
|
||||
<title>Request a New Invite</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<div className="flex flex-col items-center justify-center text-bunker-200 h-screen w-screen mt-8">
|
||||
<p className="text-4xl text-primary-100">Oops, your invite has expired.</p>
|
||||
<p className="text-lg my-4">Ask your admin for a new one.</p>
|
||||
<p className="text-sm text-bunker-400 max-w-xs px-7 text-center leading-tight">
|
||||
<span className="bg-primary-500/40 text-black px-1 rounded-md">Note:</span> If it still {"doesn't work"}, please reach out to us at
|
||||
<div className='flex flex-col items-center justify-center text-bunker-200 h-screen w-screen mt-8'>
|
||||
<p className='text-4xl text-primary-100'>
|
||||
Oops, your invite has expired.
|
||||
</p>
|
||||
<p className='text-lg my-4'>Ask your admin for a new one.</p>
|
||||
<p className='text-sm text-bunker-400 max-w-xs px-7 text-center leading-tight'>
|
||||
<span className='bg-primary-500/40 text-black px-1 rounded-md'>
|
||||
Note:
|
||||
</span>{' '}
|
||||
If it still {"doesn't work"}, please reach out to us at
|
||||
support@infisical.com
|
||||
</p>
|
||||
<div className="">
|
||||
<div className=''>
|
||||
<Image
|
||||
src="/images/invitation-expired.svg"
|
||||
src='/images/invitation-expired.svg'
|
||||
height={500}
|
||||
width={800}
|
||||
alt="google logo"
|
||||
alt='google logo'
|
||||
></Image>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,120 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import Plan from "~/components/billing/Plan";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "~/utilities/config";
|
||||
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
||||
|
||||
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
|
||||
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
|
||||
|
||||
export default function SettingsBilling() {
|
||||
let [currentPlan, setCurrentPlan] = useState("");
|
||||
let [numUsers, setNumUsers] = useState("");
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const plans = [
|
||||
{
|
||||
key: 1,
|
||||
name: t("billing:starter.name"),
|
||||
price: t("billing:free"),
|
||||
priceExplanation: t("billing:starter.price-explanation"),
|
||||
text: t("billing:starter.text"),
|
||||
subtext: t("billing:starter.subtext"),
|
||||
buttonTextMain: t("billing:downgrade"),
|
||||
buttonTextSecondary: t("billing:learn-more"),
|
||||
current: currentPlan == STRIPE_PRODUCT_STARTER,
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: t("billing:professional.name"),
|
||||
price: "$9",
|
||||
priceExplanation: t("billing:professional.price-explanation"),
|
||||
subtext: t("billing:professional.subtext"),
|
||||
text: t("billing:professional.text"),
|
||||
buttonTextMain: t("billing:upgrade"),
|
||||
buttonTextSecondary: t("billing:learn-more"),
|
||||
current: currentPlan == STRIPE_PRODUCT_PRO,
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: t("billing:enterprise.name"),
|
||||
price: t("billing:custom-pricing"),
|
||||
text: t("billing:enterprise.text"),
|
||||
buttonTextMain: t("billing:schedule-demo"),
|
||||
buttonTextSecondary: t("billing:learn-more"),
|
||||
current: false,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(async () => {
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
|
||||
setCurrentPlan(subscriptions.data[0].plan.id);
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setNumUsers(orgUsers.length);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>{t("common:head-title", { title: t("billing:title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName={t("billing:title")} />
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
{t("billing:title")}
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
{t("billing:description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50">
|
||||
<p className="text-xl font-semibold">{t("billing:subscription")}</p>
|
||||
<div className="flex flex-row mt-4 overflow-x-auto">
|
||||
{plans.map((plan) => (
|
||||
<Plan key={plan.name} plan={plan} />
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xl font-bold mt-12">
|
||||
{t("billing:current-usage")}
|
||||
</p>
|
||||
<div className="flex flex-row">
|
||||
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
<p className="text-6xl font-bold">{numUsers}</p>
|
||||
<p className="text-gray-300">
|
||||
{numUsers > 1
|
||||
? "Organization members"
|
||||
: "Organization member"}
|
||||
</p>
|
||||
</div>
|
||||
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
<p className="text-6xl font-bold">1 </p>
|
||||
<p className="text-gray-300">Organization projects</p>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsBilling.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps([
|
||||
"settings",
|
||||
"billing",
|
||||
]);
|
123
frontend/pages/settings/billing/[id].tsx
Normal file
123
frontend/pages/settings/billing/[id].tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import Plan from '~/components/billing/Plan';
|
||||
import NavHeader from '~/components/navigation/NavHeader';
|
||||
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from '~/utilities/config';
|
||||
import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
|
||||
|
||||
import getOrganizationSubscriptions from '../../api/organization/GetOrgSubscription';
|
||||
import getOrganizationUsers from '../../api/organization/GetOrgUsers';
|
||||
|
||||
export default function SettingsBilling() {
|
||||
const [currentPlan, setCurrentPlan] = useState('');
|
||||
const [numUsers, setNumUsers] = useState(0);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const plans = [
|
||||
{
|
||||
key: 1,
|
||||
name: t('billing:starter.name')!,
|
||||
price: t('billing:free')!,
|
||||
priceExplanation: t('billing:starter.price-explanation')!,
|
||||
text: t('billing:starter.text')!,
|
||||
subtext: t('billing:starter.subtext')!,
|
||||
buttonTextMain: t('billing:downgrade')!,
|
||||
buttonTextSecondary: t('billing:learn-more')!,
|
||||
current: currentPlan == STRIPE_PRODUCT_STARTER,
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: t('billing:professional.name')!,
|
||||
price: '$9',
|
||||
priceExplanation: t('billing:professional.price-explanation')!,
|
||||
subtext: t('billing:professional.subtext')!,
|
||||
text: t('billing:professional.text')!,
|
||||
buttonTextMain: t('billing:upgrade')!,
|
||||
buttonTextSecondary: t('billing:learn-more')!,
|
||||
current: currentPlan == STRIPE_PRODUCT_PRO,
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: t('billing:enterprise.name')!,
|
||||
price: t('billing:custom-pricing')!,
|
||||
text: t('billing:enterprise.text')!,
|
||||
buttonTextMain: t('billing:schedule-demo')!,
|
||||
buttonTextSecondary: t('billing:learn-more')!,
|
||||
current: false,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const orgId = localStorage.getItem('orgData.id') as string;
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId,
|
||||
});
|
||||
|
||||
setCurrentPlan(subscriptions.data[0].plan.id);
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId,
|
||||
});
|
||||
setNumUsers(orgUsers.length);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='bg-bunker-800 max-h-screen flex flex-col justify-between text-white'>
|
||||
<Head>
|
||||
<title>{t('common:head-title', { title: t('billing:title') })}</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<div className='flex flex-row'>
|
||||
<div className='w-full max-h-screen pb-2 overflow-y-auto'>
|
||||
<NavHeader pageName={t('billing:title')} />
|
||||
<div className='flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl'>
|
||||
<div className='flex flex-col justify-start items-start text-3xl'>
|
||||
<p className='font-semibold mr-4 text-gray-200'>
|
||||
{t('billing:title')}
|
||||
</p>
|
||||
<p className='font-normal mr-4 text-gray-400 text-base'>
|
||||
{t('billing:description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col ml-6 text-mineshaft-50'>
|
||||
<p className='text-xl font-semibold'>{t('billing:subscription')}</p>
|
||||
<div className='flex flex-row mt-4 overflow-x-auto'>
|
||||
{plans.map((plan) => (
|
||||
<Plan key={plan.name} plan={plan} />
|
||||
))}
|
||||
</div>
|
||||
<p className='text-xl font-bold mt-12'>
|
||||
{t('billing:current-usage')}
|
||||
</p>
|
||||
<div className='flex flex-row'>
|
||||
<div className='mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col'>
|
||||
<p className='text-6xl font-bold'>{numUsers}</p>
|
||||
<p className='text-gray-300'>
|
||||
{numUsers > 1
|
||||
? 'Organization members'
|
||||
: 'Organization member'}
|
||||
</p>
|
||||
</div>
|
||||
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
<p className="text-6xl font-bold">1 </p>
|
||||
<p className="text-gray-300">Organization projects</p>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsBilling.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps([
|
||||
'settings',
|
||||
'billing',
|
||||
]);
|
@ -1,23 +1,23 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
faX
|
||||
faX,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import AddIncidentContactDialog from "~/components/basic/dialog/AddIncidentContactDialog";
|
||||
import AddUserDialog from "~/components/basic/dialog/AddUserDialog";
|
||||
import InputField from "~/components/basic/InputField";
|
||||
import UserTable from "~/components/basic/table/UserTable";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import guidGenerator from "~/utilities/randomId";
|
||||
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
||||
import Button from '~/components/basic/buttons/Button';
|
||||
import AddIncidentContactDialog from '~/components/basic/dialog/AddIncidentContactDialog';
|
||||
import AddUserDialog from '~/components/basic/dialog/AddUserDialog';
|
||||
import InputField from '~/components/basic/InputField';
|
||||
import UserTable from '~/components/basic/table/UserTable';
|
||||
import NavHeader from '~/components/navigation/NavHeader';
|
||||
import guidGenerator from '~/utilities/randomId';
|
||||
import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
|
||||
|
||||
import addUserToOrg from '../../api/organization/addUserToOrg';
|
||||
import deleteIncidentContact from '../../api/organization/deleteIncidentContact';
|
||||
@ -37,74 +37,76 @@ export default function SettingsOrg() {
|
||||
const [emailUser, setEmailUser] = useState('');
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState('');
|
||||
const [searchUsers, setSearchUsers] = useState('');
|
||||
const [workspaceId, setWorkspaceId] = useState('');
|
||||
const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] =
|
||||
useState(false);
|
||||
const [isAddUserOpen, setIsAddUserOpen] = useState(
|
||||
router.asPath.split('?')[1] == 'invite'
|
||||
);
|
||||
const [incidentContacts, setIncidentContacts] = useState([]);
|
||||
const [incidentContacts, setIncidentContacts] = useState<string[]>([]);
|
||||
const [searchIncidentContact, setSearchIncidentContact] = useState('');
|
||||
const [userList, setUserList] = useState();
|
||||
const [userList, setUserList] = useState<any[]>([]);
|
||||
const [personalEmail, setPersonalEmail] = useState('');
|
||||
let workspaceIdTemp;
|
||||
const [email, setEmail] = useState('');
|
||||
const [currentPlan, setCurrentPlan] = useState('');
|
||||
|
||||
const workspaceId = router.query.id as string;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(async () => {
|
||||
let org = await getOrganization({
|
||||
orgId: localStorage.getItem('orgData.id')
|
||||
});
|
||||
let orgData = org;
|
||||
setOrgName(orgData.name);
|
||||
let incidentContactsData = await getIncidentContacts(
|
||||
localStorage.getItem('orgData.id')
|
||||
);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const orgId = localStorage.getItem('orgData.id') as string;
|
||||
const org = await getOrganization({
|
||||
orgId,
|
||||
});
|
||||
|
||||
setIncidentContacts(incidentContactsData?.map((contact) => contact.email));
|
||||
setOrgName(org.name);
|
||||
const incidentContactsData = await getIncidentContacts(
|
||||
localStorage.getItem('orgData.id') as string
|
||||
);
|
||||
|
||||
const user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
setIncidentContacts(
|
||||
incidentContactsData?.map((contact) => contact.email)
|
||||
);
|
||||
|
||||
workspaceIdTemp = router.query.id;
|
||||
let orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem('orgData.id')
|
||||
});
|
||||
setUserList(
|
||||
orgUsers.map((user) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: user.user?.firstName,
|
||||
lastName: user.user?.lastName,
|
||||
email: user.user?.email == null ? user.inviteEmail : user.user?.email,
|
||||
role: user?.role,
|
||||
status: user?.status,
|
||||
userId: user.user?._id,
|
||||
membershipId: user._id,
|
||||
publicKey: user.user?.publicKey
|
||||
}))
|
||||
);
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId: localStorage.getItem('orgData.id')
|
||||
});
|
||||
setCurrentPlan(subscriptions.data[0].plan.product);
|
||||
const user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId,
|
||||
});
|
||||
|
||||
setUserList(
|
||||
orgUsers.map((user) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: user.user?.firstName,
|
||||
lastName: user.user?.lastName,
|
||||
email: user.user?.email == null ? user.inviteEmail : user.user?.email,
|
||||
role: user?.role,
|
||||
status: user?.status,
|
||||
userId: user.user?._id,
|
||||
membershipId: user._id,
|
||||
publicKey: user.user?.publicKey,
|
||||
}))
|
||||
);
|
||||
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId,
|
||||
});
|
||||
setCurrentPlan(subscriptions.data[0].plan.product);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const modifyOrgName = (newName) => {
|
||||
const modifyOrgName = (newName: string) => {
|
||||
setButtonReady(true);
|
||||
setOrgName(newName);
|
||||
};
|
||||
|
||||
const submitChanges = (newOrgName) => {
|
||||
renameOrg(localStorage.getItem('orgData.id'), newOrgName);
|
||||
const submitChanges = (newOrgName: string) => {
|
||||
renameOrg(localStorage.getItem('orgData.id') as string, newOrgName);
|
||||
setButtonReady(false);
|
||||
};
|
||||
|
||||
useEffect(async () => {
|
||||
setWorkspaceId(router.query.id);
|
||||
}, []);
|
||||
|
||||
function closeAddUserModal() {
|
||||
setIsAddUserOpen(false);
|
||||
}
|
||||
@ -121,18 +123,21 @@ export default function SettingsOrg() {
|
||||
setIsAddIncidentContactOpen(true);
|
||||
}
|
||||
|
||||
async function submitAddUserModal(email) {
|
||||
await addUserToOrg(email, localStorage.getItem('orgData.id'));
|
||||
async function submitAddUserModal(email: string) {
|
||||
await addUserToOrg(email, localStorage.getItem('orgData.id') as string);
|
||||
setEmail('');
|
||||
setIsAddUserOpen(false);
|
||||
router.reload();
|
||||
}
|
||||
|
||||
const deleteIncidentContactFully = (incidentContact) => {
|
||||
const deleteIncidentContactFully = (incidentContact: string) => {
|
||||
setIncidentContacts(
|
||||
incidentContacts.filter((contact) => contact != incidentContact)
|
||||
);
|
||||
deleteIncidentContact(localStorage.getItem('orgData.id'), incidentContact);
|
||||
deleteIncidentContact(
|
||||
localStorage.getItem('orgData.id') as string,
|
||||
incidentContact
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -142,32 +147,31 @@ export default function SettingsOrg() {
|
||||
* It then deletes the workspace and forwards the user to another aviable workspace.
|
||||
*/
|
||||
const executeDeletingWorkspace = async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id == router.query.id
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
userWorkspaces.filter((workspace) => workspace._id == workspaceId)[0]
|
||||
.name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(router.query.id);
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
await deleteWorkspace(workspaceId);
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
router.push('/dashboard/' + userWorkspaces[0]._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<div className='bg-bunker-800 max-h-screen flex flex-col justify-between text-white'>
|
||||
<Head>
|
||||
<title>
|
||||
{t("common:head-title", { title: t("settings-org:title") })}
|
||||
{t('common:head-title', { title: t('settings-org:title') })}
|
||||
</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName={t("settings-org:title")} />
|
||||
<div className='flex flex-row'>
|
||||
<div className='w-full max-h-screen pb-2 overflow-y-auto'>
|
||||
<NavHeader pageName={t('settings-org:title')} />
|
||||
<AddIncidentContactDialog
|
||||
isOpen={isAddIncidentContactOpen}
|
||||
closeModal={closeAddIncidentContactModal}
|
||||
@ -175,55 +179,56 @@ export default function SettingsOrg() {
|
||||
incidentContacts={incidentContacts}
|
||||
setIncidentContacts={setIncidentContacts}
|
||||
/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
{t("settings-org:title")}
|
||||
<div className='flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl'>
|
||||
<div className='flex flex-col justify-start items-start text-3xl'>
|
||||
<p className='font-semibold mr-4 text-gray-200'>
|
||||
{t('settings-org:title')}
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
{t("settings-org:description")}
|
||||
<p className='font-normal mr-4 text-gray-400 text-base'>
|
||||
{t('settings-org:description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md mt-2 flex flex-col items-end pb-4">
|
||||
<div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
|
||||
{t("common:display-name")}
|
||||
<div className='flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl'>
|
||||
<div className='flex flex-col'>
|
||||
<div className='min-w-md mt-2 flex flex-col items-end pb-4'>
|
||||
<div className='bg-white/5 rounded-md px-6 py-4 flex flex-col items-start w-full mb-6'>
|
||||
<div className='max-h-28 w-full max-w-md mr-auto'>
|
||||
<p className='font-semibold mr-4 text-gray-200 text-xl mb-2'>
|
||||
{t('common:display-name')}
|
||||
</p>
|
||||
<InputField
|
||||
label=''
|
||||
// label="Organization Name"
|
||||
onChangeHandler={modifyOrgName}
|
||||
type="varName"
|
||||
type='varName'
|
||||
value={orgName}
|
||||
placeholder=""
|
||||
placeholder=''
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start w-full">
|
||||
<div className='flex justify-start w-full'>
|
||||
<div className={`flex justify-start max-w-sm mt-4 mb-2`}>
|
||||
<Button
|
||||
text={t("common:save-changes")}
|
||||
text={t('common:save-changes') as string}
|
||||
onButtonPressed={() => submitChanges(orgName)}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
color='mineshaft'
|
||||
size='md'
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled={t("common:saved")}
|
||||
textDisabled={t('common:saved') as string}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<p className="font-semibold mr-4 text-white text-xl">
|
||||
{t("section-members:org-members")}
|
||||
<div className='bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start w-full mb-6'>
|
||||
<p className='font-semibold mr-4 text-white text-xl'>
|
||||
{t('section-members:org-members')}
|
||||
</p>
|
||||
<p className="mr-4 text-gray-400 mt-2 mb-2">
|
||||
{t("section-members:org-members-description")}
|
||||
<p className='mr-4 text-gray-400 mt-2 mb-2'>
|
||||
{t('section-members:org-members-description')}
|
||||
</p>
|
||||
<AddUserDialog
|
||||
isOpen={isAddUserOpen}
|
||||
@ -236,31 +241,31 @@ export default function SettingsOrg() {
|
||||
orgName={orgName}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
<div className="pb-1 w-full flex flex-row items-start max-w-6xl">
|
||||
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center">
|
||||
<div className='pb-1 w-full flex flex-row items-start max-w-6xl'>
|
||||
<div className='h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex-row '>
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
|
||||
className='bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400'
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
|
||||
className='pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none'
|
||||
value={searchUsers}
|
||||
onChange={(e) => setSearchUsers(e.target.value)}
|
||||
placeholder={t("section-members:search-members")}
|
||||
placeholder={t('section-members:search-members') as string}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
|
||||
<div className='mt-2 ml-2 min-w-max flex flex-row items-start justify-start'>
|
||||
<Button
|
||||
text={t("section-members:add-member")}
|
||||
text={t('section-members:add-member') as string}
|
||||
onButtonPressed={openAddUserModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
color='mineshaft'
|
||||
size='md'
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{userList && (
|
||||
<div className="overflow-y-auto max-w-6xl w-full">
|
||||
<div className='overflow-y-auto max-w-6xl w-full'>
|
||||
<UserTable
|
||||
userData={userList}
|
||||
changeData={setUserList}
|
||||
@ -271,42 +276,42 @@ export default function SettingsOrg() {
|
||||
// onClick={openDeleteModal}
|
||||
// deleteUser={deleteMembership}
|
||||
// setUserIdToBeDeleted={setUserIdToBeDeleted}
|
||||
className="w-full"
|
||||
className='w-full'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
{t("section-incident:incident-contacts")}
|
||||
<div className='bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start w-full mb-6 mt-4'>
|
||||
<div className='flex flex-row max-w-5xl justify-between items-center w-full'>
|
||||
<div className='flex flex-col justify-between w-full max-w-3xl'>
|
||||
<p className='text-xl font-semibold mb-3 min-w-max'>
|
||||
{t('section-incident:incident-contacts')}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mb-2 min-w-max">
|
||||
{t("section-incident:incident-contacts-description")}
|
||||
<p className='text-xs text-gray-500 mb-2 min-w-max'>
|
||||
{t('section-incident:incident-contacts-description')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
|
||||
<div className='mt-4 mb-2 min-w-max flex flex-row items-end justify-center'>
|
||||
<Button
|
||||
text={t("section-incident:add-contact")}
|
||||
text={t('section-incident:add-contact') as string}
|
||||
onButtonPressed={openAddIncidentContactModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
color='mineshaft'
|
||||
size='md'
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flex flwex-row items-center">
|
||||
<div className='h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flwex-row'>
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400"
|
||||
className='bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400'
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none"
|
||||
className='pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none'
|
||||
value={searchIncidentContact}
|
||||
onChange={(e) => setSearchIncidentContact(e.target.value)}
|
||||
placeholder={t("common:search")}
|
||||
placeholder={t('common:search') as string}
|
||||
/>
|
||||
</div>
|
||||
{incidentContacts?.filter((email) =>
|
||||
@ -317,25 +322,25 @@ export default function SettingsOrg() {
|
||||
.map((contact) => (
|
||||
<div
|
||||
key={guidGenerator()}
|
||||
className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full"
|
||||
className='flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full'
|
||||
>
|
||||
<p className="text-gray-300">{contact}</p>
|
||||
<div className="opacity-50 hover:opacity-100 duration-200">
|
||||
<p className='text-gray-300'>{contact}</p>
|
||||
<div className='opacity-50 hover:opacity-100 duration-200'>
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
deleteIncidentContactFully(contact)
|
||||
}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
color='red'
|
||||
size='icon-sm'
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6">
|
||||
<p className="text-gray-400">
|
||||
{t("section-incident:no-incident-contacts")}
|
||||
<div className='w-full flex flex-row justify-center mt-6 max-w-4xl ml-6'>
|
||||
<p className='text-gray-400'>
|
||||
{t('section-incident:no-incident-contacts')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@ -386,8 +391,8 @@ export default function SettingsOrg() {
|
||||
SettingsOrg.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps([
|
||||
"settings",
|
||||
"settings-org",
|
||||
"section-incident",
|
||||
"section-members",
|
||||
'settings',
|
||||
'settings-org',
|
||||
'section-incident',
|
||||
'section-members',
|
||||
]);
|
@ -1,77 +1,78 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import InputField from "~/components/basic/InputField";
|
||||
import ListBox from "~/components/basic/Listbox";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import changePassword from "~/components/utilities/cryptography/changePassword";
|
||||
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
|
||||
import passwordCheck from "~/utilities/checks/PasswordCheck";
|
||||
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
||||
import Button from '~/components/basic/buttons/Button';
|
||||
import InputField from '~/components/basic/InputField';
|
||||
import ListBox from '~/components/basic/Listbox';
|
||||
import NavHeader from '~/components/navigation/NavHeader';
|
||||
import changePassword from '~/components/utilities/cryptography/changePassword';
|
||||
import issueBackupKey from '~/components/utilities/cryptography/issueBackupKey';
|
||||
import passwordCheck from '~/utilities/checks/PasswordCheck';
|
||||
import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
|
||||
|
||||
import getUser from "../../api/user/getUser";
|
||||
import getUser from '../../api/user/getUser';
|
||||
|
||||
export default function PersonalSettings() {
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
const [personalName, setPersonalName] = useState("");
|
||||
const [personalEmail, setPersonalEmail] = useState('');
|
||||
const [personalName, setPersonalName] = useState('');
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const [currentPasswordError, setCurrentPasswordError] = useState(false);
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [backupPassword, setBackupPassword] = useState("");
|
||||
const [currentPassword, setCurrentPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [backupPassword, setBackupPassword] = useState('');
|
||||
const [passwordChanged, setPasswordChanged] = useState(false);
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const lang = router.locale ?? "en";
|
||||
const lang = router.locale ?? 'en';
|
||||
|
||||
const setLanguage = async (to) => {
|
||||
const setLanguage = async (to: string) => {
|
||||
router.push(router.asPath, router.asPath, { locale: to });
|
||||
localStorage.setItem("lang", to);
|
||||
localStorage.setItem('lang', to);
|
||||
};
|
||||
|
||||
useEffect(async () => {
|
||||
let user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
setPersonalName(user.firstName + " " + user.lastName);
|
||||
useEffect(() => {
|
||||
getUser().then((user) => {
|
||||
setPersonalEmail(user.email);
|
||||
setPersonalName(user.firstName + ' ' + user.lastName);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<div className='bg-bunker-800 max-h-screen flex flex-col justify-between text-white'>
|
||||
<Head>
|
||||
<title>
|
||||
{t("common:head-title", { title: t("settings-personal:title") })}
|
||||
{t('common:head-title', { title: t('settings-personal:title') })}
|
||||
</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<div className='flex flex-row'>
|
||||
<div className='w-full max-h-screen pb-2 overflow-y-auto'>
|
||||
<NavHeader
|
||||
pageName={t("settings-personal:title")}
|
||||
pageName={t('settings-personal:title')}
|
||||
isProjectRelated={false}
|
||||
/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
{t("settings-personal:title")}
|
||||
<div className='flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl'>
|
||||
<div className='flex flex-col justify-start items-start text-3xl'>
|
||||
<p className='font-semibold mr-4 text-gray-200'>
|
||||
{t('settings-personal:title')}
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
{t("settings-personal:description")}
|
||||
<p className='font-normal mr-4 text-gray-400 text-base'>
|
||||
{t('settings-personal:description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md flex flex-col items-end pb-4">
|
||||
<div className='flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl'>
|
||||
<div className='flex flex-col'>
|
||||
<div className='min-w-md flex flex-col items-end pb-4'>
|
||||
{/* <div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
|
||||
@ -118,46 +119,45 @@ export default function PersonalSettings() {
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<p className="text-xl font-semibold self-start">
|
||||
{t("settings-personal:change-language")}
|
||||
<div className='bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start w-full mb-6 mt-4'>
|
||||
<p className='text-xl font-semibold self-start'>
|
||||
{t('settings-personal:change-language')}
|
||||
</p>
|
||||
<div className="max-h-28 w-ful mt-4">
|
||||
<div className='max-h-28 w-ful mt-4'>
|
||||
<ListBox
|
||||
selected={lang}
|
||||
onChange={setLanguage}
|
||||
data={["en", "ko", "fr"]}
|
||||
width="full"
|
||||
text={`${t("common:language")}: `}
|
||||
data={['en', 'ko', 'fr']}
|
||||
text={`${t('common:language')}: `}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
{t("section-password:change")}
|
||||
<div className='bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start w-full mb-6'>
|
||||
<div className='flex flex-row max-w-5xl justify-between items-center w-full'>
|
||||
<div className='flex flex-col justify-between w-full max-w-3xl'>
|
||||
<p className='text-xl font-semibold mb-3 min-w-max'>
|
||||
{t('section-password:change')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-xl w-full">
|
||||
<div className='max-w-xl w-full'>
|
||||
<InputField
|
||||
label={t("section-password:current")}
|
||||
label={t('section-password:current') as string}
|
||||
onChangeHandler={(password) => {
|
||||
setCurrentPassword(password);
|
||||
}}
|
||||
type="password"
|
||||
type='password'
|
||||
value={currentPassword}
|
||||
isRequired
|
||||
error={currentPasswordError}
|
||||
errorText={t("section-password:current-wrong")}
|
||||
autoComplete="current-password"
|
||||
id="current-password"
|
||||
errorText={t('section-password:current-wrong') as string}
|
||||
autoComplete='current-password'
|
||||
id='current-password'
|
||||
/>
|
||||
<div className="py-2"></div>
|
||||
<div className='py-2'></div>
|
||||
<InputField
|
||||
label={t("section-password:new")}
|
||||
label={t('section-password:new') as string}
|
||||
onChangeHandler={(password) => {
|
||||
setNewPassword(password);
|
||||
passwordCheck({
|
||||
@ -165,10 +165,10 @@ export default function PersonalSettings() {
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck: false,
|
||||
errorCheck: false,
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
type='password'
|
||||
value={newPassword}
|
||||
isRequired
|
||||
error={
|
||||
@ -176,86 +176,86 @@ export default function PersonalSettings() {
|
||||
passwordErrorLowerCase &&
|
||||
passwordErrorNumber
|
||||
}
|
||||
autoComplete="new-password"
|
||||
id="new-password"
|
||||
autoComplete='new-password'
|
||||
id='new-password'
|
||||
/>
|
||||
</div>
|
||||
{passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber ? (
|
||||
<div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
|
||||
<div className='w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2'>
|
||||
<div className={`text-gray-400 text-sm mb-1`}>
|
||||
{t("section-password:validate-base")}
|
||||
{t('section-password:validate-base')}
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
className='text-md text-red mr-2.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLength ? "text-gray-400" : "text-gray-600"
|
||||
passwordErrorLength ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
{t("section-password:validate-length")}
|
||||
{t('section-password:validate-length')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
className='text-md text-red mr-2.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLowerCase
|
||||
? "text-gray-400"
|
||||
: "text-gray-600"
|
||||
? 'text-gray-400'
|
||||
: 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
{t("section-password:validate-case")}
|
||||
{t('section-password:validate-case')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
className='text-md text-red mr-2.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
|
||||
passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
{t("section-password:validate-number")}
|
||||
{t('section-password:validate-number')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2"></div>
|
||||
<div className='py-2'></div>
|
||||
)}
|
||||
<div className="flex flex-row items-center mt-3 w-52 pr-3">
|
||||
<div className='flex flex-row items-center mt-3 w-52 pr-3'>
|
||||
<Button
|
||||
text={t("section-password:change")}
|
||||
text={t('section-password:change') as string}
|
||||
onButtonPressed={() => {
|
||||
if (
|
||||
!passwordErrorLength &&
|
||||
@ -273,58 +273,58 @@ export default function PersonalSettings() {
|
||||
);
|
||||
}
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
color='mineshaft'
|
||||
size='md'
|
||||
active={
|
||||
newPassword != "" &&
|
||||
currentPassword != "" &&
|
||||
newPassword != '' &&
|
||||
currentPassword != '' &&
|
||||
!(
|
||||
passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber
|
||||
)
|
||||
}
|
||||
textDisabled={t("section-password:change")}
|
||||
textDisabled={t('section-password:change') as string}
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className={`ml-4 text-primary text-3xl ${
|
||||
passwordChanged ? "opacity-100" : "opacity-0"
|
||||
passwordChanged ? 'opacity-100' : 'opacity-0'
|
||||
} duration-300`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 mt-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
{t("settings-personal:emergency.name")}
|
||||
<div className='bg-white/5 rounded-md px-6 pt-5 pb-6 mt-4 flex flex-col items-start w-full mb-6'>
|
||||
<div className='flex flex-row max-w-5xl justify-between items-center w-full'>
|
||||
<div className='flex flex-col justify-between w-full max-w-3xl'>
|
||||
<p className='text-xl font-semibold mb-3 min-w-max'>
|
||||
{t('settings-personal:emergency.name')}
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 min-w-max">
|
||||
{t("settings-personal:emergency.text1")}
|
||||
<p className='text-sm text-mineshaft-300 min-w-max'>
|
||||
{t('settings-personal:emergency.text1')}
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
|
||||
{t("settings-personal:emergency.text2")}
|
||||
<p className='text-sm text-mineshaft-300 mb-5 min-w-max'>
|
||||
{t('settings-personal:emergency.text2')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full max-w-xl mb-4">
|
||||
<div className='w-full max-w-xl mb-4'>
|
||||
<InputField
|
||||
label={t("section-password:current")}
|
||||
label={t('section-password:current') as string}
|
||||
onChangeHandler={setBackupPassword}
|
||||
type="password"
|
||||
type='password'
|
||||
value={backupPassword}
|
||||
isRequired
|
||||
error={backupKeyError}
|
||||
errorText={t("section-password:current-wrong")}
|
||||
autoComplete="current-password"
|
||||
id="current-password"
|
||||
errorText={t('section-password:current-wrong') as string}
|
||||
autoComplete='current-password'
|
||||
id='current-password'
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-center mt-3 w-full w-60">
|
||||
<div className='flex flex-row items-center mt-3 w-60'>
|
||||
<Button
|
||||
text={t("settings-personal:emergency.download")}
|
||||
text={t('settings-personal:emergency.download') as string}
|
||||
onButtonPressed={() => {
|
||||
issueBackupKey({
|
||||
email: personalEmail,
|
||||
@ -334,15 +334,17 @@ export default function PersonalSettings() {
|
||||
setBackupKeyIssued,
|
||||
});
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={backupPassword != ""}
|
||||
textDisabled={t("settings-personal:emergency.download")}
|
||||
color='mineshaft'
|
||||
size='md'
|
||||
active={backupPassword != ''}
|
||||
textDisabled={
|
||||
t('settings-personal:emergency.download') as string
|
||||
}
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className={`ml-4 text-primary text-3xl ${
|
||||
backupKeyIssued ? "opacity-100" : "opacity-0"
|
||||
backupKeyIssued ? 'opacity-100' : 'opacity-0'
|
||||
} duration-300`}
|
||||
/>
|
||||
</div>
|
||||
@ -357,7 +359,7 @@ export default function PersonalSettings() {
|
||||
PersonalSettings.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps([
|
||||
"settings",
|
||||
"settings-personal",
|
||||
"section-password",
|
||||
'settings',
|
||||
'settings-personal',
|
||||
'section-password',
|
||||
]);
|
@ -101,9 +101,8 @@ export default function SettingsBasic() {
|
||||
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id === workspaceId
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
userWorkspaces.filter((workspace) => workspace._id === workspaceId)[0]
|
||||
.name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(workspaceId);
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
@ -323,7 +322,7 @@ export default function SettingsBasic() {
|
||||
</p>
|
||||
<div className='max-h-28 w-full max-w-md mr-auto mt-4'>
|
||||
<InputField
|
||||
label={t('settings-project:project-to-delete')}
|
||||
label={t('settings-project:project-to-delete') as string}
|
||||
onChangeHandler={setWorkspaceToBeDeletedName}
|
||||
type='varName'
|
||||
value={workspaceToBeDeletedName}
|
||||
|
@ -5,6 +5,10 @@ import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { faCheck, faWarning, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import jsrp from 'jsrp';
|
||||
import queryString from 'query-string';
|
||||
import nacl from 'tweetnacl';
|
||||
import { encodeBase64 } from 'tweetnacl-util';
|
||||
|
||||
import Button from '~/components/basic/buttons/Button';
|
||||
import InputField from '~/components/basic/InputField';
|
||||
@ -16,11 +20,7 @@ import passwordCheck from '~/utilities/checks/PasswordCheck';
|
||||
import completeAccountInformationSignupInvite from './api/auth/CompleteAccountInformationSignupInvite';
|
||||
import verifySignupInvite from './api/auth/VerifySignupInvite';
|
||||
|
||||
const nacl = require('tweetnacl');
|
||||
const jsrp = require('jsrp');
|
||||
nacl.util = require('tweetnacl-util');
|
||||
const client = new jsrp.client();
|
||||
const queryString = require('query-string');
|
||||
|
||||
export default function SignupInvite() {
|
||||
const [password, setPassword] = useState('');
|
||||
@ -31,21 +31,22 @@ export default function SignupInvite() {
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
||||
const [email, setEmail] = useState(parsedUrl.to?.replace(' ', '+').trim());
|
||||
const token = parsedUrl.token;
|
||||
const [errorLogin, setErrorLogin] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [step, setStep] = useState(1);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
const [verificationToken, setVerificationToken] = useState();
|
||||
const [verificationToken, setVerificationToken] = useState('');
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
||||
const token = parsedUrl.token as string;
|
||||
const email = (parsedUrl.to as string)?.replace(' ', '+').trim();
|
||||
|
||||
// Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
|
||||
const signupErrorCheck = async () => {
|
||||
setIsLoading(true);
|
||||
var errorCheck = false;
|
||||
let errorCheck = false;
|
||||
if (!firstName) {
|
||||
setFirstNameError(true);
|
||||
errorCheck = true;
|
||||
@ -63,7 +64,7 @@ export default function SignupInvite() {
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck
|
||||
errorCheck,
|
||||
});
|
||||
|
||||
if (!errorCheck) {
|
||||
@ -71,8 +72,8 @@ export default function SignupInvite() {
|
||||
const pair = nacl.box.keyPair();
|
||||
const secretKeyUint8Array = pair.secretKey;
|
||||
const publicKeyUint8Array = pair.publicKey;
|
||||
const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array);
|
||||
const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array);
|
||||
const PRIVATE_KEY = encodeBase64(secretKeyUint8Array);
|
||||
const PUBLIC_KEY = encodeBase64(publicKeyUint8Array);
|
||||
|
||||
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
|
||||
text: PRIVATE_KEY,
|
||||
@ -81,7 +82,7 @@ export default function SignupInvite() {
|
||||
.padStart(
|
||||
32 + (password.slice(0, 32).length - new Blob([password]).size),
|
||||
'0'
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
|
||||
@ -89,7 +90,7 @@ export default function SignupInvite() {
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password: password
|
||||
password: password,
|
||||
},
|
||||
async () => {
|
||||
client.createVerifier(async (err, result) => {
|
||||
@ -103,11 +104,11 @@ export default function SignupInvite() {
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
token: verificationToken
|
||||
token: verificationToken,
|
||||
});
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
if (!errorCheck && response.status == '200') {
|
||||
if (!errorCheck && response.status == 200) {
|
||||
response = await response.json();
|
||||
|
||||
localStorage.setItem('publicKey', PUBLIC_KEY);
|
||||
@ -140,42 +141,42 @@ export default function SignupInvite() {
|
||||
|
||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||
const stepConfirmEmail = (
|
||||
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl text-center font-semibold mb-6 flex justify-center text-primary-100">
|
||||
<div className='bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl'>
|
||||
<p className='text-4xl text-center font-semibold mb-6 flex justify-center text-primary-100'>
|
||||
Confirm your email
|
||||
</p>
|
||||
<Image
|
||||
src="/images/dragon-signupinvite.svg"
|
||||
src='/images/dragon-signupinvite.svg'
|
||||
height={262}
|
||||
width={410}
|
||||
alt="verify email"
|
||||
alt='verify email'
|
||||
></Image>
|
||||
<div className="flex max-w-max flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-10 mb-2">
|
||||
<div className='flex flex-col items-center justify-center md:p-2 max-h-24 max-w-md mx-auto text-lg px-4 mt-10 mb-2'>
|
||||
<Button
|
||||
text="Confirm Email"
|
||||
text='Confirm Email'
|
||||
onButtonPressed={async () => {
|
||||
const response = await verifySignupInvite({
|
||||
email,
|
||||
code: token
|
||||
code: token,
|
||||
});
|
||||
if (response.status == 200) {
|
||||
const res = await response.json();
|
||||
// user will have temp token if doesn't have an account
|
||||
// then continue with account setup workflow
|
||||
if(res?.token){
|
||||
if (res?.token) {
|
||||
setVerificationToken(res.token);
|
||||
setStep(2);
|
||||
} else {
|
||||
// user will be redirected to dashboard
|
||||
// if not logged in gets kicked out to login
|
||||
router.push("/dashboard")
|
||||
// if not logged in gets kicked out to login
|
||||
router.push('/dashboard');
|
||||
}
|
||||
} else {
|
||||
console.log('ERROR', response);
|
||||
router.push('/requestnewinvite');
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
size='lg'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -183,37 +184,37 @@ export default function SignupInvite() {
|
||||
|
||||
// Because this is the invite signup - we directly go to the last step of signup (email is already verified)
|
||||
const main = (
|
||||
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 md:mb-16">
|
||||
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
<div className='bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 md:mb-16'>
|
||||
<p className='text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
|
||||
Almost there!
|
||||
</p>
|
||||
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
|
||||
<div className='relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24'>
|
||||
<InputField
|
||||
label="First Name"
|
||||
label='First Name'
|
||||
onChangeHandler={setFirstName}
|
||||
type="name"
|
||||
type='name'
|
||||
value={firstName}
|
||||
isRequired
|
||||
errorText="Please input your first name."
|
||||
errorText='Please input your first name.'
|
||||
error={firstNameError}
|
||||
autoComplete="given-name"
|
||||
autoComplete='given-name'
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24">
|
||||
<div className='flex items-center justify-center w-full md:p-2 rounded-lg max-h-24'>
|
||||
<InputField
|
||||
label="Last Name"
|
||||
label='Last Name'
|
||||
onChangeHandler={setLastName}
|
||||
type="name"
|
||||
type='name'
|
||||
value={lastName}
|
||||
isRequired
|
||||
errorText="Please input your last name."
|
||||
errorText='Please input your last name.'
|
||||
error={lastNameError}
|
||||
autoComplete="family-name"
|
||||
autoComplete='family-name'
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
|
||||
<div className='mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60'>
|
||||
<InputField
|
||||
label="Password"
|
||||
label='Password'
|
||||
onChangeHandler={(password) => {
|
||||
setPassword(password);
|
||||
passwordCheck({
|
||||
@ -221,35 +222,35 @@ export default function SignupInvite() {
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
currentErrorCheck: false
|
||||
errorCheck: false,
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
type='password'
|
||||
value={password}
|
||||
isRequired
|
||||
error={
|
||||
passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase
|
||||
}
|
||||
autoComplete="new-password"
|
||||
id="new-password"
|
||||
autoComplete='new-password'
|
||||
id='new-password'
|
||||
/>
|
||||
{passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber ? (
|
||||
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
|
||||
<div className='w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md'>
|
||||
<div className={`text-gray-400 text-sm mb-1`}>
|
||||
Password should contain at least:
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
className='text-md text-red mr-2.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
@ -260,16 +261,16 @@ export default function SignupInvite() {
|
||||
14 characters
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
className='text-md text-red mr-2.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
@ -280,16 +281,16 @@ export default function SignupInvite() {
|
||||
1 lowercase character
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
<div className='flex flex-row justify-start items-center ml-1'>
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
className='text-md text-red mr-2.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
className='text-md text-primary mr-2'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
@ -302,17 +303,17 @@ export default function SignupInvite() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2"></div>
|
||||
<div className='py-2'></div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
|
||||
<div className='flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg'>
|
||||
<Button
|
||||
text="Sign Up"
|
||||
text='Sign Up'
|
||||
onButtonPressed={() => {
|
||||
signupErrorCheck();
|
||||
}}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
size='lg'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -320,38 +321,38 @@ export default function SignupInvite() {
|
||||
|
||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||
const step4 = (
|
||||
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
<div className='bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl'>
|
||||
<p className='text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary'>
|
||||
Save your Emergency Kit
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
|
||||
<div className='flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2'>
|
||||
<div>
|
||||
If you get locked out of your account, your Emergency Kit is the only
|
||||
way to sign in.
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<div className='mt-3'>
|
||||
We recommend you download it and keep it somewhere safe.
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
|
||||
<div className='w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4'>
|
||||
<FontAwesomeIcon icon={faWarning} className='ml-2 mr-4 text-4xl' />
|
||||
It contains your Secret Key which we cannot access or recover for you if
|
||||
you lose it.
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
|
||||
<div className='flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg'>
|
||||
<Button
|
||||
text="Download PDF"
|
||||
text='Download PDF'
|
||||
onButtonPressed={async () => {
|
||||
await issueBackupKey({
|
||||
email,
|
||||
password,
|
||||
personalName: firstName + ' ' + lastName,
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued
|
||||
setBackupKeyIssued,
|
||||
});
|
||||
router.push('/noprojects/');
|
||||
}}
|
||||
size="lg"
|
||||
size='lg'
|
||||
/>
|
||||
{/* <div
|
||||
className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer"
|
||||
@ -370,18 +371,18 @@ export default function SignupInvite() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
|
||||
<div className='bg-bunker-800 h-screen flex flex-col items-center justify-center'>
|
||||
<Head>
|
||||
<title>Sign Up</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="flex justify-center mb-2 md:mb-4 opacity-80 cursor-pointer">
|
||||
<Link href='/'>
|
||||
<div className='flex justify-center mb-2 md:mb-4 opacity-80 cursor-pointer'>
|
||||
<Image
|
||||
src="/images/biglogo.png"
|
||||
src='/images/biglogo.png'
|
||||
height={90}
|
||||
width={120}
|
||||
alt="Infisical Wide Logo"
|
||||
alt='Infisical Wide Logo'
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
@ -1,17 +1,17 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { faMagnifyingGlass, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { faMagnifyingGlass, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import AddProjectMemberDialog from "~/components/basic/dialog/AddProjectMemberDialog";
|
||||
import UserTable from "~/components/basic/table/UserTable";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import guidGenerator from "~/utilities/randomId";
|
||||
import { getTranslatedServerSideProps } from "~/utilities/withTranslateProps";
|
||||
import Button from '~/components/basic/buttons/Button';
|
||||
import AddProjectMemberDialog from '~/components/basic/dialog/AddProjectMemberDialog';
|
||||
import UserTable from '~/components/basic/table/UserTable';
|
||||
import NavHeader from '~/components/navigation/NavHeader';
|
||||
import guidGenerator from '~/utilities/randomId';
|
||||
import { getTranslatedServerSideProps } from '~/utilities/withTranslateProps';
|
||||
|
||||
import getOrganizationUsers from '../api/organization/GetOrgUsers';
|
||||
import getUser from '../api/user/getUser';
|
||||
@ -21,7 +21,7 @@ import getWorkspaceUsers from '../api/workspace/getWorkspaceUsers';
|
||||
import uploadKeys from '../api/workspace/uploadKeys';
|
||||
|
||||
interface UserProps {
|
||||
firstName: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
_id: string;
|
||||
@ -29,7 +29,7 @@ interface UserProps {
|
||||
}
|
||||
|
||||
interface MembershipProps {
|
||||
user: UserProps
|
||||
user: UserProps;
|
||||
inviteEmail: string;
|
||||
role: string;
|
||||
status: string;
|
||||
@ -40,7 +40,7 @@ interface MembershipProps {
|
||||
const crypto = require('crypto');
|
||||
const {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric
|
||||
encryptAssymmetric,
|
||||
} = require('../../components/utilities/cryptography/crypto');
|
||||
const nacl = require('tweetnacl');
|
||||
nacl.util = require('tweetnacl-util');
|
||||
@ -84,16 +84,21 @@ export default function Users() {
|
||||
ciphertext: result.latestKey.encryptedKey,
|
||||
nonce: result.latestKey.nonce,
|
||||
publicKey: result.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: result.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
uploadKeys(String(router.query.id), result.invitee._id, ciphertext, nonce);
|
||||
uploadKeys(
|
||||
String(router.query.id),
|
||||
result.invitee._id,
|
||||
ciphertext,
|
||||
nonce
|
||||
);
|
||||
}
|
||||
setEmail('');
|
||||
setIsAddOpen(false);
|
||||
@ -105,7 +110,7 @@ export default function Users() {
|
||||
}
|
||||
|
||||
const [userList, setUserList] = useState([]);
|
||||
const [orgUserList, setOrgUserList] = useState([]);
|
||||
const [orgUserList, setOrgUserList] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@ -114,55 +119,65 @@ export default function Users() {
|
||||
|
||||
// This part quiries the current users of a project
|
||||
const workspaceUsers = await getWorkspaceUsers({
|
||||
workspaceId: String(router.query.id)
|
||||
workspaceId: String(router.query.id),
|
||||
});
|
||||
const tempUserList = workspaceUsers.map((membership: MembershipProps) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: membership.user?.firstName,
|
||||
lastName: membership.user?.lastName,
|
||||
email: membership.user?.email == null ? membership.inviteEmail : membership.user?.email,
|
||||
role: membership?.role,
|
||||
status: membership?.status,
|
||||
userId: membership.user?._id,
|
||||
membershipId: membership._id,
|
||||
publicKey: membership.user?.publicKey
|
||||
}));
|
||||
const tempUserList = workspaceUsers.map(
|
||||
(membership: MembershipProps) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: membership.user?.firstName,
|
||||
lastName: membership.user?.lastName,
|
||||
email:
|
||||
membership.user?.email == null
|
||||
? membership.inviteEmail
|
||||
: membership.user?.email,
|
||||
role: membership?.role,
|
||||
status: membership?.status,
|
||||
userId: membership.user?._id,
|
||||
membershipId: membership._id,
|
||||
publicKey: membership.user?.publicKey,
|
||||
})
|
||||
);
|
||||
setUserList(tempUserList);
|
||||
|
||||
// This is needed to know wha users from an org (if any), we are able to add to a certain project
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: String(localStorage.getItem('orgData.id'))
|
||||
orgId: String(localStorage.getItem('orgData.id')),
|
||||
});
|
||||
setOrgUserList(orgUsers);
|
||||
setEmail(
|
||||
orgUsers
|
||||
?.filter((membership: MembershipProps) => membership.status == 'accepted')
|
||||
?.filter(
|
||||
(membership: MembershipProps) => membership.status == 'accepted'
|
||||
)
|
||||
.map((membership: MembershipProps) => membership.user.email)
|
||||
.filter(
|
||||
(email: string) => !tempUserList?.map((user1: UserProps) => user1.email).includes(email)
|
||||
(email: string) =>
|
||||
!tempUserList
|
||||
?.map((user1: UserProps) => user1.email)
|
||||
.includes(email)
|
||||
)[0]
|
||||
);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return userList ? (
|
||||
<div className="bg-bunker-800 md:h-screen flex flex-col justify-start">
|
||||
<div className='bg-bunker-800 md:h-screen flex flex-col justify-start'>
|
||||
<Head>
|
||||
<title>
|
||||
{t("common:head-title", { title: t("settings-members:title") })}
|
||||
{t('common:head-title', { title: t('settings-members:title') })}
|
||||
</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<NavHeader
|
||||
pageName={t("settings-members:title")}
|
||||
pageName={t('settings-members:title')}
|
||||
isProjectRelated={true}
|
||||
/>
|
||||
<div className="flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl">
|
||||
<p className="font-semibold mr-4 text-white">
|
||||
{t("settings-members:title")}
|
||||
<div className='flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl'>
|
||||
<p className='font-semibold mr-4 text-white'>
|
||||
{t('settings-members:title')}
|
||||
</p>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
{t("settings-members:description")}
|
||||
<p className='mr-4 text-base text-gray-400'>
|
||||
{t('settings-members:description')}
|
||||
</p>
|
||||
</div>
|
||||
<AddProjectMemberDialog
|
||||
@ -171,39 +186,42 @@ export default function Users() {
|
||||
submitModal={submitAddModal}
|
||||
email={email}
|
||||
data={orgUserList
|
||||
?.filter((membership: MembershipProps) => membership.status == 'accepted')
|
||||
?.filter(
|
||||
(membership: MembershipProps) => membership.status == 'accepted'
|
||||
)
|
||||
.map((membership: MembershipProps) => membership.user.email)
|
||||
.filter(
|
||||
(email) => !userList?.map((user1: UserProps) => user1.email).includes(email)
|
||||
(email) =>
|
||||
!userList?.map((user1: UserProps) => user1.email).includes(email)
|
||||
)}
|
||||
workspaceId={workspaceId}
|
||||
setEmail={setEmail}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
<div className="px-6 pb-1 w-full flex flex-row items-start min-w-6xl max-w-6xl">
|
||||
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center">
|
||||
<div className='px-6 pb-1 w-full flex flex-row items-start min-w-6xl max-w-6xl'>
|
||||
<div className='h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center'>
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
|
||||
className='bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400'
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
|
||||
className='pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none'
|
||||
value={searchUsers}
|
||||
onChange={(e) => setSearchUsers(e.target.value)}
|
||||
placeholder={String(t("section-members:search-members"))}
|
||||
placeholder={String(t('section-members:search-members'))}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
|
||||
<div className='mt-2 ml-2 min-w-max flex flex-row items-start justify-start'>
|
||||
<Button
|
||||
text={String(t("section-members:add-member"))}
|
||||
text={String(t('section-members:add-member'))}
|
||||
onButtonPressed={openAddModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
color='mineshaft'
|
||||
size='md'
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="block overflow-y-auto min-w-6xl max-w-6xl px-6">
|
||||
<div className='block overflow-y-auto min-w-6xl max-w-6xl px-6'>
|
||||
<UserTable
|
||||
userData={userList}
|
||||
changeData={setUserList}
|
||||
@ -218,13 +236,13 @@ export default function Users() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
|
||||
<div className='relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center'>
|
||||
<div className='absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full'></div>
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
src='/images/loading/loading.gif'
|
||||
height={70}
|
||||
width={120}
|
||||
alt="loading animation"
|
||||
alt='loading animation'
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
@ -233,7 +251,7 @@ export default function Users() {
|
||||
Users.requireAuth = true;
|
||||
|
||||
export const getServerSideProps = getTranslatedServerSideProps([
|
||||
"settings",
|
||||
"settings-members",
|
||||
"section-members",
|
||||
'settings',
|
||||
'settings-members',
|
||||
'section-members',
|
||||
]);
|
||||
|
@ -1,40 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
const queryString = require("query-string");
|
||||
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
|
||||
|
||||
export default function Vercel() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
try {
|
||||
if (state === localStorage.getItem('latestCSRFToken')) {
|
||||
localStorage.removeItem('latestCSRFToken');
|
||||
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem('projectData.id'),
|
||||
code,
|
||||
integration: "vercel"
|
||||
});
|
||||
|
||||
router.push("/integrations/" + localStorage.getItem("projectData.id"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Vercel integration error: ', err);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Vercel.requireAuth = true;
|
46
frontend/pages/vercel.tsx
Normal file
46
frontend/pages/vercel.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import queryString from 'query-string';
|
||||
|
||||
import AuthorizeIntegration from './api/integrations/authorizeIntegration';
|
||||
|
||||
export default function Vercel() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
// type check
|
||||
if (!code) throw new Error('Code not found');
|
||||
|
||||
if (state === localStorage.getItem('latestCSRFToken')) {
|
||||
localStorage.removeItem('latestCSRFToken');
|
||||
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem('projectData.id') as string,
|
||||
code: code as string,
|
||||
integration: 'vercel',
|
||||
});
|
||||
|
||||
router.push(
|
||||
'/integrations/' + localStorage.getItem('projectData.id')
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Vercel integration error: ', err);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Vercel.requireAuth = true;
|
1
frontend/tsconfig.tsbuildinfo
Normal file
1
frontend/tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user