Merge pull request #222 from akhilmhdh/feat/migration-ts

migration(frontend): migrated frontend files to ts execpt dialog component
This commit is contained in:
mv-turtle
2023-01-14 16:00:53 -08:00
committed by GitHub
53 changed files with 1761 additions and 1626 deletions

View File

@ -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
);
}

View File

@ -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>
);

View File

@ -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>
)}

View File

@ -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>

View File

@ -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>
)
);
}

View File

@ -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>

View File

@ -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>
</>
);
};

View 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>
</>
);
};

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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>
)
);
}

View File

@ -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;

View File

@ -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('');

View File

@ -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,
};

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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",

View File

@ -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",

View File

@ -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>

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View 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;

View File

@ -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
View 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;

View File

@ -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
View 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;

View File

@ -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",
]);

View 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',
]);

View File

@ -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;

View 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;

View File

@ -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"]);

View 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']);

View File

@ -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}

View File

@ -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>

View File

@ -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",
]);

View 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',
]);

View File

@ -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',
]);

View File

@ -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',
]);

View File

@ -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}

View File

@ -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>

View File

@ -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',
]);

View File

@ -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
View 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;

File diff suppressed because one or more lines are too long