lint(frontend): apply two space instead of tab

pull/45/head
이강준 1 year ago
parent 66d2a2724e
commit ef234a270f
No known key found for this signature in database
GPG Key ID: FB63ECACBEBFE9CD

@ -1,4 +1,4 @@
{
"tabWidth": 2,
"useTabs": false
"tabWidth": 2,
"useTabs": false
}

@ -2,9 +2,8 @@ import { useEffect, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
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

@ -1,15 +1,20 @@
import posthog from "posthog-js";
import { ENV, POSTHOG_API_KEY, POSTHOG_HOST, TELEMETRY_ENABLED } from "../utilities/config";
import {
ENV,
POSTHOG_API_KEY,
POSTHOG_HOST,
TELEMETRY_ENABLED,
} from "../utilities/config";
export const initPostHog = () => {
if (typeof window !== "undefined") {
if (ENV == "production" && TELEMETRY_ENABLED) {
posthog.init(POSTHOG_API_KEY, {
api_host: POSTHOG_HOST,
});
}
}
if (typeof window !== "undefined") {
if (ENV == "production" && TELEMETRY_ENABLED) {
posthog.init(POSTHOG_API_KEY, {
api_host: POSTHOG_HOST,
});
}
}
return posthog;
return posthog;
};

@ -3,17 +3,15 @@ import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function Error({ text }) {
return (
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
<FontAwesomeIcon
icon={faExclamationTriangle}
className="text-white mt-1.5 mb-2 mx-2"
/>
{text && (
<p className="relative top-0 text-white mr-2 text-sm py-1">
{text}
</p>
)}
</div>
);
return (
<div className="relative bg-red-500 opacity-100 border flex flex-row justify-center m-auto items-center w-fit rounded-full mb-4">
<FontAwesomeIcon
icon={faExclamationTriangle}
className="text-white mt-1.5 mb-2 mx-2"
/>
{text && (
<p className="relative top-0 text-white mr-2 text-sm py-1">{text}</p>
)}
</div>
);
}

@ -2,11 +2,11 @@ import React from "react";
import { useState } from "react";
import { useRouter } from "next/router";
import {
faCircle,
faCircleExclamation,
faE,
faEye,
faEyeSlash,
faCircle,
faCircleExclamation,
faE,
faEye,
faEyeSlash,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -14,36 +14,36 @@ import guidGenerator from "../utilities/randomId";
import Error from "./Error";
const InputField = (props) => {
const [passwordVisible, setPasswordVisible] = useState(false);
const router = useRouter();
const [passwordVisible, setPasswordVisible] = useState(false);
const router = useRouter();
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">
{props.label}
</p>
{props.text && (
<p className="text-xs text-gray-400 mb-2">{props.text}</p>
)}
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={props.type}
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"
name={props.name}
readOnly
/>
</div>
);
} 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>
{/* {props.label == "Password" && router.asPath != "/login" && (
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">
{props.label}
</p>
{props.text && (
<p className="text-xs text-gray-400 mb-2">{props.text}</p>
)}
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={props.type}
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"
name={props.name}
readOnly
/>
</div>
);
} 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>
{/* {props.label == "Password" && router.asPath != "/login" && (
<div className="mb-0.5 relative inline-block text-gray-400 underline hover:text-primary duration-200">
<FontAwesomeIcon
icon={faCircleExclamation}
@ -59,73 +59,71 @@ const InputField = (props) => {
</span>
</div>
)} */}
</div>
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
props.error ? "border-red" : "border-mineshaft-500"
} rounded-md`}
>
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={passwordVisible == false ? props.type : "text"}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className={`${
props.blurred
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
: ""
} ${
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"
/>
{props.label?.includes("Password") && (
<button
onClick={() => {
setPasswordVisible(!passwordVisible);
}}
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
>
{passwordVisible ? (
<FontAwesomeIcon icon={faEyeSlash} />
) : (
<FontAwesomeIcon icon={faEye} />
)}
</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>
{props.value
.split("")
.slice(0, 54)
.map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"
icon={faCircle}
/>
))}
</div>
)}
{/* {props.error && (
</div>
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
props.error ? "border-red" : "border-mineshaft-500"
} rounded-md`}
>
<input
onChange={(e) => props.onChangeHandler(e.target.value)}
type={passwordVisible == false ? props.type : "text"}
placeholder={props.placeholder}
value={props.value}
required={props.isRequired}
className={`${
props.blurred
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
: ""
} ${
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"
/>
{props.label?.includes("Password") && (
<button
onClick={() => {
setPasswordVisible(!passwordVisible);
}}
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
>
{passwordVisible ? (
<FontAwesomeIcon icon={faEyeSlash} />
) : (
<FontAwesomeIcon icon={faEye} />
)}
</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>
{props.value
.split("")
.slice(0, 54)
.map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"
icon={faCircle}
/>
))}
</div>
)}
{/* {props.error && (
<div className="absolute z-20 flex items-end justify-end mt-4 mr-1.5 self-end">
<Error />
</div>
)} */}
</div>
{props.error && (
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">
{props.errorText}
</p>
)}
</div>
);
}
</div>
{props.error && (
<p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">
{props.errorText}
</p>
)}
</div>
);
}
};
export default React.memo(InputField);

@ -1,7 +1,11 @@
import React from "react";
import { Fragment } from "react";
import { useRouter } from "next/router";
import { faAngleDown,faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
import {
faAngleDown,
faCheck,
faPlus,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Listbox, Transition } from "@headlessui/react";
@ -11,98 +15,97 @@ import { Listbox, Transition } from "@headlessui/react";
* @returns
*/
export default function ListBox({
selected,
onChange,
data,
text,
buttonAction,
width,
workspaceMapping = [],
selected,
onChange,
data,
text,
buttonAction,
width,
workspaceMapping = [],
}) {
const router = useRouter();
const router = useRouter();
return (
<Listbox value={selected} onChange={onChange}>
<div className="relative">
<Listbox.Button
className={`text-gray-400 relative ${
width == "full" ? "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">
{text}
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
{" "}
{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>
)}
</Listbox.Button>
{data && (
<Transition
as={Fragment}
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">
{data.map((person, personIdx) => (
<Listbox.Option
key={personIdx}
className={({ active, selected }) =>
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
selected
? "bg-white/10 text-gray-400 font-bold"
: ""
} ${
active & !selected
? "bg-white/5 text-mineshaft-200 cursor-pointer"
: "text-gray-400"
} `
}
value={person}
>
{({ selected }) => (
<>
<span
className={`block truncate text-primary${
selected
? "font-medium"
: "font-normal"
}`}
>
{person}
</span>
{selected ? (
<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"/>
</span>
) : null}
</>
)}
</Listbox.Option>
))}
{buttonAction && (
<button
onClick={buttonAction}
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"/>
</span>
Add Project
</div>
</button>
)}
</Listbox.Options>
</Transition>
)}
</div>
</Listbox>
);
return (
<Listbox value={selected} onChange={onChange}>
<div className="relative">
<Listbox.Button
className={`text-gray-400 relative ${
width == "full" ? "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">
{text}
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
{" "}
{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>
)}
</Listbox.Button>
{data && (
<Transition
as={Fragment}
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">
{data.map((person, personIdx) => (
<Listbox.Option
key={personIdx}
className={({ active, selected }) =>
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
selected ? "bg-white/10 text-gray-400 font-bold" : ""
} ${
active & !selected
? "bg-white/5 text-mineshaft-200 cursor-pointer"
: "text-gray-400"
} `
}
value={person}
>
{({ selected }) => (
<>
<span
className={`block truncate text-primary${
selected ? "font-medium" : "font-normal"
}`}
>
{person}
</span>
{selected ? (
<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"
/>
</span>
) : null}
</>
)}
</Listbox.Option>
))}
{buttonAction && (
<button
onClick={buttonAction}
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" />
</span>
Add Project
</div>
</button>
)}
</Listbox.Options>
</Transition>
)}
</div>
</Listbox>
);
}

@ -7,105 +7,105 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
var classNames = require("classnames");
export default function Button({
text,
onButtonPressed,
link,
loading,
color,
size,
icon,
active = true,
iconDisabled,
textDisabled
text,
onButtonPressed,
link,
loading,
color,
size,
icon,
active = true,
iconDisabled,
textDisabled,
}) {
let styleButton = classNames(
"group m-auto md:m-0 inline-block rounded-md duration-200",
let styleButton = classNames(
"group m-auto md:m-0 inline-block rounded-md duration-200",
// Setting background colors and hover modes
color == "mineshaft" && active && "bg-mineshaft-700 hover:bg-primary",
color == "mineshaft" && !active && "bg-mineshaft",
(color == "primary" || !color) && active && "bg-primary hover:opacity-80",
(color == "primary" || !color) && !active && "bg-primary",
color == "red" && "bg-red",
// Changing the opacity when active vs when not
active ? "opacity-100 cursor-pointer" : "opacity-40",
// Setting background colors and hover modes
color == "mineshaft" && active && "bg-mineshaft-700 hover:bg-primary",
color == "mineshaft" && !active && "bg-mineshaft",
(color == "primary" || !color) && active && "bg-primary hover:opacity-80",
(color == "primary" || !color) && !active && "bg-primary",
color == "red" && "bg-red",
// Setting the button sizes
size == "md" && "h-10 w-full px-2 md:px-4",
size == "lg" && "h-12 w-full px-2 md:px-8",
!size && "md:py-1 px-3 md:px-8",
size == "icon-md" && "h-10 w-10 flex items-center justify-center",
size == "icon-sm" && "h-9 w-9 flex items-center justify-center",
);
// Changing the opacity when active vs when not
active ? "opacity-100 cursor-pointer" : "opacity-40",
let styleMainDiv = classNames(
"relative font-medium flex items-center",
// Setting the button sizes
size == "md" && "h-10 w-full px-2 md:px-4",
size == "lg" && "h-12 w-full px-2 md:px-8",
!size && "md:py-1 px-3 md:px-8",
size == "icon-md" && "h-10 w-10 flex items-center justify-center",
size == "icon-sm" && "h-9 w-9 flex items-center justify-center"
);
// Setting the text color for the text and icon
color == "mineshaft" && "text-gray-400",
color != "mineshaft" && color != "red" && "text-black",
color == "red" && "text-gray-200",
active && color != "red" ? "group-hover:text-black" : "",
let styleMainDiv = classNames(
"relative font-medium flex items-center",
size == "icon" && "flex items-center justify-center",
);
// Setting the text color for the text and icon
color == "mineshaft" && "text-gray-400",
color != "mineshaft" && color != "red" && "text-black",
color == "red" && "text-gray-200",
active && color != "red" ? "group-hover:text-black" : "",
let textStyle = classNames(
"relative duration-200",
size == "icon" && "flex items-center justify-center"
);
// Show the loading sign if the loading indicator is on
loading == true ? "opacity-0" : "opacity-100",
size == "md" && "text-sm",
size == "lg" && "text-lg"
);
let textStyle = classNames(
"relative duration-200",
const button = (
<button
disabled={!active}
onClick={onButtonPressed}
className={styleButton}
>
<div className={styleMainDiv}>
<div
className={`${
loading == true ? "opacity-100" : "opacity-0"
} absolute flex items-center px-2 duration-200`}
>
<Image
src="/images/loading/loadingblack.gif"
height={25}
width={42}
alt="loading animation"
className={`rounded-xl`}
></Image>
</div>
{icon &&
<FontAwesomeIcon
icon={icon}
className={`flex my-auto font-extrabold ${size == "icon-sm" ? "text-sm" : "text-md"} ${(text || textDisabled) && "mr-2"}`}
/>
}
{iconDisabled &&
<FontAwesomeIcon
icon={iconDisabled}
className={`flex my-auto font-extrabold ${size == "icon-sm" ? "text-sm" : "text-md"} ${(text || textDisabled) && "mr-2"}`}
/>
}
{(text || textDisabled) &&
<p
className={textStyle}
>
{active ? text : textDisabled}
</p>
}
</div>
</button>
);
// Show the loading sign if the loading indicator is on
loading == true ? "opacity-0" : "opacity-100",
size == "md" && "text-sm",
size == "lg" && "text-lg"
);
if (link) {
return <Link href={link}>{button}</Link>;
}
const button = (
<button
disabled={!active}
onClick={onButtonPressed}
className={styleButton}
>
<div className={styleMainDiv}>
<div
className={`${
loading == true ? "opacity-100" : "opacity-0"
} absolute flex items-center px-2 duration-200`}
>
<Image
src="/images/loading/loadingblack.gif"
height={25}
width={42}
alt="loading animation"
className={`rounded-xl`}
></Image>
</div>
{icon && (
<FontAwesomeIcon
icon={icon}
className={`flex my-auto font-extrabold ${
size == "icon-sm" ? "text-sm" : "text-md"
} ${(text || textDisabled) && "mr-2"}`}
/>
)}
{iconDisabled && (
<FontAwesomeIcon
icon={iconDisabled}
className={`flex my-auto font-extrabold ${
size == "icon-sm" ? "text-sm" : "text-md"
} ${(text || textDisabled) && "mr-2"}`}
/>
)}
{(text || textDisabled) && (
<p className={textStyle}>{active ? text : textDisabled}</p>
)}
</div>
</button>
);
return button;
if (link) {
return <Link href={link}>{button}</Link>;
}
return button;
}

@ -7,91 +7,92 @@ import Button from "../buttons/Button";
import InputField from "../InputField";
const AddIncidentContactDialog = ({
isOpen,
closeModal,
workspaceId,
incidentContacts,
setIncidentContacts,
isOpen,
closeModal,
workspaceId,
incidentContacts,
setIncidentContacts,
}) => {
let [incidentContactEmail, setIncidentContactEmail] = useState("");
let [incidentContactEmail, setIncidentContactEmail] = useState("");
const submit = () => {
setIncidentContacts(
incidentContacts?.length > 0
? incidentContacts.concat([incidentContactEmail])
: [incidentContactEmail]
);
addIncidentContact(localStorage.getItem("orgData.id"), incidentContactEmail);
closeModal();
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
const submit = () => {
setIncidentContacts(
incidentContacts?.length > 0
? incidentContacts.concat([incidentContactEmail])
: [incidentContactEmail]
);
addIncidentContact(
localStorage.getItem("orgData.id"),
incidentContactEmail
);
closeModal();
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Add an Incident Contact
</Dialog.Title>
<div className="mt-2 mb-2">
<p className="text-sm text-gray-500">
This contact will be notified in the
unlikely event of a severe incident.
</p>
</div>
<div className="max-h-28">
<InputField
label="Email"
onChangeHandler={
setIncidentContactEmail
}
type="varName"
value={incidentContactEmail}
placeholder=""
isRequired
/>
</div>
<div className="mt-6 max-w-max">
<Button
onButtonPressed={submit}
color="mineshaft"
text="Add Incident Contact"
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Add an Incident Contact
</Dialog.Title>
<div className="mt-2 mb-2">
<p className="text-sm text-gray-500">
This contact will be notified in the unlikely event of a
severe incident.
</p>
</div>
<div className="max-h-28">
<InputField
label="Email"
onChangeHandler={setIncidentContactEmail}
type="varName"
value={incidentContactEmail}
placeholder=""
isRequired
/>
</div>
<div className="mt-6 max-w-max">
<Button
onButtonPressed={submit}
color="mineshaft"
text="Add Incident Contact"
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddIncidentContactDialog;

@ -6,146 +6,133 @@ import Button from "../buttons/Button";
import ListBox from "../Listbox";
const AddProjectMemberDialog = ({
isOpen,
closeModal,
submitModal,
data,
email,
workspaceId,
setEmail,
isOpen,
closeModal,
submitModal,
data,
email,
workspaceId,
setEmail,
}) => {
const router = useRouter();
const router = useRouter();
return (
<div className="z-50">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
return (
<div className="z-50">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
{data?.length > 0 ? (
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
Add a member to your project
</Dialog.Title>
) : (
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
All the users in your organization
are already invited.
</Dialog.Title>
)}
<div className="mt-2 mb-4">
{data?.length > 0 ? (
<div className="flex flex-col">
<p className="text-sm text-gray-500">
The user will receive an email
with the instructions.
</p>
<div className="">
<button
type="button"
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() =>
router.push(
"/settings/org/" +
router.query.id
)
}
>
If you are looking to add users to your org,
</button>
<button
type="button"
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() =>
router.push(
"/settings/org/" +
router.query.id +
"?invite"
)
}
>
click here.
</button>
</div>
</div>
) : (
<p className="text-sm text-gray-500">
Add more users to the
organization first.
</p>
)}
</div>
<div className="max-h-28">
{data?.length > 0 && (
<ListBox
selected={
email ? email : data[0]
}
onChange={setEmail}
data={data}
width="full"
/>
)}
</div>
<div className="max-w-max">
{data?.length > 0 ? (
<div className="mt-6 flex flex-col justify-start w-max">
<Button
onButtonPressed={submitModal}
color="mineshaft"
text="Add Member"
size="md"
/>
</div>
) : (
<Button
onButtonPressed={() =>
router.push(
"/settings/org/" +
router.query.id
)
}
color="mineshaft"
text="Add Users to Organization"
size="md"
/>
)}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
{data?.length > 0 ? (
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
Add a member to your project
</Dialog.Title>
) : (
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
All the users in your organization are already invited.
</Dialog.Title>
)}
<div className="mt-2 mb-4">
{data?.length > 0 ? (
<div className="flex flex-col">
<p className="text-sm text-gray-500">
The user will receive an email with the instructions.
</p>
<div className="">
<button
type="button"
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() =>
router.push("/settings/org/" + router.query.id)
}
>
If you are looking to add users to your org,
</button>
<button
type="button"
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() =>
router.push(
"/settings/org/" + router.query.id + "?invite"
)
}
>
click here.
</button>
</div>
</div>
) : (
<p className="text-sm text-gray-500">
Add more users to the organization first.
</p>
)}
</div>
<div className="max-h-28">
{data?.length > 0 && (
<ListBox
selected={email ? email : data[0]}
onChange={setEmail}
data={data}
width="full"
/>
)}
</div>
<div className="max-w-max">
{data?.length > 0 ? (
<div className="mt-6 flex flex-col justify-start w-max">
<Button
onButtonPressed={submitModal}
color="mineshaft"
text="Add Member"
size="md"
/>
</div>
) : (
<Button
onButtonPressed={() =>
router.push("/settings/org/" + router.query.id)
}
color="mineshaft"
text="Add Users to Organization"
size="md"
/>
)}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddProjectMemberDialog;

@ -8,276 +8,254 @@ import nacl from "tweetnacl";
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import { decryptAssymmetric, encryptAssymmetric } from "../../utilities/cryptography/crypto";
import {
decryptAssymmetric,
encryptAssymmetric,
} from "../../utilities/cryptography/crypto";
import Button from "../buttons/Button";
import InputField from "../InputField";
import ListBox from "../Listbox";
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const expiryMapping = {
"1 day": 86400,
"7 days": 604800,
"1 month": 2592000,
"1 day": 86400,
"7 days": 604800,
"1 month": 2592000,
};
const AddServiceTokenDialog = ({
isOpen,
closeModal,
workspaceId,
workspaceName,
isOpen,
closeModal,
workspaceId,
workspaceName,
}) => {
const router = useRouter();
const [serviceToken, setServiceToken] = useState("");
const [serviceTokenName, setServiceTokenName] = useState("");
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const router = useRouter();
const [serviceToken, setServiceToken] = useState("");
const [serviceTokenName, setServiceTokenName] = useState("");
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey(workspaceId);
const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey(workspaceId);
const key = decryptAssymmetric({
ciphertext: latestFileKey.latestKey.encryptedKey,
nonce: latestFileKey.latestKey.nonce,
publicKey: latestFileKey.latestKey.sender.publicKey,
privateKey: localStorage.getItem("PRIVATE_KEY"),
});
const key = decryptAssymmetric({
ciphertext: latestFileKey.latestKey.encryptedKey,
nonce: latestFileKey.latestKey.nonce,
publicKey: latestFileKey.latestKey.sender.publicKey,
privateKey: localStorage.getItem("PRIVATE_KEY"),
});
// generate new public/private key pair
const pair = nacl.box.keyPair();
const publicKey = nacl.util.encodeBase64(pair.publicKey);
const privateKey = nacl.util.encodeBase64(pair.secretKey);
// generate new public/private key pair
const pair = nacl.box.keyPair();
const publicKey = nacl.util.encodeBase64(pair.publicKey);
const privateKey = nacl.util.encodeBase64(pair.secretKey);
// encrypt workspace key under newly-generated public key
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey,
});
// encrypt workspace key under newly-generated public key
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey,
});
let newServiceToken = await addServiceToken({
name: serviceTokenName,
workspaceId,
environment: envMapping[serviceTokenEnv],
expiresIn: expiryMapping[serviceTokenExpiresIn],
publicKey,
encryptedKey,
nonce,
});
let newServiceToken = await addServiceToken({
name: serviceTokenName,
workspaceId,
environment: envMapping[serviceTokenEnv],
expiresIn: expiryMapping[serviceTokenExpiresIn],
publicKey,
encryptedKey,
nonce,
});
const serviceToken = newServiceToken + "," + privateKey;
setServiceToken(serviceToken);
};
const serviceToken = newServiceToken + "," + privateKey;
setServiceToken(serviceToken);
};
function copyToClipboard() {
// Get the text field
var copyText = document.getElementById("serviceToken");
function copyToClipboard() {
// Get the text field
var copyText = document.getElementById("serviceToken");
// Select the text field
copyText.select();
copyText.setSelectionRange(0, 99999); // For mobile devices
// Select the text field
copyText.select();
copyText.setSelectionRange(0, 99999); // For mobile devices
// Copy the text inside the text field
navigator.clipboard.writeText(copyText.value);
// Copy the text inside the text field
navigator.clipboard.writeText(copyText.value);
setServiceTokenCopied(true);
setTimeout(() => setServiceTokenCopied(false), 2000);
// Alert the copied text
// alert("Copied the text: " + copyText.value);
}
setServiceTokenCopied(true);
setTimeout(() => setServiceTokenCopied(false), 2000);
// Alert the copied text
// alert("Copied the text: " + copyText.value);
}
const closeAddServiceTokenModal = () => {
closeModal();
setServiceTokenName("");
setServiceToken("");
};
const closeAddServiceTokenModal = () => {
closeModal();
setServiceTokenName("");
setServiceToken("");
};
return (
<div className="z-50">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
</Transition.Child>
return (
<div className="z-50">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
{serviceToken == "" ? (
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
Add a service token for{" "}
{workspaceName}
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
Specify the name,
environment, and expiry
period. When a token is
generated, you will only be
able to see it once before
it disappears. Make sure to
save it somewhere.
</p>
</div>
</div>
<div className="max-h-28 mb-2">
<InputField
label="Service Token Name"
onChangeHandler={
setServiceTokenName
}
type="varName"
value={serviceTokenName}
placeholder=""
isRequired
/>
</div>
<div className="max-h-28 mb-2">
<ListBox
selected={serviceTokenEnv}
onChange={setServiceTokenEnv}
data={[
"Development",
"Staging",
"Production",
"Testing",
]}
width="full"
text="Environment: "
/>
</div>
<div className="max-h-28">
<ListBox
selected={serviceTokenExpiresIn}
onChange={
setServiceTokenExpiresIn
}
data={[
"1 day",
"7 days",
"1 month",
]}
width="full"
text="Expires in: "
/>
</div>
<div className="max-w-max">
<div className="mt-6 flex flex-col justify-start w-max">
<Button
onButtonPressed={() =>
generateServiceToken()
}
color="mineshaft"
text="Add Service Token"
textDisabled="Add Service Token"
size="md"
active={
serviceTokenName == ""
? false
: true
}
/>
</div>
</div>
</Dialog.Panel>
) : (
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
Copy your service token
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
Once you close this popup,
you will never see your
service token again
</p>
</div>
</div>
<div className="w-full">
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-36">
<input
type="text"
value={serviceToken}
id="serviceToken"
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
></input>
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
{serviceToken}
</div>
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
<button
onClick={
copyToClipboard
}
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
>
{serviceTokenCopied ? (
<FontAwesomeIcon
icon={faCheck}
className="pr-0.5"
/>
) : (
<FontAwesomeIcon
icon={faCopy}
/>
)}
</button>
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
Click to Copy
</span>
</div>
</div>
</div>
<div className="mt-6 flex flex-col justify-start w-max">
<Button
onButtonPressed={() =>
closeAddServiceTokenModal()
}
color="mineshaft"
text="Close"
size="md"
/>
</div>
</Dialog.Panel>
)}
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
{serviceToken == "" ? (
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
Add a service token for {workspaceName}
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
Specify the name, environment, and expiry period. When
a token is generated, you will only be able to see it
once before it disappears. Make sure to save it
somewhere.
</p>
</div>
</div>
<div className="max-h-28 mb-2">
<InputField
label="Service Token Name"
onChangeHandler={setServiceTokenName}
type="varName"
value={serviceTokenName}
placeholder=""
isRequired
/>
</div>
<div className="max-h-28 mb-2">
<ListBox
selected={serviceTokenEnv}
onChange={setServiceTokenEnv}
data={[
"Development",
"Staging",
"Production",
"Testing",
]}
width="full"
text="Environment: "
/>
</div>
<div className="max-h-28">
<ListBox
selected={serviceTokenExpiresIn}
onChange={setServiceTokenExpiresIn}
data={["1 day", "7 days", "1 month"]}
width="full"
text="Expires in: "
/>
</div>
<div className="max-w-max">
<div className="mt-6 flex flex-col justify-start w-max">
<Button
onButtonPressed={() => generateServiceToken()}
color="mineshaft"
text="Add Service Token"
textDisabled="Add Service Token"
size="md"
active={serviceTokenName == "" ? false : true}
/>
</div>
</div>
</Dialog.Panel>
) : (
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
Copy your service token
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
Once you close this popup, you will never see your
service token again
</p>
</div>
</div>
<div className="w-full">
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-36">
<input
type="text"
value={serviceToken}
id="serviceToken"
className="invisible bg-white/0 text-gray-400 py-2 w-full px-2 min-w-full outline-none"
></input>
<div className="bg-white/0 max-w-md text-sm text-gray-400 py-2 w-full pl-14 pr-2 break-words outline-none">
{serviceToken}
</div>
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
<button
onClick={copyToClipboard}
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
>
{serviceTokenCopied ? (
<FontAwesomeIcon
icon={faCheck}
className="pr-0.5"
/>
) : (
<FontAwesomeIcon icon={faCopy} />
)}
</button>
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
Click to Copy
</span>
</div>
</div>
</div>
<div className="mt-6 flex flex-col justify-start w-max">
<Button
onButtonPressed={() => closeAddServiceTokenModal()}
color="mineshaft"
text="Close"
size="md"
/>
</div>
</Dialog.Panel>
)}
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddServiceTokenDialog;

@ -7,107 +7,103 @@ import Button from "../buttons/Button";
import InputField from "../InputField";
const AddUserDialog = ({
isOpen,
closeModal,
submitModal,
email,
workspaceId,
setEmail,
currentPlan,
orgName
isOpen,
closeModal,
submitModal,
email,
workspaceId,
setEmail,
currentPlan,
orgName,
}) => {
const submit = () => {
submitModal(email);
};
const router = useRouter();
const submit = () => {
submitModal(email);
};
const router = useRouter();
return (
<div className="z-50">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
return (
<div className="z-50">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-lg transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
Invite others to {orgName}
</Dialog.Title>
<div className="mt-2 mb-4">
<p className="text-sm text-gray-500">
An invite is specific to an email address
and expires after 1 day. For security reasons,
you will need to separately add members to projects.
</p>
</div>
<div className="max-h-28">
<InputField
label="Email"
onChangeHandler={setEmail}
type="varName"
value={email}
placeholder=""
isRequired
/>
</div>
{currentPlan == STRIPE_PRODUCT_STARTER && <div className="flex flex-row">
<button
type="button"
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() =>
router.push(
"/settings/billing/" +
router.query.id
)
}
>
You can add up to 5 members on a Free tier.
</button>
<button
type="button"
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() =>
router.push(
"/settings/billing/" +
router.query.id
)
}
>
Upgrade now.
</button>
</div>}
<div className="mt-4 max-w-max">
<Button
onButtonPressed={submit}
color="mineshaft"
text="Invite"
size="md"
/>
</div>
</Dialog.Panel>
{/* <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-lg transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
Invite others to {orgName}
</Dialog.Title>
<div className="mt-2 mb-4">
<p className="text-sm text-gray-500">
An invite is specific to an email address and expires
after 1 day. For security reasons, you will need to
separately add members to projects.
</p>
</div>
<div className="max-h-28">
<InputField
label="Email"
onChangeHandler={setEmail}
type="varName"
value={email}
placeholder=""
isRequired
/>
</div>
{currentPlan == STRIPE_PRODUCT_STARTER && (
<div className="flex flex-row">
<button
type="button"
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() =>
router.push("/settings/billing/" + router.query.id)
}
>
You can add up to 5 members on a Free tier.
</button>
<button
type="button"
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={() =>
router.push("/settings/billing/" + router.query.id)
}
>
Upgrade now.
</button>
</div>
)}
<div className="mt-4 max-w-max">
<Button
onButtonPressed={submit}
color="mineshaft"
text="Invite"
size="md"
/>
</div>
</Dialog.Panel>
{/* <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-xl font-medium leading-6 text-gray-300 z-50"
@ -139,13 +135,13 @@ const AddUserDialog = ({
</button>
</div>
</Dialog.Panel> */}
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddUserDialog;

@ -12,94 +12,93 @@ import { Checkbox } from "../table/Checkbox";
* @returns
*/
const AddWorkspaceDialog = ({
isOpen,
closeModal,
submitModal,
workspaceName,
setWorkspaceName,
error,
loading,
isOpen,
closeModal,
submitModal,
workspaceName,
setWorkspaceName,
error,
loading,
}) => {
const [addAllUsers, setAddAllUsers] = useState(true);
const submit = () => {
submitModal(workspaceName, addAllUsers);
};
const [addAllUsers, setAddAllUsers] = useState(true);
const submit = () => {
submitModal(workspaceName, addAllUsers);
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto z-50">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Create a new project
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
This project will contain your
environmental variables.
</p>
</div>
<div className="max-h-28 mt-4">
<InputField
label="Project Name"
onChangeHandler={setWorkspaceName}
type="varName"
value={workspaceName}
placeholder=""
isRequired
error={error.length > 0}
errorText={error}
/>
</div>
<div className="mt-4 ml-1">
<Checkbox
addAllUsers={addAllUsers}
setAddAllUsers={setAddAllUsers}
/>
</div>
<div className="mt-4 max-w-min">
<Button
onButtonPressed={submit}
loading={loading}
color="mineshaft"
text="Create"
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
<div className="fixed inset-0 overflow-y-auto z-50">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Create a new project
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
This project will contain your environmental variables.
</p>
</div>
<div className="max-h-28 mt-4">
<InputField
label="Project Name"
onChangeHandler={setWorkspaceName}
type="varName"
value={workspaceName}
placeholder=""
isRequired
error={error.length > 0}
errorText={error}
/>
</div>
<div className="mt-4 ml-1">
<Checkbox
addAllUsers={addAllUsers}
setAddAllUsers={setAddAllUsers}
/>
</div>
<div className="mt-4 max-w-min">
<Button
onButtonPressed={submit}
loading={loading}
color="mineshaft"
text="Create"
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddWorkspaceDialog;

@ -1,78 +1,83 @@
import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import InputField from '../InputField';
import InputField from "../InputField";
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
const DeleteUserDialog = ({
isOpen,
closeModal,
submitModal,
userIdToBeDeleted,
}) => {
const submit = () => {
submitModal(userIdToBeDeleted);
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
const DeleteUserDialog = ({isOpen, closeModal, submitModal, userIdToBeDeleted}) => {
const submit = () => {
submitModal(userIdToBeDeleted);
}
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Are you sure you want to remove this user from the
workspace?
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
This action is irrevertible.
</p>
</div>
<div className="mt-6">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit}
>
Delete
</button>
<button
type="button"
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit}
>
Are you sure you want to remove this user from the workspace?
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
This action is irrevertible.
</p>
</div>
<div className="mt-6">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit}
>
Delete
</button>
<button
type="button"
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</div>
);
</div>
</Dialog>
</Transition>
</div>
);
};
export default DeleteUserDialog;
export default DeleteUserDialog;

@ -3,11 +3,11 @@ import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import {
faGear,
faHouse,
faLink,
faMobile,
faUser,
faGear,
faHouse,
faLink,
faMobile,
faUser,
} from "@fortawesome/free-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -20,319 +20,285 @@ import createWorkspace from "~/pages/api/workspace/createWorkspace";
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
import NavBarDashboard from "../navigation/NavBarDashboard";
import { decryptAssymmetric, encryptAssymmetric } from "../utilities/cryptography/crypto";
import {
decryptAssymmetric,
encryptAssymmetric,
} from "../utilities/cryptography/crypto";
import Button from "./buttons/Button";
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
import Listbox from "./Listbox";
export default function Layout({ children }) {
const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]);
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
let [newWorkspaceName, setNewWorkspaceName] = useState("");
let [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]);
const [workspaceMapping, setWorkspaceMapping] = useState([{ 1: 2 }]);
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
let [newWorkspaceName, setNewWorkspaceName] = useState("");
let [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
function closeModal() {
setIsOpen(false);
}
function closeModal() {
setIsOpen(false);
}
// TODO: what to do about the fact that 2ids can have the same name
// TODO: what to do about the fact that 2ids can have the same name
/**
* When a user creates a new workspace, redirect them to the page of the new workspace.
* @param {*} workspaceName
*/
async function submitModal(workspaceName, addAllUsers) {
setLoading(true);
setTimeout(() => setLoading(false), 1500);
const workspaces = await getWorkspaces();
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace(
workspaceName,
localStorage.getItem("orgData.id")
);
let newWorkspaceId;
try {
newWorkspaceId = newWorkspace._id;
} catch (error) {
console.log(error);
}
if (addAllUsers) {
let orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
orgUsers.map(async (user) => {
if (user.status == "accepted") {
let result = await addUserToWorkspace(
user.user.email,
newWorkspaceId
);
if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY =
localStorage.getItem("PRIVATE_KEY");
/**
* When a user creates a new workspace, redirect them to the page of the new workspace.
* @param {*} workspaceName
*/
async function submitModal(workspaceName, addAllUsers) {
setLoading(true);
setTimeout(() => setLoading(false), 1500);
const workspaces = await getWorkspaces();
const currentWorkspaces = workspaces.map((workspace) => workspace.name);
if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace(
workspaceName,
localStorage.getItem("orgData.id")
);
let newWorkspaceId;
try {
newWorkspaceId = newWorkspace._id;
} catch (error) {
console.log(error);
}
if (addAllUsers) {
let orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
orgUsers.map(async (user) => {
if (user.status == "accepted") {
let result = await addUserToWorkspace(
user.user.email,
newWorkspaceId
);
if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
});
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: result.invitee.publicKey,
privateKey: PRIVATE_KEY,
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: result.invitee.publicKey,
privateKey: PRIVATE_KEY,
});
uploadKeys(
newWorkspaceId,
result.invitee._id,
ciphertext,
nonce
);
}
}
});
}
router.push("/dashboard/" + newWorkspaceId + "?Development");
setIsOpen(false);
setNewWorkspaceName("");
} else {
setError("A project with this name already exists.");
setLoading(false);
}
}
uploadKeys(newWorkspaceId, result.invitee._id, ciphertext, nonce);
}
}
});
}
router.push("/dashboard/" + newWorkspaceId + "?Development");
setIsOpen(false);
setNewWorkspaceName("");
} else {
setError("A project with this name already exists.");
setLoading(false);
}
}
function openModal() {
setIsOpen(true);
}
function openModal() {
setIsOpen(true);
}
const menuItems = [
{
href:
"/dashboard/" +
workspaceMapping[workspaceSelected] +
"?Development",
title: "Secrets",
emoji: <FontAwesomeIcon icon={faHouse} />,
},
{
href: "/users/" + workspaceMapping[workspaceSelected],
title: "Members",
emoji: <FontAwesomeIcon icon={faUser} />,
},
{
href: "/integrations/" + workspaceMapping[workspaceSelected],
title: "Integrations",
emoji: <FontAwesomeIcon icon={faLink} />,
},
{
href: "/settings/project/" + workspaceMapping[workspaceSelected],
title: "Project Settings",
emoji: <FontAwesomeIcon icon={faGear} />,
},
];
const menuItems = [
{
href:
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development",
title: "Secrets",
emoji: <FontAwesomeIcon icon={faHouse} />,
},
{
href: "/users/" + workspaceMapping[workspaceSelected],
title: "Members",
emoji: <FontAwesomeIcon icon={faUser} />,
},
{
href: "/integrations/" + workspaceMapping[workspaceSelected],
title: "Integrations",
emoji: <FontAwesomeIcon icon={faLink} />,
},
{
href: "/settings/project/" + workspaceMapping[workspaceSelected],
title: "Project Settings",
emoji: <FontAwesomeIcon icon={faGear} />,
},
];
useEffect(async () => {
// Put a user in a workspace if they're not in one yet
if (
localStorage.getItem("orgData.id") == null ||
localStorage.getItem("orgData.id") == ""
) {
const userOrgs = await getOrganizations();
localStorage.setItem("orgData.id", userOrgs[0]._id);
}
useEffect(async () => {
// Put a user in a workspace if they're not in one yet
if (
localStorage.getItem("orgData.id") == null ||
localStorage.getItem("orgData.id") == ""
) {
const userOrgs = await getOrganizations();
localStorage.setItem("orgData.id", userOrgs[0]._id);
}
let orgUserProjects = await getOrganizationUserProjects({
orgId: localStorage.getItem("orgData.id"),
});
let userWorkspaces = orgUserProjects;
if (
userWorkspaces.length == 0 &&
router.asPath != "/noprojects" &&
!router.asPath.includes("settings")
) {
router.push("/noprojects");
} else if (router.asPath != "/noprojects") {
const intendedWorkspaceId = router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0];
let orgUserProjects = await getOrganizationUserProjects({
orgId: localStorage.getItem("orgData.id"),
});
let userWorkspaces = orgUserProjects;
if (
userWorkspaces.length == 0 &&
router.asPath != "/noprojects" &&
!router.asPath.includes("settings")
) {
router.push("/noprojects");
} else if (router.asPath != "/noprojects") {
const intendedWorkspaceId = router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0];
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
if (
intendedWorkspaceId != "heroku" &&
!userWorkspaces
.map((workspace) => workspace._id)
.includes(intendedWorkspaceId)
) {
router.push(
"/dashboard/" + userWorkspaces[0]._id + "?Development"
);
} else {
setWorkspaceList(
userWorkspaces.map((workspace) => workspace.name)
);
setWorkspaceMapping(
Object.fromEntries(
userWorkspaces.map((workspace) => [
workspace.name,
workspace._id,
])
)
);
setWorkspaceSelected(
Object.fromEntries(
userWorkspaces.map((workspace) => [
workspace._id,
workspace.name,
])
)[
router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0]
]
);
}
}
}, []);
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
if (
intendedWorkspaceId != "heroku" &&
!userWorkspaces
.map((workspace) => workspace._id)
.includes(intendedWorkspaceId)
) {
router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
} else {
setWorkspaceList(userWorkspaces.map((workspace) => workspace.name));
setWorkspaceMapping(
Object.fromEntries(
userWorkspaces.map((workspace) => [workspace.name, workspace._id])
)
);
setWorkspaceSelected(
Object.fromEntries(
userWorkspaces.map((workspace) => [workspace._id, workspace.name])
)[
router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0]
]
);
}
}
}, []);
useEffect(() => {
try {
if (
workspaceMapping[workspaceSelected] &&
workspaceMapping[workspaceSelected] !==
router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0]
) {
router.push(
"/dashboard/" +
workspaceMapping[workspaceSelected] +
"?Development"
);
localStorage.setItem(
"projectData.id",
workspaceMapping[workspaceSelected]
);
}
} catch (error) {
console.log(error);
}
}, [workspaceSelected]);
useEffect(() => {
try {
if (
workspaceMapping[workspaceSelected] &&
workspaceMapping[workspaceSelected] !==
router.asPath
.split("/")
[router.asPath.split("/").length - 1].split("?")[0]
) {
router.push(
"/dashboard/" + workspaceMapping[workspaceSelected] + "?Development"
);
localStorage.setItem(
"projectData.id",
workspaceMapping[workspaceSelected]
);
}
} catch (error) {
console.log(error);
}
}, [workspaceSelected]);
return (
<>
<div className="fixed w-full hidden md:block flex flex-col h-screen">
<NavBarDashboard />
<div className="flex flex-col md:flex-row flex-1">
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
<nav>
<div className="py-6"></div>
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
PROJECT
</div>
{workspaceList.length > 0 ? (
<Listbox
selected={workspaceSelected}
onChange={setWorkspaceSelected}
data={workspaceList}
buttonAction={openModal}
text=""
workspaceMapping={workspaceMapping}
/>
) : (
<Button
text="Add Project"
onButtonPressed={openModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
)}
</div>
<ul>
{workspaceList.length > 0 &&
menuItems.map(({ href, title, emoji }) => (
<li className="mt-1.5 mx-2" key={title}>
{router.asPath.split("/")[1] ===
href.split("/")[1] &&
([
"project",
"billing",
"org",
"personal",
].includes(
router.asPath.split("/")[2]
)
? router.asPath.split(
"/"
)[2] === href.split("/")[2]
: true) ? (
<div
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
>
<div className="bg-primary w-1 rounded-xl mr-1"></div>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
) : router.asPath ==
"/noprojects" ? (
<div
className={`flex p-2 text-white text-sm rounded`}
>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
) : (
<Link href={href}>
<div
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
</Link>
)}
</li>
))}
</ul>
</nav>
</aside>
<AddWorkspaceDialog
isOpen={isOpen}
closeModal={closeModal}
submitModal={submitModal}
workspaceName={newWorkspaceName}
setWorkspaceName={setNewWorkspaceName}
error={error}
loading={loading}
/>
<main className="flex-1 bg-bunker-800">{children}</main>
</div>
</div>
<div className="block md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
<FontAwesomeIcon
icon={faMobile}
className="text-gray-300 text-7xl mb-8"
/>
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
{" "}
To use Infisical, please log in through a device with larger
dimensions.{" "}
</p>
</div>
</>
);
return (
<>
<div className="fixed w-full hidden md:block flex flex-col h-screen">
<NavBarDashboard />
<div className="flex flex-col md:flex-row flex-1">
<aside className="bg-bunker-600 border-r border-mineshaft-500 w-full md:w-60 h-screen">
<nav>
<div className="py-6"></div>
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
PROJECT
</div>
{workspaceList.length > 0 ? (
<Listbox
selected={workspaceSelected}
onChange={setWorkspaceSelected}
data={workspaceList}
buttonAction={openModal}
text=""
workspaceMapping={workspaceMapping}
/>
) : (
<Button
text="Add Project"
onButtonPressed={openModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
)}
</div>
<ul>
{workspaceList.length > 0 &&
menuItems.map(({ href, title, emoji }) => (
<li className="mt-1.5 mx-2" key={title}>
{router.asPath.split("/")[1] === href.split("/")[1] &&
(["project", "billing", "org", "personal"].includes(
router.asPath.split("/")[2]
)
? router.asPath.split("/")[2] === href.split("/")[2]
: true) ? (
<div
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
>
<div className="bg-primary w-1 rounded-xl mr-1"></div>
<p className="ml-2 mr-4">{emoji}</p>
{title}
</div>
) : router.asPath == "/noprojects" ? (
<div className={`flex p-2 text-white text-sm rounded`}>
<p className="ml-2 mr-4">{emoji}</p>
{title}
</div>
) : (
<Link href={href}>
<div
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
>
<p className="ml-2 mr-4">{emoji}</p>
{title}
</div>
</Link>
)}
</li>
))}
</ul>
</nav>
</aside>
<AddWorkspaceDialog
isOpen={isOpen}
closeModal={closeModal}
submitModal={submitModal}
workspaceName={newWorkspaceName}
setWorkspaceName={setNewWorkspaceName}
error={error}
loading={loading}
/>
<main className="flex-1 bg-bunker-800">{children}</main>
</div>
</div>
<div className="block md:hidden bg-bunker-800 w-screen h-screen flex flex-col justify-center items-center">
<FontAwesomeIcon
icon={faMobile}
className="text-gray-300 text-7xl mb-8"
/>
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
{" "}
To use Infisical, please log in through a device with larger
dimensions.{" "}
</p>
</div>
</>
);
}

@ -3,49 +3,49 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function BottonRightPopup({
buttonText,
buttonLink,
titleText,
emoji,
textLine1,
textLine2,
setCheckDocsPopUpVisible,
buttonText,
buttonLink,
titleText,
emoji,
textLine1,
textLine2,
setCheckDocsPopUpVisible,
}) {
return (
<div
class="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-xl 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>{titleText}</div>
<div class="ml-2.5">{emoji}</div>
</div>
<button
className="mt-1"
onClick={() => setCheckDocsPopUpVisible(false)}
>
<FontAwesomeIcon
icon={faXmark}
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
/>
</button>
</div>
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
{textLine1}
</div>
<div class="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
class="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"
>
{buttonText}
</a>
</div>
</div>
);
return (
<div
class="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-xl 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>{titleText}</div>
<div class="ml-2.5">{emoji}</div>
</div>
<button
className="mt-1"
onClick={() => setCheckDocsPopUpVisible(false)}
>
<FontAwesomeIcon
icon={faXmark}
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
/>
</button>
</div>
<div class="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">
{textLine1}
</div>
<div class="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
class="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"
>
{buttonText}
</a>
</div>
</div>
);
}

@ -1,28 +1,28 @@
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>
)}
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>
</>
);
<label className="ml-2 text-gray-500 text-sm">
Add all members of my organization to this project.
</label>
</div>
</>
);
};

@ -8,11 +8,11 @@ import Button from "../buttons/Button";
const roles = ["admin", "user"];
const reverseEnvMapping = {
"dev": "Development",
"staging": "Staging",
"prod": "Production",
"test": "Testing"
}
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing",
};
/**
* This is the component that we utilize for the user table - in future, can reuse it for some other purposes too.
@ -20,63 +20,66 @@ const reverseEnvMapping = {
* @param {*} props
* @returns
*/
const ServiceTokenTable = ({
data,
workspaceName
}) => {
const router = useRouter();
const ServiceTokenTable = ({ data, workspaceName }) => {
const router = useRouter();
return (
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
<table className="w-full my-1">
<thead className="text-bunker-300">
<tr>
<th className="text-left pl-6 pt-2.5 pb-2">Token name</th>
<th className="text-left pl-6 pt-2.5 pb-2">Project</th>
<th className="text-left pl-6 pt-2.5 pb-2">Environment</th>
<th className="text-left pl-6 pt-2.5 pb-2">Valid until</th>
<th></th>
</tr>
</thead>
<tbody>
{data?.length > 0
? data
.map((row, index) => {
return (
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-800/5 duration-100">
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.name}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{workspaceName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{reverseEnvMapping[row.environment]}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{new Date(row.expiresAt).toUTCString()}
</td>
<td className="py-2 border-mineshaft-700 border-t">
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
<Button
onButtonPressed={() => {}}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
</td>
</tr>
);
})
: <tr>
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">No service tokens yet</td>
</tr>}
</tbody>
</table>
</div>
);
return (
<div className="table-container w-full bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
<div className="absolute rounded-t-md w-full h-12 bg-white/5"></div>
<table className="w-full my-1">
<thead className="text-bunker-300">
<tr>
<th className="text-left pl-6 pt-2.5 pb-2">Token name</th>
<th className="text-left pl-6 pt-2.5 pb-2">Project</th>
<th className="text-left pl-6 pt-2.5 pb-2">Environment</th>
<th className="text-left pl-6 pt-2.5 pb-2">Valid until</th>
<th></th>
</tr>
</thead>
<tbody>
{data?.length > 0 ? (
data.map((row, index) => {
return (
<tr
key={guidGenerator()}
className="bg-bunker-800 hover:bg-bunker-800/5 duration-100"
>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.name}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{workspaceName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{reverseEnvMapping[row.environment]}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{new Date(row.expiresAt).toUTCString()}
</td>
<td className="py-2 border-mineshaft-700 border-t">
<div className="opacity-50 hover:opacity-100 duration-200 flex items-center">
<Button
onButtonPressed={() => {}}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
</td>
</tr>
);
})
) : (
<tr>
<td colSpan="4" className="text-center pt-7 pb-4 text-bunker-400">
No service tokens yet
</td>
</tr>
)}
</tbody>
</table>
</div>
);
};
export default ServiceTokenTable;

@ -13,8 +13,8 @@ import Button from "../buttons/Button";
import Listbox from "../Listbox";
const {
decryptAssymmetric,
encryptAssymmetric,
decryptAssymmetric,
encryptAssymmetric,
} = require("../../utilities/cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
@ -28,245 +28,214 @@ const roles = ["admin", "user"];
* @returns
*/
const UserTable = ({
userData,
changeData,
myUser,
filter,
resendInvite,
isOrg,
onClick,
deleteUser,
setUserIdToBeDeleted,
userData,
changeData,
myUser,
filter,
resendInvite,
isOrg,
onClick,
deleteUser,
setUserIdToBeDeleted,
}) => {
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const router = useRouter();
const [myRole, setMyRole] = useState("member");
// Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
const handleDelete = (membershipId, index, e) => {
// setUserIdToBeDeleted(userId);
// onClick();
if (isOrg) {
deleteUserFromOrganization(membershipId);
} else {
deleteUserFromWorkspace(membershipId);
}
changeData(userData.filter((v, i) => i !== index));
setRoleSelected([
...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length),
]);
};
// Update the rold of a certain user
const handleRoleUpdate = (index, e) => {
changeUserRoleInWorkspace(userData[index].membershipId, e);
changeData([
...userData.slice(0, index),
...[
{
key: userData[index].key,
firstName: userData[index].firstName,
lastName: userData[index].lastName,
email: userData[index].email,
role: e,
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
},
],
...userData.slice(index + 1, userData?.length),
]);
};
useEffect(() => {
setMyRole(userData.filter((user) => user.email == myUser)[0]?.role);
}, [userData, myUser]);
const grantAccess = async (id, publicKey) => {
let result = await getLatestFileKey(router.query.id);
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: publicKey,
privateKey: PRIVATE_KEY,
});
uploadKeys(router.query.id, id, ciphertext, nonce);
router.reload();
};
const deleteMembershipAndResendInvite = (email, membershipId) => {
// deleteUserFromWorkspace(membershipId);
resendInvite(email);
};
return (
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
<div className="absolute rounded-t-md w-full h-14 bg-white/5"></div>
<table className="w-full my-1">
<thead className="text-gray-400">
<tr>
<th className="text-left pl-6 py-3.5">First Name</th>
<th className="text-left pl-6 py-3.5">Last Name</th>
<th className="text-left pl-6 py-3.5">Email</th>
<th></th>
</tr>
</thead>
<tbody>
{userData?.filter(
(user) =>
user.firstName?.toLowerCase().includes(filter) ||
user.lastName?.toLowerCase().includes(filter) ||
user.email?.toLowerCase().includes(filter)
).length > 0 &&
userData
?.filter(
(user) =>
user.firstName
?.toLowerCase()
.includes(filter) ||
user.lastName
?.toLowerCase()
.includes(filter) ||
user.email?.toLowerCase().includes(filter)
)
.map((row, index) => {
return (
<tr
key={guidGenerator()}
className="bg-bunker-800 hover:bg-bunker-800/5"
>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.lastName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.email}
</td>
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
<div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center">
{row.status == "granted" &&
((myRole == "admin" &&
row.role != "owner") ||
myRole == "owner") &&
myUser !== row.email ? (
<Listbox
selected={row.role}
onChange={(e) =>
handleRoleUpdate(
index,
e
)
}
data={
myRole == "owner"
? [
"owner",
"admin",
"member",
]
: [
"admin",
"member",
]
}
text="Role: "
membershipId={
row.membershipId
}
/>
) : (
row.status != "invited" &&
row.status !=
"verified" && (
<Listbox
selected={row.role}
text="Role: "
membershipId={
row.membershipId
}
/>
)
)}
{(row.status == "invited" ||
row.status ==
"verified") && (
<div className="w-full pl-12">
<Button
onButtonPressed={() =>
deleteMembershipAndResendInvite(
row.email,
row.membershipId
)
}
color="mineshaft"
text="Resend Invite"
size="md"
/>
</div>
)}
{row.status == "completed" &&
myUser !== row.email && (
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
<Button
onButtonPressed={() =>
grantAccess(
row.userId,
row.publicKey
)
}
color="mineshaft"
text="Grant Access"
size="md"
/>
</div>
)}
</div>
{myUser !== row.email &&
// row.role != "admin" &&
myRole != "member" ? (
<div className="opacity-50 hover:opacity-100 flex items-center">
<Button
onButtonPressed={(e) =>
handleDelete(
row.membershipId,
index,
e
)
}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
) : (
<div className="w-9 h-9"></div>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const router = useRouter();
const [myRole, setMyRole] = useState("member");
// Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
const handleDelete = (membershipId, index, e) => {
// setUserIdToBeDeleted(userId);
// onClick();
if (isOrg) {
deleteUserFromOrganization(membershipId);
} else {
deleteUserFromWorkspace(membershipId);
}
changeData(userData.filter((v, i) => i !== index));
setRoleSelected([
...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length),
]);
};
// Update the rold of a certain user
const handleRoleUpdate = (index, e) => {
changeUserRoleInWorkspace(userData[index].membershipId, e);
changeData([
...userData.slice(0, index),
...[
{
key: userData[index].key,
firstName: userData[index].firstName,
lastName: userData[index].lastName,
email: userData[index].email,
role: e,
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
},
],
...userData.slice(index + 1, userData?.length),
]);
};
useEffect(() => {
setMyRole(userData.filter((user) => user.email == myUser)[0]?.role);
}, [userData, myUser]);
const grantAccess = async (id, publicKey) => {
let result = await getLatestFileKey(router.query.id);
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: publicKey,
privateKey: PRIVATE_KEY,
});
uploadKeys(router.query.id, id, ciphertext, nonce);
router.reload();
};
const deleteMembershipAndResendInvite = (email, membershipId) => {
// deleteUserFromWorkspace(membershipId);
resendInvite(email);
};
return (
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
<div className="absolute rounded-t-md w-full h-14 bg-white/5"></div>
<table className="w-full my-1">
<thead className="text-gray-400">
<tr>
<th className="text-left pl-6 py-3.5">First Name</th>
<th className="text-left pl-6 py-3.5">Last Name</th>
<th className="text-left pl-6 py-3.5">Email</th>
<th></th>
</tr>
</thead>
<tbody>
{userData?.filter(
(user) =>
user.firstName?.toLowerCase().includes(filter) ||
user.lastName?.toLowerCase().includes(filter) ||
user.email?.toLowerCase().includes(filter)
).length > 0 &&
userData
?.filter(
(user) =>
user.firstName?.toLowerCase().includes(filter) ||
user.lastName?.toLowerCase().includes(filter) ||
user.email?.toLowerCase().includes(filter)
)
.map((row, index) => {
return (
<tr
key={guidGenerator()}
className="bg-bunker-800 hover:bg-bunker-800/5"
>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.lastName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.email}
</td>
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
<div className="flex justify-end mr-6 w-3/4 mx-2 w-full h-full flex flex-row items-center">
{row.status == "granted" &&
((myRole == "admin" && row.role != "owner") ||
myRole == "owner") &&
myUser !== row.email ? (
<Listbox
selected={row.role}
onChange={(e) => handleRoleUpdate(index, e)}
data={
myRole == "owner"
? ["owner", "admin", "member"]
: ["admin", "member"]
}
text="Role: "
membershipId={row.membershipId}
/>
) : (
row.status != "invited" &&
row.status != "verified" && (
<Listbox
selected={row.role}
text="Role: "
membershipId={row.membershipId}
/>
)
)}
{(row.status == "invited" ||
row.status == "verified") && (
<div className="w-full pl-12">
<Button
onButtonPressed={() =>
deleteMembershipAndResendInvite(
row.email,
row.membershipId
)
}
color="mineshaft"
text="Resend Invite"
size="md"
/>
</div>
)}
{row.status == "completed" && myUser !== row.email && (
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
<Button
onButtonPressed={() =>
grantAccess(row.userId, row.publicKey)
}
color="mineshaft"
text="Grant Access"
size="md"
/>
</div>
)}
</div>
{myUser !== row.email &&
// row.role != "admin" &&
myRole != "member" ? (
<div className="opacity-50 hover:opacity-100 flex items-center">
<Button
onButtonPressed={(e) =>
handleDelete(row.membershipId, index, e)
}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
) : (
<div className="w-9 h-9"></div>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
};
export default UserTable;

@ -3,91 +3,82 @@ import React from "react";
import StripeRedirect from "~/pages/api/organization/StripeRedirect";
export default function Plan({ plan }) {
return (
<div
className={`relative flex flex-col justify-between border border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
(plan.name != "Starter") & (plan.current == true)
? "border-primary"
: "border-chicago-700"
}
return (
<div
className={`relative flex flex-col justify-between border border-2 min-w-fit w-96 rounded-lg h-68 mr-4 bg-mineshaft-800 ${
(plan.name != "Starter") & (plan.current == true)
? "border-primary"
: "border-chicago-700"
}
`}
>
<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">
{plan.price}
</p>
<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">
{plan.text}
</p>
<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">
{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">
{plan.buttonTextMain}
</div>
</a>
) : (
<div
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${
plan.buttonTextMain == "Downgrade"
? "hover:bg-red hover:text-white hover:border-red"
: "hover:bg-primary hover:text-black hover:border-primary"
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
>
<button
onClick={() =>
StripeRedirect({
orgId: localStorage.getItem(
"orgData.id"
),
})
}
>
{plan.buttonTextMain}
</button>
</div>
)}
<a href="/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">
{plan.buttonTextSecondary}
</div>
</a>
</>
) : (
<div
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
(plan.name != "Starter") & (plan.current == true)
? "bg-primary"
: "bg-chicago-700"
}`}
>
<p className="text-xs text-black font-semibold">
CURRENT PLAN
</p>
</div>
)}
</div>
</div>
);
>
<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">
{plan.price}
</p>
<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">
{plan.text}
</p>
<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">
{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">
{plan.buttonTextMain}
</div>
</a>
) : (
<div
className={`relative z-10 mx-5 mt-3 mb-4 py-2 px-4 border border-1 border-gray-600 text-gray-400 font-semibold ${
plan.buttonTextMain == "Downgrade"
? "hover:bg-red hover:text-white hover:border-red"
: "hover:bg-primary hover:text-black hover:border-primary"
} bg-bunker duration-200 cursor-pointer rounded-md flex w-max`}
>
<button
onClick={() =>
StripeRedirect({
orgId: localStorage.getItem("orgData.id"),
})
}
>
{plan.buttonTextMain}
</button>
</div>
)}
<a href="/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">
{plan.buttonTextSecondary}
</div>
</a>
</>
) : (
<div
className={`h-8 w-full rounded-b-md flex justify-center items-center z-10 ${
(plan.name != "Starter") & (plan.current == true)
? "bg-primary"
: "bg-chicago-700"
}`}
>
<p className="text-xs text-black font-semibold">CURRENT PLAN</p>
</div>
)}
</div>
</div>
);
}

@ -10,17 +10,17 @@ import guidGenerator from "../utilities/randomId";
* @returns
*/
const findReferences = (text) => {
var splitText = text.split("${");
let textArray = [splitText[0]];
for (var i = 1; i < splitText.length; i++) {
let insideBrackets = "${" + splitText[i].split("}")[0];
if (splitText[i].includes("}")) {
insideBrackets += "}";
}
textArray.push(insideBrackets);
textArray.push(splitText[i].split("}")[1]);
}
return textArray;
var splitText = text.split("${");
let textArray = [splitText[0]];
for (var i = 1; i < splitText.length; i++) {
let insideBrackets = "${" + splitText[i].split("}")[0];
if (splitText[i].includes("}")) {
insideBrackets += "}";
}
textArray.push(insideBrackets);
textArray.push(splitText[i].split("}")[1]);
}
return textArray;
};
/**
@ -34,110 +34,98 @@ const findReferences = (text) => {
* @returns
*/
const DashboardInputField = ({
index,
onChangeHandler,
type,
value,
blurred,
index,
onChangeHandler,
type,
value,
blurred,
}) => {
if (type === "varName") {
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
onChange={(e) => onChangeHandler(e.target.value, index)}
type={type}
value={value}
className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200"
spellCheck="false"
/>
</div>
</div>
);
} else if (type === "value") {
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
onChange={(e) => onChangeHandler(e.target.value, index)}
type={type}
value={value}
className={`${
blurred
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
: ""
} asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`}
spellCheck="false"
/>
<div
className={`${
blurred
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
: ""
} flex flex-row font-mono absolute z-0 ph-no-capture bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-100`}
>
{findReferences(value).map((texts, id) => {
if (id % 2 == 0 || texts.length <= 2) {
return (
<span className="ph-no-capture" key={id}>
{texts}
</span>
);
}
return (
<span
className="ph-no-capture text-yellow"
key={id}
>
{texts.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{texts.slice(2, texts.length - 1)}
</span>
{texts.slice(
texts.length - 1,
texts.length
) == "}" ? (
<span className="ph-no-capture text-yellow">
{texts.slice(
texts.length - 1,
texts.length
)}{" "}
</span>
) : (
<span className="ph-no-capture text-yellow-400">
{texts.slice(
texts.length - 1,
texts.length
)}{" "}
</span>
)}
</span>
);
})}
</div>
{blurred && (
<div className="z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-9 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
<p className="ml-2"></p>
{value
.split("")
.slice(0, 42)
.map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"
icon={faCircle}
/>
))}
</div>
)}
</div>
</div>
);
}
if (type === "varName") {
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
onChange={(e) => onChangeHandler(e.target.value, index)}
type={type}
value={value}
className="asolute z-10 peer font-mono ph-no-capture bg-bunker-800 rounded-md caret-white text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200"
spellCheck="false"
/>
</div>
</div>
);
} else if (type === "value") {
return (
<div className="flex-col w-full">
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border border-mineshaft-500 rounded-md`}
>
<input
onChange={(e) => onChangeHandler(e.target.value, index)}
type={type}
value={value}
className={`${
blurred
? "text-transparent group-hover:text-transparent focus:text-transparent active:text-transparent"
: ""
} asolute z-10 peer font-mono ph-no-capture bg-transparent rounded-md caret-white text-transparent text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-200`}
spellCheck="false"
/>
<div
className={`${
blurred
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-400 peer-active:text-gray-400"
: ""
} flex flex-row font-mono absolute z-0 ph-no-capture bg-bunker-800 h-9 rounded-md text-gray-400 text-md px-2 py-1.5 w-full min-w-16 outline-none focus:ring-4 focus:ring-primary/50 duration-100`}
>
{findReferences(value).map((texts, id) => {
if (id % 2 == 0 || texts.length <= 2) {
return (
<span className="ph-no-capture" key={id}>
{texts}
</span>
);
}
return (
<span className="ph-no-capture text-yellow" key={id}>
{texts.slice(0, 2)}
<span className="ph-no-capture text-yellow-200/80">
{texts.slice(2, texts.length - 1)}
</span>
{texts.slice(texts.length - 1, texts.length) == "}" ? (
<span className="ph-no-capture text-yellow">
{texts.slice(texts.length - 1, texts.length)}{" "}
</span>
) : (
<span className="ph-no-capture text-yellow-400">
{texts.slice(texts.length - 1, texts.length)}{" "}
</span>
)}
</span>
);
})}
</div>
{blurred && (
<div className="z-20 peer pr-2 bg-bunker-800 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-9 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
<p className="ml-2"></p>
{value
.split("")
.slice(0, 42)
.map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"
icon={faCircle}
/>
))}
</div>
)}
</div>
</div>
);
}
};
export default React.memo(DashboardInputField);

@ -9,178 +9,177 @@ import parse from "../utilities/file";
import guidGenerator from "../utilities/randomId";
const DropZone = ({
setData,
setErrorDragAndDrop,
createNewFile,
errorDragAndDrop,
addPresetRow,
setButtonReady,
keysExist,
numCurrentRows
setData,
setErrorDragAndDrop,
createNewFile,
errorDragAndDrop,
addPresetRow,
setButtonReady,
keysExist,
numCurrentRows,
}) => {
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
// set dropEffect to copy i.e copy of the source item
e.dataTransfer.dropEffect = "copy";
};
// set dropEffect to copy i.e copy of the source item
e.dataTransfer.dropEffect = "copy";
};
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(false);
// This function function immediately parses the file after it is dropped
const handleDrop = async (e) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = "copy";
// This function function immediately parses the file after it is dropped
const handleDrop = async (e) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = "copy";
var file = e.dataTransfer.files[0],
reader = new FileReader();
reader.onload = function (event) {
const keyPairs = parse(event.target.result)
const newData = Object.keys(keyPairs)
.map((key, index) => [
guidGenerator(),
numCurrentRows + index,
key,
keyPairs[key],
"shared",
]);
setData(newData);
setButtonReady(true);
};
var file = e.dataTransfer.files[0],
reader = new FileReader();
reader.onload = function (event) {
const keyPairs = parse(event.target.result);
const newData = Object.keys(keyPairs).map((key, index) => [
guidGenerator(),
numCurrentRows + index,
key,
keyPairs[key],
"shared",
]);
setData(newData);
setButtonReady(true);
};
// If something is wrong show an error
try {
reader.readAsText(file);
setLoading(false);
} catch (error) {
setErrorDragAndDrop(true);
setLoading(false);
}
};
// If something is wrong show an error
try {
reader.readAsText(file);
setLoading(false);
} catch (error) {
setErrorDragAndDrop(true);
setLoading(false);
}
};
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
const handleFileSelect = (e) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
var file = e.target.files[0],
reader = new FileReader();
reader.onload = function (event) {
const newData = event.target.result
.split("\n")
.map((line, index) => [
guidGenerator(),
numCurrentRows + index,
line.split("=")[0],
line.split("=").slice(1, line.split("=").length).join("="),
"shared",
]);
setData(newData);
setButtonReady(true);
};
reader.readAsText(file);
};
// This function is used when the user manually selects a file from the in-browser dircetory (not drag and drop)
const handleFileSelect = (e) => {
setLoading(true);
setTimeout(() => setLoading(false), 5000);
var file = e.target.files[0],
reader = new FileReader();
reader.onload = function (event) {
const newData = event.target.result
.split("\n")
.map((line, index) => [
guidGenerator(),
numCurrentRows + index,
line.split("=")[0],
line.split("=").slice(1, line.split("=").length).join("="),
"shared",
]);
setData(newData);
setButtonReady(true);
};
reader.readAsText(file);
};
return loading ? (
<div className="flex items-center justify-center pt-16 mb-16">
<Image
src="/images/loading/loading.gif"
height={70}
width={120}
alt="google logo"
></Image>
</div>
) : keysExist ? (
<div
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
onDragEnter={(e) => handleDragEnter(e)}
onDragOver={(e) => handleDragOver(e)}
onDragLeave={(e) => handleDragLeave(e)}
onDrop={(e) => handleDrop(e)}
>
<input
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
onChange={(e) => handleFileSelect(e)}
/>
{errorDragAndDrop ? (
<div className="my-3 max-w-xl opacity-80"></div>
) : (
<div className=""></div>
)}
<div className="flex flex-row">
<FontAwesomeIcon
icon={faUpload}
className="text-gray-300 text-3xl mr-6"
/>
<p className="text-gray-300 mt-1">
Drag and drop your .env file here to add more keys.
</p>
</div>
{errorDragAndDrop ? (
<div className="mt-8 max-w-xl opacity-80">
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
</div>
) : (
<></>
)}
</div>
) : (
<div
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
onDragEnter={(e) => handleDragEnter(e)}
onDragOver={(e) => handleDragOver(e)}
onDragLeave={(e) => handleDragLeave(e)}
onDrop={(e) => handleDrop(e)}
>
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
<p className="">Drag and drop your .env file here.</p>
<input
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
onChange={(e) => handleFileSelect(e)}
/>
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
<div className="border-t border-gray-700 w-1/5"></div>
<p className="text-gray-400 text-xs mx-4">OR</p>
<div className="border-t border-gray-700 w-1/5"></div>
</div>
<div className="z-10 mb-6">
<Button
color="mineshaft"
text="Create a new .env file"
onButtonPressed={createNewFile}
size="md"
/>
</div>
{errorDragAndDrop ? (
<div className="opacity-80">
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
</div>
) : (
<div className="py-3">
{/* <p className="text-xs text-gray-500"> If you are expecting to see a file here, contact your administrator for permission. </p> */}
</div>
)}
</div>
);
return loading ? (
<div className="flex items-center justify-center pt-16 mb-16">
<Image
src="/images/loading/loading.gif"
height={70}
width={120}
alt="google logo"
></Image>
</div>
) : keysExist ? (
<div
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
onDragEnter={(e) => handleDragEnter(e)}
onDragOver={(e) => handleDragOver(e)}
onDragLeave={(e) => handleDragLeave(e)}
onDrop={(e) => handleDrop(e)}
>
<input
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
onChange={(e) => handleFileSelect(e)}
/>
{errorDragAndDrop ? (
<div className="my-3 max-w-xl opacity-80"></div>
) : (
<div className=""></div>
)}
<div className="flex flex-row">
<FontAwesomeIcon
icon={faUpload}
className="text-gray-300 text-3xl mr-6"
/>
<p className="text-gray-300 mt-1">
Drag and drop your .env file here to add more keys.
</p>
</div>
{errorDragAndDrop ? (
<div className="mt-8 max-w-xl opacity-80">
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
</div>
) : (
<></>
)}
</div>
) : (
<div
className="opacity-80 hover:opacity-100 duration-200 relative bg-bunker outline max-w-2xl w-full outline-dashed outline-gray-700 rounded-md outline-2 flex flex-col items-center justify-center pt-16 mb-16 px-4"
onDragEnter={(e) => handleDragEnter(e)}
onDragOver={(e) => handleDragOver(e)}
onDragLeave={(e) => handleDragLeave(e)}
onDrop={(e) => handleDrop(e)}
>
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
<p className="">Drag and drop your .env file here.</p>
<input
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
onChange={(e) => handleFileSelect(e)}
/>
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
<div className="border-t border-gray-700 w-1/5"></div>
<p className="text-gray-400 text-xs mx-4">OR</p>
<div className="border-t border-gray-700 w-1/5"></div>
</div>
<div className="z-10 mb-6">
<Button
color="mineshaft"
text="Create a new .env file"
onButtonPressed={createNewFile}
size="md"
/>
</div>
{errorDragAndDrop ? (
<div className="opacity-80">
<Error text="Something went wrong! Make sure you drag the file directly from the folder in which it is located (e.g., not VS code). Tip: click 'Reveal in Finder/Explorer'" />
</div>
) : (
<div className="py-3">
{/* <p className="text-xs text-gray-500"> If you are expecting to see a file here, contact your administrator for permission. </p> */}
</div>
)}
</div>
);
};
export default DropZone;

@ -1,18 +1,18 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react/jsx-key */
import React, { Fragment, useEffect,useState } from "react";
import React, { Fragment, useEffect, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { faGithub,faSlack } from "@fortawesome/free-brands-svg-icons";
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
import {
faAngleDown,
faBook,
faCoins,
faEnvelope,
faGear,
faPlus,
faRightFromBracket,
faAngleDown,
faBook,
faCoins,
faEnvelope,
faGear,
faPlus,
faRightFromBracket,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Menu, Transition } from "@headlessui/react";
@ -25,301 +25,269 @@ import getUser from "~/pages/api/user/getUser";
import guidGenerator from "../utilities/randomId";
const supportOptions = [
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
"Join Slack Forum",
"https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g",
],
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
"Read Docs",
"https://infisical.com/docs/getting-started/introduction",
],
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
"Open a GitHub Issue",
"https://github.com/Infisical/infisical-cli/issues",
],
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
"Send us an Email",
"mailto:support@infisical.com",
],
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
"Join Slack Forum",
"https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g",
],
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
"Read Docs",
"https://infisical.com/docs/getting-started/introduction",
],
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
"Open a GitHub Issue",
"https://github.com/Infisical/infisical-cli/issues",
],
[
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
"Send us an Email",
"mailto:support@infisical.com",
],
];
export default function Navbar({ onButtonPressed }) {
const router = useRouter();
const [user, setUser] = useState({});
const [orgs, setOrgs] = useState([]);
const [currentOrg, setCurrentOrg] = useState([]);
const router = useRouter();
const [user, setUser] = useState({});
const [orgs, setOrgs] = useState([]);
const [currentOrg, setCurrentOrg] = useState([]);
useEffect(async () => {
const userData = await getUser();
setUser(userData);
const orgsData = await getOrganizations();
setOrgs(orgsData);
const currentOrg = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
setCurrentOrg(currentOrg);
}, []);
useEffect(async () => {
const userData = await getUser();
setUser(userData);
const orgsData = await getOrganizations();
setOrgs(orgsData);
const currentOrg = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
setCurrentOrg(currentOrg);
}, []);
const closeApp = async () => {
console.log("Logging out...");
await logout();
router.push("/");
};
const closeApp = async () => {
console.log("Logging out...");
await logout();
router.push("/");
};
return (
<div className="absolute flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-50">
<div className="m-auto flex justify-start items-center mx-4">
<div className="flex flex-row items-center">
<div className="flex justify-center py-4">
<Image
src="/images/logotransparent.png"
height={23}
width={57}
alt="logo"
/>
</div>
<a className="text-2xl text-white font-semibold mx-2">
Infisical
</a>
</div>
</div>
<div className="relative flex justify-start items-center mx-2 z-40">
<Menu as="div" className="relative inline-block text-left">
<div className="mr-4">
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
<FontAwesomeIcon
className="text-xl"
icon={faCircleQuestion}
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
{supportOptions.map((option) => (
// eslint-disable-next-line react/jsx-no-target-blank
<a
key={guidGenerator()}
target="_blank"
rel="noopener"
href={option[2]}
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
>
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
{option[0]}
<div className="text-sm">
{option[1]}
</div>
</div>
</a>
))}
</Menu.Items>
</Transition>
</Menu>
<Menu as="div" className="relative inline-block text-left mr-4">
<div>
<Menu.Button className="inline-flex w-full justify-center rounded-md pr-2 pl-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
{user?.firstName} {user?.lastName}
<FontAwesomeIcon
icon={faAngleDown}
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
<div className="px-1 py-1 ">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
SIGNED IN AS
</div>
<div
onClick={() =>
router.push(
"/settings/personal/" +
router.query.id
)
}
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-8 w-9 rounded-full flex items-center justify-center text-gray-300">
{user?.firstName?.charAt(0)}
</div>
<div className="flex items-center justify-between w-full">
<div>
<p className="text-gray-300 px-2 pt-1 text-sm">
{" "}
{user?.firstName}{" "}
{user?.lastName}
</p>
<p className="text-gray-400 px-2 pb-1 text-xs">
{" "}
{user?.email}
</p>
</div>
<FontAwesomeIcon
icon={faGear}
className="text-lg text-gray-400 p-2 mr-1 rounded-md cursor-pointer hover:bg-white/10"
/>
</div>
</div>
</div>
<div className="px-2 pt-2">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
CURRENT ORGANIZATION
</div>
<div
onClick={() =>
router.push(
"/settings/org/" + router.query.id
)
}
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
{currentOrg?.name?.charAt(0)}
</div>
<div className="flex items-center justify-between w-full">
<p className="text-gray-300 px-2 text-sm">
{currentOrg?.name}
</p>
<FontAwesomeIcon
icon={faGear}
className="text-lg text-gray-400 p-2 rounded-md cursor-pointer hover:bg-white/10"
/>
</div>
</div>
<button
// onClick={buttonAction}
className="cursor-pointer w-full"
>
<div
onClick={() =>
router.push(
"/settings/billing/" +
router.query.id
)
}
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
>
<FontAwesomeIcon
className="text-lg pl-1.5 pr-3"
icon={faCoins}
/>
<div className="text-sm">
Usage & Billing
</div>
</div>
</button>
<button
// onClick={buttonAction}
className="cursor-pointer w-full mb-2"
>
<div
onClick={() =>
router.push(
"/settings/org/" +
router.query.id +
"?invite"
)
}
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
>
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
<FontAwesomeIcon
icon={faPlus}
className="ml-1"
/>
</span>
<div className="text-sm ml-1">
Invite Members
</div>
</div>
</button>
</div>
{orgs?.length > 1 && (
<div className="px-1 pt-1">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
OTHER ORGANIZATIONS
</div>
<div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs
.filter(
(org) =>
org._id !=
localStorage.getItem(
"orgData.id"
)
)
.map((org) => (
<div
key={guidGenerator()}
onClick={() => {
localStorage.setItem(
"orgData.id",
org._id
);
router.reload();
}}
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
{org.name.charAt(0)}
</div>
<div className="flex items-center justify-between w-full">
<p className="text-gray-300 px-2 text-sm">
{org.name}
</p>
</div>
</div>
))}
</div>
</div>
)}
<div className="px-1 py-1">
<Menu.Item>
{({ active }) => (
<button
onClick={closeApp}
className={`${
active
? "bg-red font-semibold text-white"
: "text-gray-400"
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
>
<div className="relative flex justify-start items-center cursor-pointer select-none">
<FontAwesomeIcon
className="text-lg ml-1.5 mr-3"
icon={faRightFromBracket}
/>
Log Out
</div>
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
);
return (
<div className="absolute flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-50">
<div className="m-auto flex justify-start items-center mx-4">
<div className="flex flex-row items-center">
<div className="flex justify-center py-4">
<Image
src="/images/logotransparent.png"
height={23}
width={57}
alt="logo"
/>
</div>
<a className="text-2xl text-white font-semibold mx-2">Infisical</a>
</div>
</div>
<div className="relative flex justify-start items-center mx-2 z-40">
<Menu as="div" className="relative inline-block text-left">
<div className="mr-4">
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
<FontAwesomeIcon className="text-xl" icon={faCircleQuestion} />
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
{supportOptions.map((option) => (
// eslint-disable-next-line react/jsx-no-target-blank
<a
key={guidGenerator()}
target="_blank"
rel="noopener"
href={option[2]}
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
>
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
{option[0]}
<div className="text-sm">{option[1]}</div>
</div>
</a>
))}
</Menu.Items>
</Transition>
</Menu>
<Menu as="div" className="relative inline-block text-left mr-4">
<div>
<Menu.Button className="inline-flex w-full justify-center rounded-md pr-2 pl-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
{user?.firstName} {user?.lastName}
<FontAwesomeIcon
icon={faAngleDown}
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
<div className="px-1 py-1 ">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
SIGNED IN AS
</div>
<div
onClick={() =>
router.push("/settings/personal/" + router.query.id)
}
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-8 w-9 rounded-full flex items-center justify-center text-gray-300">
{user?.firstName?.charAt(0)}
</div>
<div className="flex items-center justify-between w-full">
<div>
<p className="text-gray-300 px-2 pt-1 text-sm">
{" "}
{user?.firstName} {user?.lastName}
</p>
<p className="text-gray-400 px-2 pb-1 text-xs">
{" "}
{user?.email}
</p>
</div>
<FontAwesomeIcon
icon={faGear}
className="text-lg text-gray-400 p-2 mr-1 rounded-md cursor-pointer hover:bg-white/10"
/>
</div>
</div>
</div>
<div className="px-2 pt-2">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
CURRENT ORGANIZATION
</div>
<div
onClick={() =>
router.push("/settings/org/" + router.query.id)
}
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
{currentOrg?.name?.charAt(0)}
</div>
<div className="flex items-center justify-between w-full">
<p className="text-gray-300 px-2 text-sm">
{currentOrg?.name}
</p>
<FontAwesomeIcon
icon={faGear}
className="text-lg text-gray-400 p-2 rounded-md cursor-pointer hover:bg-white/10"
/>
</div>
</div>
<button
// onClick={buttonAction}
className="cursor-pointer w-full"
>
<div
onClick={() =>
router.push("/settings/billing/" + router.query.id)
}
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
>
<FontAwesomeIcon
className="text-lg pl-1.5 pr-3"
icon={faCoins}
/>
<div className="text-sm">Usage & Billing</div>
</div>
</button>
<button
// onClick={buttonAction}
className="cursor-pointer w-full mb-2"
>
<div
onClick={() =>
router.push(
"/settings/org/" + router.query.id + "?invite"
)
}
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
>
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
<FontAwesomeIcon icon={faPlus} className="ml-1" />
</span>
<div className="text-sm ml-1">Invite Members</div>
</div>
</button>
</div>
{orgs?.length > 1 && (
<div className="px-1 pt-1">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
OTHER ORGANIZATIONS
</div>
<div className="flex flex-col items-start px-1 mt-3 mb-2">
{orgs
.filter(
(org) => org._id != localStorage.getItem("orgData.id")
)
.map((org) => (
<div
key={guidGenerator()}
onClick={() => {
localStorage.setItem("orgData.id", org._id);
router.reload();
}}
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
{org.name.charAt(0)}
</div>
<div className="flex items-center justify-between w-full">
<p className="text-gray-300 px-2 text-sm">
{org.name}
</p>
</div>
</div>
))}
</div>
</div>
)}
<div className="px-1 py-1">
<Menu.Item>
{({ active }) => (
<button
onClick={closeApp}
className={`${
active
? "bg-red font-semibold text-white"
: "text-gray-400"
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
>
<div className="relative flex justify-start items-center cursor-pointer select-none">
<FontAwesomeIcon
className="text-lg ml-1.5 mr-3"
icon={faRightFromBracket}
/>
Log Out
</div>
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
);
}

@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { faCcMastercard, faCcVisa } from "@fortawesome/free-brands-svg-icons";
import {
faAngleRight,
faQuestionCircle,
faAngleRight,
faQuestionCircle,
} from "@fortawesome/free-solid-svg-icons";
import { faCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -12,46 +12,46 @@ import getOrganization from "~/pages/api/organization/GetOrg";
import getWorkspaceInfo from "~/pages/api/workspace/getWorkspaceInfo";
export default function NavHeader({ pageName, isProjectRelated }) {
const [orgName, setOrgName] = useState("");
const [workspaceName, setWorkspaceName] = useState("");
const router = useRouter();
const [orgName, setOrgName] = useState("");
const [workspaceName, setWorkspaceName] = useState("");
const router = useRouter();
useEffect(() => {
(async () => {
let org = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
setOrgName(org.name);
let workspace = await getWorkspaceInfo({
workspaceId: router.query.id,
});
setWorkspaceName(workspace.name);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
(async () => {
let org = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
setOrgName(org.name);
let workspace = await getWorkspaceInfo({
workspaceId: router.query.id,
});
setWorkspaceName(workspace.name);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
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">
{orgName?.charAt(0)}
</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"
/>
<div className="font-semibold text-primary text-sm">
{workspaceName}
</div>
</>
)}
<FontAwesomeIcon
icon={faAngleRight}
className="ml-3 text-sm text-gray-400 mr-3"
/>
<div className="text-gray-400 text-sm">{pageName}</div>
</div>
);
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">
{orgName?.charAt(0)}
</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"
/>
<div className="font-semibold text-primary text-sm">
{workspaceName}
</div>
</>
)}
<FontAwesomeIcon
icon={faAngleRight}
className="ml-3 text-sm text-gray-400 mr-3"
/>
<div className="text-gray-400 text-sm">{pageName}</div>
</div>
);
}

@ -1,6 +1,5 @@
import token from "~/pages/api/auth/Token";
import { PATH } from "~/const";
import token from "~/pages/api/auth/Token";
export default class SecurityClient {
static authOrigins = [PATH];

@ -5,8 +5,8 @@ import getOrganizations from "~/pages/api/organization/getOrgs";
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
import { initPostHog } from "../analytics/posthog";
import { ENV } from "./config";
import pushKeys from "./secrets/pushKeys";
import { ENV } from "./config";
import SecurityClient from "./SecurityClient";
const nacl = require("tweetnacl");
@ -24,184 +24,158 @@ const client = new jsrp.client();
* @returns
*/
const attemptLogin = async (
email,
password,
setErrorLogin,
router,
isSignUp,
isLogin
email,
password,
setErrorLogin,
router,
isSignUp,
isLogin
) => {
try {
let userWorkspace, userOrg;
client.init(
{
username: email,
password: password,
},
async () => {
const clientPublicKey = client.getPublicKey();
try {
let userWorkspace, userOrg;
client.init(
{
username: email,
password: password,
},
async () => {
const clientPublicKey = client.getPublicKey();
let serverPublicKey, salt;
try {
const res = await login1(email, clientPublicKey);
res = await res.json();
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setErrorLogin(true);
console.log("Wrong password", err);
}
let serverPublicKey, salt;
try {
const res = await login1(email, clientPublicKey);
res = await res.json();
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setErrorLogin(true);
console.log("Wrong password", err);
}
let response;
try {
client.setSalt(salt);
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
response = await login2(email, clientProof);
} catch (err) {
setErrorLogin(true);
console.log("Password verification failed");
}
let response;
try {
client.setSalt(salt);
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
response = await login2(email, clientProof);
} catch (err) {
setErrorLogin(true);
console.log("Password verification failed");
}
// if everything works, go the main dashboard page.
try {
if (response.status == "200") {
response = await response.json();
SecurityClient.setToken(response["token"]);
const publicKey = response["publicKey"];
const encryptedPrivateKey =
response["encryptedPrivateKey"];
const iv = response["iv"];
const tag = response["tag"];
// if everything works, go the main dashboard page.
try {
if (response.status == "200") {
response = await response.json();
SecurityClient.setToken(response["token"]);
const publicKey = response["publicKey"];
const encryptedPrivateKey = response["encryptedPrivateKey"];
const iv = response["iv"];
const tag = response["tag"];
const PRIVATE_KEY = Aes256Gcm.decrypt(
encryptedPrivateKey,
iv,
tag,
password
.slice(0, 32)
.padStart(
32 +
(password.slice(0, 32).length -
new Blob([password]).size),
"0"
)
);
const PRIVATE_KEY = Aes256Gcm.decrypt(
encryptedPrivateKey,
iv,
tag,
password
.slice(0, 32)
.padStart(
32 +
(password.slice(0, 32).length - new Blob([password]).size),
"0"
)
);
try {
localStorage.setItem("publicKey", publicKey);
localStorage.setItem(
"encryptedPrivateKey",
encryptedPrivateKey
);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
} catch (err) {
setErrorLogin(true);
console.error(
"Unable to send the tokens in local storage:" +
err.message
);
}
} else {
setErrorLogin(true);
}
try {
localStorage.setItem("publicKey", publicKey);
localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
} catch (err) {
setErrorLogin(true);
console.error(
"Unable to send the tokens in local storage:" + err.message
);
}
} else {
setErrorLogin(true);
}
const userOrgs = await getOrganizations();
const userOrgsData = userOrgs.map((org) => org._id);
const userOrgs = await getOrganizations();
const userOrgsData = userOrgs.map((org) => org._id);
let orgToLogin;
if (
userOrgsData.includes(
localStorage.getItem("orgData.id")
)
) {
orgToLogin = localStorage.getItem("orgData.id");
} else {
orgToLogin = userOrgsData[0];
localStorage.setItem("orgData.id", orgToLogin);
}
let orgToLogin;
if (userOrgsData.includes(localStorage.getItem("orgData.id"))) {
orgToLogin = localStorage.getItem("orgData.id");
} else {
orgToLogin = userOrgsData[0];
localStorage.setItem("orgData.id", orgToLogin);
}
let orgUserProjects = await getOrganizationUserProjects({
orgId: orgToLogin,
});
let orgUserProjects = await getOrganizationUserProjects({
orgId: orgToLogin,
});
orgUserProjects = orgUserProjects?.map(
(project) => project._id
);
let projectToLogin;
if (
orgUserProjects.includes(
localStorage.getItem("projectData.id")
)
) {
projectToLogin = localStorage.getItem("projectData.id");
} else {
try {
projectToLogin = orgUserProjects[0];
localStorage.setItem(
"projectData.id",
projectToLogin
);
} catch (error) {
console.log(
"ERROR: User likely has no projects. ",
error
);
}
}
orgUserProjects = orgUserProjects?.map((project) => project._id);
let projectToLogin;
if (
orgUserProjects.includes(localStorage.getItem("projectData.id"))
) {
projectToLogin = localStorage.getItem("projectData.id");
} else {
try {
projectToLogin = orgUserProjects[0];
localStorage.setItem("projectData.id", projectToLogin);
} catch (error) {
console.log("ERROR: User likely has no projects. ", error);
}
}
// If user is logging in for the first time, add the example keys
if (isSignUp) {
await pushKeys(
{
DATABASE_URL: [
"mongodb+srv://this_is:an_example@mongodb.net",
"personal",
],
TWILIO_AUTH_TOKEN: [
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
"shared",
],
WEBSITE_URL: [
"http://localhost:3000",
"shared",
],
STRIPE_SECRET_KEY: [
"sk_test_7348oyho4hfq398HIUOH78",
"shared",
],
},
projectToLogin,
"Development"
);
}
try {
if (email) {
if (ENV == "production") {
const posthog = initPostHog();
posthog.identify(email);
posthog.capture("User Logged In");
}
}
} catch (error) {
console.log("posthog", error);
}
// If user is logging in for the first time, add the example keys
if (isSignUp) {
await pushKeys(
{
DATABASE_URL: [
"mongodb+srv://this_is:an_example@mongodb.net",
"personal",
],
TWILIO_AUTH_TOKEN: [
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
"shared",
],
WEBSITE_URL: ["http://localhost:3000", "shared"],
STRIPE_SECRET_KEY: ["sk_test_7348oyho4hfq398HIUOH78", "shared"],
},
projectToLogin,
"Development"
);
}
try {
if (email) {
if (ENV == "production") {
const posthog = initPostHog();
posthog.identify(email);
posthog.capture("User Logged In");
}
}
} catch (error) {
console.log("posthog", error);
}
if (isLogin) {
router.push("/dashboard/");
}
} catch (error) {
setErrorLogin(true);
console.log("Login response not available");
}
}
);
} catch (error) {
console.log("Something went wrong during authentication");
}
return true;
if (isLogin) {
router.push("/dashboard/");
}
} catch (error) {
setErrorLogin(true);
console.log("Login response not available");
}
}
);
} catch (error) {
console.log("Something went wrong during authentication");
}
return true;
};
export default attemptLogin;

@ -7,59 +7,59 @@
* @returns
*/
const passwordCheck = (
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck
) => {
let errorCheck = currentErrorCheck;
if (!password || password.length < 14) {
setPasswordErrorLength(true);
errorCheck = true;
} else {
setPasswordErrorLength(false);
}
if (!/\d/.test(password)) {
setPasswordErrorNumber(true);
errorCheck = true;
} else {
setPasswordErrorNumber(false);
}
if (!/[a-z]/.test(password)) {
setPasswordErrorLowerCase(true);
errorCheck = true;
// } else if (/(.)(?:(?!\1).){1,2}/.test(password)) {
// console.log(111)
// setPasswordError(true);
// setPasswordErrorMessage("Password should not contain repeating characters.");
// errorCheck = true;
// } else if (RegExp(`[${email}]`).test(password)) {
// console.log(222)
// setPasswordError(true);
// setPasswordErrorMessage("Password should not contain your email.");
// errorCheck = true;
} else {
setPasswordErrorLowerCase(false);
}
// if (!/[A-Z]/.test(password)) {
// setPasswordErrorUpperCase(true);
// errorCheck = true;
// } else {
// setPasswordErrorUpperCase(false);
// }
// if (!/(?=.*[!@#$%^&*])/.test(password)) {
// setPasswordErrorSpecialChar(true);
// // "Please add at least 1 special character (*, !, #, %)."
// errorCheck = true;
// } else {
// setPasswordErrorSpecialChar(false);
// }
return errorCheck;
let errorCheck = currentErrorCheck;
if (!password || password.length < 14) {
setPasswordErrorLength(true);
errorCheck = true;
} else {
setPasswordErrorLength(false);
}
if (!/\d/.test(password)) {
setPasswordErrorNumber(true);
errorCheck = true;
} else {
setPasswordErrorNumber(false);
}
if (!/[a-z]/.test(password)) {
setPasswordErrorLowerCase(true);
errorCheck = true;
// } else if (/(.)(?:(?!\1).){1,2}/.test(password)) {
// console.log(111)
// setPasswordError(true);
// setPasswordErrorMessage("Password should not contain repeating characters.");
// errorCheck = true;
// } else if (RegExp(`[${email}]`).test(password)) {
// console.log(222)
// setPasswordError(true);
// setPasswordErrorMessage("Password should not contain your email.");
// errorCheck = true;
} else {
setPasswordErrorLowerCase(false);
}
// if (!/[A-Z]/.test(password)) {
// setPasswordErrorUpperCase(true);
// errorCheck = true;
// } else {
// setPasswordErrorUpperCase(false);
// }
// if (!/(?=.*[!@#$%^&*])/.test(password)) {
// setPasswordErrorSpecialChar(true);
// // "Please add at least 1 special character (*, !, #, %)."
// errorCheck = true;
// } else {
// setPasswordErrorSpecialChar(false);
// }
return errorCheck;
};
export default passwordCheck;

@ -1,15 +1,17 @@
const ENV = process.env.NEXT_PUBLIC_ENV! || 'development'; // investigate
const ENV = process.env.NEXT_PUBLIC_ENV! || "development"; // investigate
const POSTHOG_API_KEY = process.env.NEXT_PUBLIC_POSTHOG_API_KEY!;
const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST! || 'https://app.posthog.com';
const POSTHOG_HOST =
process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com";
const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!;
const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!;
const TELEMETRY_ENABLED = (process.env.NEXT_PUBLIC_TELEMETRY_ENABLED! !== 'false');
const TELEMETRY_ENABLED =
process.env.NEXT_PUBLIC_TELEMETRY_ENABLED! !== "false";
export {
ENV,
POSTHOG_API_KEY,
POSTHOG_HOST,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER,
TELEMETRY_ENABLED
ENV,
POSTHOG_API_KEY,
POSTHOG_HOST,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER,
TELEMETRY_ENABLED,
};

@ -13,51 +13,51 @@ const BLOCK_SIZE_BYTES = 16; // 128 bit
* Provides easy encryption/decryption methods using AES 256 GCM.
*/
class Aes256Gcm {
/**
* No need to run the constructor. The class only has static methods.
*/
constructor() {}
/**
* Encrypts text with AES 256 GCM.
* @param {string} text - Cleartext to encode.
* @param {string} secret - Shared secret key, must be 32 bytes.
* @returns {object}
*/
static encrypt(text, secret) {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, "utf8", "base64");
ciphertext += cipher.final("base64");
return {
ciphertext,
iv: iv.toString("base64"),
tag: cipher.getAuthTag().toString("base64"),
};
}
/**
* Decrypts AES 256 CGM encrypted text.
* @param {string} ciphertext - Base64-encoded ciphertext.
* @param {string} iv - The base64-encoded initialization vector.
* @param {string} tag - The base64-encoded authentication tag generated by getAuthTag().
* @param {string} secret - Shared secret key, must be 32 bytes.
* @returns {string}
*/
static decrypt(ciphertext, iv, tag, secret) {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, "base64")
);
decipher.setAuthTag(Buffer.from(tag, "base64"));
let cleartext = decipher.update(ciphertext, "base64", "utf8");
cleartext += decipher.final("utf8");
return cleartext;
}
/**
* No need to run the constructor. The class only has static methods.
*/
constructor() {}
/**
* Encrypts text with AES 256 GCM.
* @param {string} text - Cleartext to encode.
* @param {string} secret - Shared secret key, must be 32 bytes.
* @returns {object}
*/
static encrypt(text, secret) {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, "utf8", "base64");
ciphertext += cipher.final("base64");
return {
ciphertext,
iv: iv.toString("base64"),
tag: cipher.getAuthTag().toString("base64"),
};
}
/**
* Decrypts AES 256 CGM encrypted text.
* @param {string} ciphertext - Base64-encoded ciphertext.
* @param {string} iv - The base64-encoded initialization vector.
* @param {string} tag - The base64-encoded authentication tag generated by getAuthTag().
* @param {string} secret - Shared secret key, must be 32 bytes.
* @returns {string}
*/
static decrypt(ciphertext, iv, tag, secret) {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, "base64")
);
decipher.setAuthTag(Buffer.from(tag, "base64"));
let cleartext = decipher.update(ciphertext, "base64", "utf8");
cleartext += decipher.final("utf8");
return cleartext;
}
}
module.exports = Aes256Gcm;

@ -19,109 +19,102 @@ const clientNewPassword = new jsrp.client();
* @returns
*/
const changePassword = async (
email,
currentPassword,
newPassword,
setCurrentPasswordError,
setPasswordChanged,
setCurrentPassword,
setNewPassword
email,
currentPassword,
newPassword,
setCurrentPasswordError,
setPasswordChanged,
setCurrentPassword,
setNewPassword
) => {
try {
setPasswordChanged(false);
setCurrentPasswordError(false);
try {
setPasswordChanged(false);
setCurrentPasswordError(false);
clientOldPassword.init(
{
username: email,
password: currentPassword,
},
async () => {
const clientPublicKey = clientOldPassword.getPublicKey();
clientOldPassword.init(
{
username: email,
password: currentPassword,
},
async () => {
const clientPublicKey = clientOldPassword.getPublicKey();
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey,
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setCurrentPasswordError(true);
console.log("Wrong current password", err, 1);
}
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey,
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setCurrentPasswordError(true);
console.log("Wrong current password", err, 1);
}
clientOldPassword.setSalt(salt);
clientOldPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientOldPassword.getProof(); // called M1
clientOldPassword.setSalt(salt);
clientOldPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientOldPassword.getProof(); // called M1
clientNewPassword.init(
{
username: email,
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., é, å, ø)
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
newPassword
.slice(0, 32)
.padStart(
32 +
(newPassword.slice(0, 32)
.length -
new Blob([newPassword])
.size),
"0"
)
);
clientNewPassword.init(
{
username: email,
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., é, å, ø)
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
newPassword
.slice(0, 32)
.padStart(
32 +
(newPassword.slice(0, 32).length -
new Blob([newPassword]).size),
"0"
)
);
if (ciphertext) {
localStorage.setItem(
"encryptedPrivateKey",
ciphertext
);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
if (ciphertext) {
localStorage.setItem("encryptedPrivateKey", ciphertext);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
let res;
try {
res = await changePassword2({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof,
});
if (res.status == 400) {
setCurrentPasswordError(true);
} else if (res.status == 200) {
setPasswordChanged(true);
setCurrentPassword("");
setNewPassword("");
}
} catch (err) {
setCurrentPasswordError(true);
console.log(err);
}
}
}
);
}
);
}
);
} catch (error) {
console.log(
"Something went wrong during changing the password",
slat,
serverPublicKey
);
}
return true;
let res;
try {
res = await changePassword2({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof,
});
if (res.status == 400) {
setCurrentPasswordError(true);
} else if (res.status == 200) {
setPasswordChanged(true);
setCurrentPassword("");
setNewPassword("");
}
} catch (err) {
setCurrentPasswordError(true);
console.log(err);
}
}
});
}
);
}
);
} catch (error) {
console.log(
"Something went wrong during changing the password",
slat,
serverPublicKey
);
}
return true;
};
export default changePassword;

@ -14,18 +14,18 @@ const aes = require("./aes-256-gcm");
* @returns {String} nonce - base64-encoded nonce
*/
const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
const nonce = nacl.randomBytes(24);
const ciphertext = nacl.box(
nacl.util.decodeUTF8(plaintext),
nonce,
nacl.util.decodeBase64(publicKey),
nacl.util.decodeBase64(privateKey)
);
const nonce = nacl.randomBytes(24);
const ciphertext = nacl.box(
nacl.util.decodeUTF8(plaintext),
nonce,
nacl.util.decodeBase64(publicKey),
nacl.util.decodeBase64(privateKey)
);
return {
ciphertext: nacl.util.encodeBase64(ciphertext),
nonce: nacl.util.encodeBase64(nonce),
};
return {
ciphertext: nacl.util.encodeBase64(ciphertext),
nonce: nacl.util.encodeBase64(nonce),
};
};
/**
@ -39,14 +39,14 @@ const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
* @param {String} plaintext - UTF8 plaintext
*/
const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
const plaintext = nacl.box.open(
nacl.util.decodeBase64(ciphertext),
nacl.util.decodeBase64(nonce),
nacl.util.decodeBase64(publicKey),
nacl.util.decodeBase64(privateKey)
);
const plaintext = nacl.box.open(
nacl.util.decodeBase64(ciphertext),
nacl.util.decodeBase64(nonce),
nacl.util.decodeBase64(publicKey),
nacl.util.decodeBase64(privateKey)
);
return nacl.util.encodeUTF8(plaintext);
return nacl.util.encodeUTF8(plaintext);
};
/**
@ -56,23 +56,23 @@ const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
* @param {String} obj.key - 16-byte hex key
*/
const encryptSymmetric = ({ plaintext, key }) => {
let ciphertext, iv, tag;
try {
const obj = aes.encrypt(plaintext, key);
ciphertext = obj.ciphertext;
iv = obj.iv;
tag = obj.tag;
} catch (err) {
console.log("Failed to perform encryption");
console.log(err);
process.exit(1);
}
let ciphertext, iv, tag;
try {
const obj = aes.encrypt(plaintext, key);
ciphertext = obj.ciphertext;
iv = obj.iv;
tag = obj.tag;
} catch (err) {
console.log("Failed to perform encryption");
console.log(err);
process.exit(1);
}
return {
ciphertext,
iv,
tag,
};
return {
ciphertext,
iv,
tag,
};
};
/**
@ -86,20 +86,20 @@ const encryptSymmetric = ({ plaintext, key }) => {
*
*/
const decryptSymmetric = ({ ciphertext, iv, tag, key }) => {
let plaintext;
try {
plaintext = aes.decrypt(ciphertext, iv, tag, key);
} catch (err) {
console.log("Failed to perform decryption");
process.exit(1);
}
let plaintext;
try {
plaintext = aes.decrypt(ciphertext, iv, tag, key);
} catch (err) {
console.log("Failed to perform decryption");
process.exit(1);
}
return plaintext;
return plaintext;
};
module.exports = {
encryptAssymmetric,
decryptAssymmetric,
encryptSymmetric,
decryptSymmetric,
encryptAssymmetric,
decryptAssymmetric,
encryptSymmetric,
decryptSymmetric,
};

@ -1,8 +1,8 @@
import issueBackupPrivateKey from "~/pages/api/auth/IssueBackupPrivateKey";
import SRP1 from "~/pages/api/auth/SRP1";
import Aes256Gcm from "./aes-256-gcm";
import generateBackupPDF from "../generateBackupPDF";
import Aes256Gcm from "./aes-256-gcm";
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
@ -21,82 +21,78 @@ const crypto = require("crypto");
* @returns
*/
const issueBackupKey = async ({
email,
password,
personalName,
setBackupKeyError,
setBackupKeyIssued,
email,
password,
personalName,
setBackupKeyError,
setBackupKeyIssued,
}) => {
try {
setBackupKeyError(false);
setBackupKeyIssued(false);
clientPassword.init(
{
username: email,
password: password,
},
async () => {
const clientPublicKey = clientPassword.getPublicKey();
try {
setBackupKeyError(false);
setBackupKeyIssued(false);
clientPassword.init(
{
username: email,
password: password,
},
async () => {
const clientPublicKey = clientPassword.getPublicKey();
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey,
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setBackupKeyError(true);
console.log("Wrong current password", err, 1);
}
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey,
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setBackupKeyError(true);
console.log("Wrong current password", err, 1);
}
clientPassword.setSalt(salt);
clientPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientPassword.getProof(); // called M1
clientPassword.setSalt(salt);
clientPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientPassword.getProof(); // called M1
const generatedKey = crypto.randomBytes(16).toString("hex");
const generatedKey = crypto.randomBytes(16).toString("hex");
clientKey.init(
{
username: email,
password: generatedKey,
},
async () => {
clientKey.createVerifier(async (err, result) => {
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
generatedKey
);
clientKey.init(
{
username: email,
password: generatedKey,
},
async () => {
clientKey.createVerifier(async (err, result) => {
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
generatedKey
);
const res = await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof,
});
const res = await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof,
});
if (res.status == 400) {
setBackupKeyError(true);
} else if (res.status == 200) {
generateBackupPDF(
personalName,
email,
generatedKey
);
setBackupKeyIssued(true);
}
});
}
);
}
);
} catch (error) {
setBackupKeyError(true);
console.log("Failed to issue a backup key");
}
return true;
if (res.status == 400) {
setBackupKeyError(true);
} else if (res.status == 200) {
generateBackupPDF(personalName, email, generatedKey);
setBackupKeyIssued(true);
}
});
}
);
}
);
} catch (error) {
setBackupKeyError(true);
console.log("Failed to issue a backup key");
}
return true;
};
export default issueBackupKey;

@ -1,5 +1,5 @@
const LINE =
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
/**
* Return text that is the buffer parsed
@ -7,41 +7,41 @@ const LINE =
* @returns {String} text - text of buffer
*/
function parse(src) {
const obj = {};
const obj = {};
// Convert buffer to string
let lines = src.toString();
// Convert buffer to string
let lines = src.toString();
// Convert line breaks to same format
lines = lines.replace(/\r\n?/gm, "\n");
// Convert line breaks to same format
lines = lines.replace(/\r\n?/gm, "\n");
let match;
while ((match = LINE.exec(lines)) != null) {
const key = match[1];
let match;
while ((match = LINE.exec(lines)) != null) {
const key = match[1];
// Default undefined or null to empty string
let value = match[2] || "";
// Default undefined or null to empty string
let value = match[2] || "";
// Remove whitespace
value = value.trim();
// Remove whitespace
value = value.trim();
// Check if double quoted
const maybeQuote = value[0];
// Check if double quoted
const maybeQuote = value[0];
// Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
// Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
// Expand newlines if double quoted
if (maybeQuote === '"') {
value = value.replace(/\\n/g, "\n");
value = value.replace(/\\r/g, "\r");
}
// Expand newlines if double quoted
if (maybeQuote === '"') {
value = value.replace(/\\n/g, "\n");
value = value.replace(/\\r/g, "\r");
}
// Add to object
obj[key] = value;
}
// Add to object
obj[key] = value;
}
return obj;
return obj;
}
export default parse;

File diff suppressed because one or more lines are too long

@ -3,23 +3,23 @@
* @returns
*/
const guidGenerator = () => {
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (
S4() +
S4() +
"-" +
S4() +
"-" +
S4() +
"-" +
S4() +
"-" +
S4() +
S4() +
S4()
);
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (
S4() +
S4() +
"-" +
S4() +
"-" +
S4() +
"-" +
S4() +
"-" +
S4() +
S4() +
S4()
);
};
export default guidGenerator;

@ -3,103 +3,101 @@ import getSecrets from "~/pages/api/files/GetSecrets";
import guidGenerator from "../randomId";
const {
decryptAssymmetric,
decryptSymmetric,
decryptAssymmetric,
decryptSymmetric,
} = require("../cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const getSecretsForProject = async ({
env,
setFileState,
setIsKeyAvailable,
setData,
workspaceId,
env,
setFileState,
setIsKeyAvailable,
setData,
workspaceId,
}) => {
try {
let file;
try {
file = await getSecrets(workspaceId, envMapping[env]);
try {
let file;
try {
file = await getSecrets(workspaceId, envMapping[env]);
setFileState(file);
} catch (error) {
console.log("ERROR: Not able to access the latest file");
}
// This is called isKeyAvilable but what it really means is if a person is able to create new key pairs
setIsKeyAvailable(
!file.key ? (file.secrets.length == 0 ? true : false) : true
);
setFileState(file);
} catch (error) {
console.log("ERROR: Not able to access the latest file");
}
// This is called isKeyAvilable but what it really means is if a person is able to create new key pairs
setIsKeyAvailable(
!file.key ? (file.secrets.length == 0 ? true : false) : true
);
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let tempFileState = [];
if (file.key) {
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: file.key.encryptedKey,
nonce: file.key.nonce,
publicKey: file.key.sender.publicKey,
privateKey: PRIVATE_KEY,
});
let tempFileState = [];
if (file.key) {
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: file.key.encryptedKey,
nonce: file.key.nonce,
publicKey: file.key.sender.publicKey,
privateKey: PRIVATE_KEY,
});
file.secrets.map((secretPair) => {
// decrypt .env file with symmetric key
const plainTextKey = decryptSymmetric({
ciphertext: secretPair.secretKey.ciphertext,
iv: secretPair.secretKey.iv,
tag: secretPair.secretKey.tag,
key,
});
file.secrets.map((secretPair) => {
// decrypt .env file with symmetric key
const plainTextKey = decryptSymmetric({
ciphertext: secretPair.secretKey.ciphertext,
iv: secretPair.secretKey.iv,
tag: secretPair.secretKey.tag,
key,
});
const plainTextValue = decryptSymmetric({
ciphertext: secretPair.secretValue.ciphertext,
iv: secretPair.secretValue.iv,
tag: secretPair.secretValue.tag,
key,
});
tempFileState.push({
key: plainTextKey,
value: plainTextValue,
type: secretPair.type,
});
});
}
setFileState(tempFileState);
const plainTextValue = decryptSymmetric({
ciphertext: secretPair.secretValue.ciphertext,
iv: secretPair.secretValue.iv,
tag: secretPair.secretValue.tag,
key,
});
tempFileState.push({
key: plainTextKey,
value: plainTextValue,
type: secretPair.type,
});
});
}
setFileState(tempFileState);
setData(
tempFileState.map((line, index) => [
guidGenerator(),
index,
line["key"],
line["value"],
line["type"],
])
// .sort((a, b) =>
// sortMethod == "alphabetical"
// ? a[2].localeCompare(b[2])
// : b[2].localeCompare(a[2])
// )
);
return tempFileState.map((line, index) => [
guidGenerator(),
index,
line["key"],
line["value"],
line["type"],
]);
} catch (error) {
console.log(
"Something went wrong during accessing or decripting secrets."
);
}
return true;
setData(
tempFileState.map((line, index) => [
guidGenerator(),
index,
line["key"],
line["value"],
line["type"],
])
// .sort((a, b) =>
// sortMethod == "alphabetical"
// ? a[2].localeCompare(b[2])
// : b[2].localeCompare(a[2])
// )
);
return tempFileState.map((line, index) => [
guidGenerator(),
index,
line["key"],
line["value"],
line["type"],
]);
} catch (error) {
console.log("Something went wrong during accessing or decripting secrets.");
}
return true;
};
export default getSecretsForProject;

@ -4,106 +4,103 @@ import getWorkspaceKeys from "~/pages/api/workspace/getWorkspaceKeys";
const crypto = require("crypto");
const {
decryptAssymmetric,
decryptSymmetric,
encryptSymmetric,
encryptAssymmetric,
decryptAssymmetric,
decryptSymmetric,
encryptSymmetric,
encryptAssymmetric,
} = require("../cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const pushKeys = async (obj, workspaceId, env) => {
let sharedKey = await getLatestFileKey(workspaceId);
let sharedKey = await getLatestFileKey(workspaceId);
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let randomBytes;
if (Object.keys(sharedKey).length > 0) {
// case: a (shared) key exists for the workspace
randomBytes = decryptAssymmetric({
ciphertext: sharedKey.latestKey.encryptedKey,
nonce: sharedKey.latestKey.nonce,
publicKey: sharedKey.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
});
} else {
// case: a (shared) key does not exist for the workspace
randomBytes = crypto.randomBytes(16).toString("hex");
}
let randomBytes;
if (Object.keys(sharedKey).length > 0) {
// case: a (shared) key exists for the workspace
randomBytes = decryptAssymmetric({
ciphertext: sharedKey.latestKey.encryptedKey,
nonce: sharedKey.latestKey.nonce,
publicKey: sharedKey.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY,
});
} else {
// case: a (shared) key does not exist for the workspace
randomBytes = crypto.randomBytes(16).toString("hex");
}
const secrets = Object.keys(obj).map((key) => {
// encrypt key
const {
ciphertext: ciphertextKey,
iv: ivKey,
tag: tagKey,
} = encryptSymmetric({
plaintext: key,
key: randomBytes,
});
const secrets = Object.keys(obj).map((key) => {
// encrypt key
const {
ciphertext: ciphertextKey,
iv: ivKey,
tag: tagKey,
} = encryptSymmetric({
plaintext: key,
key: randomBytes,
});
// encrypt value
const {
ciphertext: ciphertextValue,
iv: ivValue,
tag: tagValue,
} = encryptSymmetric({
plaintext: obj[key][0],
key: randomBytes,
});
// encrypt value
const {
ciphertext: ciphertextValue,
iv: ivValue,
tag: tagValue,
} = encryptSymmetric({
plaintext: obj[key][0],
key: randomBytes,
});
const visibility = obj[key][1] != null ? obj[key][1] : "personal";
const visibility = obj[key][1] != null ? obj[key][1] : "personal";
return {
ciphertextKey,
ivKey,
tagKey,
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
ciphertextValue,
ivValue,
tagValue,
hashValue: crypto
.createHash("sha256")
.update(obj[key][0])
.digest("hex"),
type: visibility,
};
});
return {
ciphertextKey,
ivKey,
tagKey,
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
ciphertextValue,
ivValue,
tagValue,
hashValue: crypto.createHash("sha256").update(obj[key][0]).digest("hex"),
type: visibility,
};
});
// obtain public keys of all receivers (i.e. members in workspace)
const publicKeys = await getWorkspaceKeys({
workspaceId,
});
// obtain public keys of all receivers (i.e. members in workspace)
const publicKeys = await getWorkspaceKeys({
workspaceId,
});
// assymmetrically encrypt key with each receiver public keys
const keys = publicKeys.map((k) => {
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: k.publicKey,
privateKey: PRIVATE_KEY,
});
// assymmetrically encrypt key with each receiver public keys
const keys = publicKeys.map((k) => {
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: k.publicKey,
privateKey: PRIVATE_KEY,
});
return {
encryptedKey: ciphertext,
nonce,
userId: k.userId,
};
});
return {
encryptedKey: ciphertext,
nonce,
userId: k.userId,
};
});
// send payload
await uploadSecrets({
workspaceId,
secrets,
keys,
environment: envMapping[env],
});
// send payload
await uploadSecrets({
workspaceId,
secrets,
keys,
environment: envMapping[env],
});
};
export default pushKeys;

@ -2,73 +2,73 @@ import publicKeyInfical from "~/pages/api/auth/publicKeyInfisical";
import changeHerokuConfigVars from "~/pages/api/integrations/ChangeHerokuConfigVars";
const crypto = require("crypto");
const { encryptSymmetric, encryptAssymmetric } = require("../cryptography/crypto");
const {
encryptSymmetric,
encryptAssymmetric,
} = require("../cryptography/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const pushKeysIntegration = async ({ obj, integrationId }) => {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let randomBytes = crypto.randomBytes(16).toString("hex");
let randomBytes = crypto.randomBytes(16).toString("hex");
const secrets = Object.keys(obj).map((key) => {
// encrypt key
const {
ciphertext: ciphertextKey,
iv: ivKey,
tag: tagKey,
} = encryptSymmetric({
plaintext: key,
key: randomBytes,
});
const secrets = Object.keys(obj).map((key) => {
// encrypt key
const {
ciphertext: ciphertextKey,
iv: ivKey,
tag: tagKey,
} = encryptSymmetric({
plaintext: key,
key: randomBytes,
});
// encrypt value
const {
ciphertext: ciphertextValue,
iv: ivValue,
tag: tagValue,
} = encryptSymmetric({
plaintext: obj[key],
key: randomBytes,
});
// encrypt value
const {
ciphertext: ciphertextValue,
iv: ivValue,
tag: tagValue,
} = encryptSymmetric({
plaintext: obj[key],
key: randomBytes,
});
const visibility = "shared";
const visibility = "shared";
return {
ciphertextKey,
ivKey,
tagKey,
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
ciphertextValue,
ivValue,
tagValue,
hashValue: crypto
.createHash("sha256")
.update(obj[key])
.digest("hex"),
type: visibility,
};
});
return {
ciphertextKey,
ivKey,
tagKey,
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
ciphertextValue,
ivValue,
tagValue,
hashValue: crypto.createHash("sha256").update(obj[key]).digest("hex"),
type: visibility,
};
});
// obtain public keys of all receivers (i.e. members in workspace)
let publicKeyInfisical = await publicKeyInfical();
// obtain public keys of all receivers (i.e. members in workspace)
let publicKeyInfisical = await publicKeyInfical();
publicKeyInfisical = (await publicKeyInfisical.json()).publicKey;
publicKeyInfisical = (await publicKeyInfisical.json()).publicKey;
// assymmetrically encrypt key with each receiver public keys
// assymmetrically encrypt key with each receiver public keys
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: publicKeyInfisical,
privateKey: PRIVATE_KEY,
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: publicKeyInfisical,
privateKey: PRIVATE_KEY,
});
const key = {
encryptedKey: ciphertext,
nonce,
};
const key = {
encryptedKey: ciphertext,
nonce,
};
changeHerokuConfigVars({ integrationId, key, secrets });
changeHerokuConfigVars({ integrationId, key, secrets });
};
export default pushKeysIntegration;

@ -1,20 +1,20 @@
export const PATH = process.env.NEXT_PUBLIC_WEBSITE_URL;
export const publicPaths = [
`/`,
// `/integrations`,
`/signupinvite`,
`/pricing`,
`/signup`,
`/login`,
`/blog`,
`/docs`,
`/changelog`,
`/security`,
`/scheduledemo`,
`/blog/[slug]`,
`/faq`,
`/privacy`,
`/terms`,
`/subprocessors`,
`/`,
// `/integrations`,
`/signupinvite`,
`/pricing`,
`/signup`,
`/login`,
`/blog`,
`/docs`,
`/changelog`,
`/security`,
`/scheduledemo`,
`/blog/[slug]`,
`/faq`,
`/privacy`,
`/terms`,
`/subprocessors`,
];

@ -2,11 +2,15 @@
const ContentSecurityPolicy = `
default-src ${process.env.NEXT_PUBLIC_WEBSITE_URL};
script-src ${process.env.NEXT_PUBLIC_WEBSITE_URL} https://app.posthog.com https://infisical.com https://assets.calendly.com/ https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval';
script-src ${
process.env.NEXT_PUBLIC_WEBSITE_URL
} https://app.posthog.com https://infisical.com https://assets.calendly.com/ https://js.stripe.com https://api.stripe.com 'unsafe-inline' 'unsafe-eval';
style-src 'self' https://rsms.me 'unsafe-inline';
child-src https://infisical.com https://api.stripe.com;
frame-src https://js.stripe.com/ https://api.stripe.com;
connect-src ws://${process.env.NEXT_PUBLIC_WEBSITE_URL?.split('//')[1]} ${process.env.NEXT_PUBLIC_WEBSITE_URL} https://api.github.com/repos/Infisical/infisical-cli https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://infisical.com https://api.stripe.com https://vitals.vercel-insights.com/v1/vitals;
connect-src ws://${process.env.NEXT_PUBLIC_WEBSITE_URL?.split("//")[1]} ${
process.env.NEXT_PUBLIC_WEBSITE_URL
} https://api.github.com/repos/Infisical/infisical-cli https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://infisical.com https://api.stripe.com https://vitals.vercel-insights.com/v1/vitals;
img-src 'self' https://*.stripe.com https://i.ytimg.com/ data:;
media-src;
font-src 'self' https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com;
@ -15,49 +19,49 @@ const ContentSecurityPolicy = `
// You can choose which headers to add to the list
// after learning more below.
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
{
key: "X-XSS-Protection",
value: "1; mode=block",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "Permissions-Policy",
value: "camera=(), microphone=()",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Content-Security-Policy",
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
{
key: "X-XSS-Protection",
value: "1; mode=block",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "Permissions-Policy",
value: "camera=(), microphone=()",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Content-Security-Policy",
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
},
];
module.exports = {
output: 'standalone',
async headers() {
return [
{
// Apply these headers to all routes in your application.
source: "/:path*",
headers: securityHeaders,
},
];
},
output: "standalone",
async headers() {
return [
{
// Apply these headers to all routes in your application.
source: "/:path*",
headers: securityHeaders,
},
];
},
};

@ -1,65 +1,65 @@
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"start:docker": "next build && next start",
"lint": "next lint"
},
"dependencies": {
"@emotion/css": "^11.10.0",
"@emotion/server": "^11.10.0",
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-brands-svg-icons": "^6.1.2",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/react-fontawesome": "^0.1.19",
"@headlessui/react": "^1.6.6",
"@heroicons/react": "^1.0.6",
"@reduxjs/toolkit": "^1.8.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"add": "^2.0.6",
"axios": "^0.27.2",
"axios-auth-refresh": "^3.3.3",
"classnames": "^2.3.1",
"cookies": "^0.8.0",
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"http-proxy": "^1.18.1",
"jspdf": "^2.5.1",
"jsrp": "^0.2.4",
"markdown-it": "^13.0.1",
"next": "^12.2.5",
"posthog-js": "^1.34.0",
"query-string": "^7.1.1",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
"react-code-input": "^3.10.1",
"react-dom": "^17.0.2",
"react-github-btn": "^1.4.0",
"react-grid-layout": "^1.3.4",
"react-mailchimp-subscribe": "^2.1.3",
"react-markdown": "^8.0.3",
"react-redux": "^8.0.2",
"react-table": "^7.8.0",
"set-cookie-parser": "^2.5.1",
"styled-components": "^5.3.5",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"uuid": "^8.3.2",
"uuidv4": "^6.2.13"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.4",
"autoprefixer": "^10.4.7",
"eslint": "^8.28.0",
"eslint-config-next": "^13.0.5",
"eslint-plugin-simple-import-sort": "^8.0.0",
"postcss": "^8.4.14",
"prettier": "2.7.1",
"tailwindcss": "^3.1.4",
"typescript": "^4.9.3"
}
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"start:docker": "next build && next start",
"lint": "next lint"
},
"dependencies": {
"@emotion/css": "^11.10.0",
"@emotion/server": "^11.10.0",
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-brands-svg-icons": "^6.1.2",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/react-fontawesome": "^0.1.19",
"@headlessui/react": "^1.6.6",
"@heroicons/react": "^1.0.6",
"@reduxjs/toolkit": "^1.8.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"add": "^2.0.6",
"axios": "^0.27.2",
"axios-auth-refresh": "^3.3.3",
"classnames": "^2.3.1",
"cookies": "^0.8.0",
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"http-proxy": "^1.18.1",
"jspdf": "^2.5.1",
"jsrp": "^0.2.4",
"markdown-it": "^13.0.1",
"next": "^12.2.5",
"posthog-js": "^1.34.0",
"query-string": "^7.1.1",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
"react-code-input": "^3.10.1",
"react-dom": "^17.0.2",
"react-github-btn": "^1.4.0",
"react-grid-layout": "^1.3.4",
"react-mailchimp-subscribe": "^2.1.3",
"react-markdown": "^8.0.3",
"react-redux": "^8.0.2",
"react-table": "^7.8.0",
"set-cookie-parser": "^2.5.1",
"styled-components": "^5.3.5",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"uuid": "^8.3.2",
"uuidv4": "^6.2.13"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.4",
"autoprefixer": "^10.4.7",
"eslint": "^8.28.0",
"eslint-config-next": "^13.0.5",
"eslint-plugin-simple-import-sort": "^8.0.0",
"postcss": "^8.4.14",
"prettier": "2.7.1",
"tailwindcss": "^3.1.4",
"typescript": "^4.9.3"
}
}

@ -5,9 +5,8 @@ import { config } from "@fortawesome/fontawesome-svg-core";
import { initPostHog } from "~/components/analytics/posthog";
import Layout from "~/components/basic/layout";
import RouteGuard from "~/components/RouteGuard";
import { ENV } from "~/utilities/config";
import { publicPaths } from "~/const";
import { ENV } from "~/utilities/config";
import "@fortawesome/fontawesome-svg-core/styles.css";
import "../styles/globals.css";

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the second step of the change password process (pake)

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient.js";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient.js";
/**
* This function is used to check if the user is authenticated.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the route that issues a backup private key that will afterwards be added into a pdf

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route logs the user out. Note: the user should authorized to do this.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the first step of the change password process (pake)

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient.js";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient.js";
/**
* This function fetches the encrypted secrets from the .env file

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function uploads the encrypted .env file

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
const changeHerokuConfigVars = ({ integrationId, key, secrets }) => {
return SecurityClient.fetchCall(

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes an integration from a certain project

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes an integration authorization from a certain project

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
const getIntegrationApps = ({ integrationAuthId }) => {
return SecurityClient.fetchCall(

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
const getIntegrations = () => {
return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", {

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route starts the integration after teh default one if gonna set up.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This is the first step of the change password process (pake)

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets authorizations of a certain project (Heroku, etc.)

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets integrations of a certain project (Heroku, etc.)

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get info about a certain org

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the users in an org.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the current subscription of an org.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the projects of a certain user in an org.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the users in an org.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route redirects the user to the right stripe billing page.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route add an incident contact email to a certain organization

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function sends an email invite to a user to join an org

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes an incident Contact from a certain organization

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function removes a certain member from a certain organization

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This routes gets all the incident contacts of a certain organization

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the all the orgs of a certain user.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us rename a certain org.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets service tokens for a specific user in a project

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets service tokens for a specific user in a project

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route gets the information about a specific user.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route registers a certain action for a user

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route registers a certain action for a user

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function adds a user to a project

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function change the access of a user in a certain workspace

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route creates a new workspace for a user.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This function removes a certain member from a certain workspace

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route deletes a specified workspace.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* Get the latest key pairs from a certain workspace

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the information of a certain project.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the public keys of everyone in your workspace.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get all the users in the workspace.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us get the public keys of everyone in your workspace.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route lets us rename a certain workspace.

@ -1,6 +1,5 @@
import SecurityClient from "~/utilities/SecurityClient";
import { PATH } from "~/const";
import SecurityClient from "~/utilities/SecurityClient";
/**
* This route uplods the keys in an encrypted format.

@ -5,31 +5,29 @@ import { useRouter } from "next/router";
import getWorkspaces from "./api/workspace/getWorkspaces";
export default function DashboardRedirect() {
const router = useRouter();
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
}, []);
/**
* 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>;
return <div></div>;
}
DashboardRedirect.requireAuth = true;

File diff suppressed because it is too large Load Diff

@ -5,34 +5,32 @@ 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;
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")) {
await AuthorizeIntegration({
workspaceId: localStorage.getItem("projectData.id"),
code,
integration: "heroku",
});
router.push(
"/integrations/" + localStorage.getItem("projectData.id")
);
}
} catch (error) {
console.log("Error - Not logged in yet");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
/**
* 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")) {
await AuthorizeIntegration({
workspaceId: localStorage.getItem("projectData.id"),
code,
integration: "heroku",
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (error) {
console.log("Error - Not logged in yet");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
return <div></div>;
}
Heroku.requireAuth = true;

@ -2,12 +2,12 @@ import React, { useEffect } from "react";
import { useRouter } from "next/router";
export default function Home() {
const router = useRouter();
const router = useRouter();
useEffect(() => {
router.push("/login");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
router.push("/login");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div className="bg-bunker-800 w-screen"></div>;
return <div className="bg-bunker-800 w-screen"></div>;
}

@ -1,12 +1,12 @@
import React, { useEffect,useState } from "react";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";
import {
faArrowRight,
faCheck,
faRotate,
faX,
faArrowRight,
faCheck,
faRotate,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -28,408 +28,345 @@ import startIntegration from "../api/integrations/StartIntegration";
const crypto = require("crypto");
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const reverseEnvMapping = {
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing",
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing",
};
const Integration = ({ projectIntegration }) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState(
reverseEnvMapping[projectIntegration.environment]
);
const [fileState, setFileState] = useState([]);
const [data, setData] = useState();
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
const router = useRouter();
const [apps, setApps] = useState([]);
const [integrationApp, setIntegrationApp] = useState(
projectIntegration.app ? projectIntegration.app : apps[0]
);
const [integrationEnvironment, setIntegrationEnvironment] = useState(
reverseEnvMapping[projectIntegration.environment]
);
const [fileState, setFileState] = useState([]);
const [data, setData] = useState();
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
const router = useRouter();
const [apps, setApps] = useState([]);
const [integrationApp, setIntegrationApp] = useState(
projectIntegration.app ? projectIntegration.app : apps[0]
);
useEffect(async () => {
const tempHerokuApps = await getIntegrationApps({
integrationAuthId: projectIntegration.integrationAuth,
});
const tempHerokuAppNames = tempHerokuApps.map((app) => app.name);
setApps(tempHerokuAppNames);
setIntegrationApp(
projectIntegration.app
? projectIntegration.app
: tempHerokuAppNames[0]
);
}, []);
useEffect(async () => {
const tempHerokuApps = await getIntegrationApps({
integrationAuthId: projectIntegration.integrationAuth,
});
const tempHerokuAppNames = tempHerokuApps.map((app) => app.name);
setApps(tempHerokuAppNames);
setIntegrationApp(
projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0]
);
}, []);
return (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
<div className="flex flex-row">
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
ENVIRONMENT
</div>
<ListBox
data={
!projectIntegration.isActive && [
"Development",
"Staging",
"Production",
]
}
selected={integrationEnvironment}
onChange={setIntegrationEnvironment}
/>
</div>
<FontAwesomeIcon
icon={faArrowRight}
className="mx-4 text-gray-400 mt-8"
/>
<div className="mr-2">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
INTEGRATION
</div>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
{projectIntegration.integration
.charAt(0)
.toUpperCase() +
projectIntegration.integration.slice(1)}
</div>
</div>
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
HEROKU APP
</div>
<ListBox
data={!projectIntegration.isActive && apps}
selected={integrationApp}
onChange={setIntegrationApp}
/>
</div>
</div>
<div className="flex flex-row mt-6">
{projectIntegration.isActive ? (
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
<FontAwesomeIcon
icon={faRotate}
className="text-lg mr-2.5 text-primary animate-spin"
/>
<div className="text-gray-300 font-semibold">
In Sync
</div>
</div>
) : (
<Button
text="Start Integration"
onButtonPressed={async () => {
const result = await startIntegration({
integrationId: projectIntegration._id,
environment:
envMapping[integrationEnvironment],
appName: integrationApp,
});
if (result?.status == 200) {
let currentSecrets =
await getSecretsForProject({
env: integrationEnvironment,
setFileState,
setIsKeyAvailable,
setData,
workspaceId: router.query.id,
});
return (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
<div className="flex flex-row">
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
ENVIRONMENT
</div>
<ListBox
data={
!projectIntegration.isActive && [
"Development",
"Staging",
"Production",
]
}
selected={integrationEnvironment}
onChange={setIntegrationEnvironment}
/>
</div>
<FontAwesomeIcon
icon={faArrowRight}
className="mx-4 text-gray-400 mt-8"
/>
<div className="mr-2">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
INTEGRATION
</div>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
{projectIntegration.integration.charAt(0).toUpperCase() +
projectIntegration.integration.slice(1)}
</div>
</div>
<div>
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
HEROKU APP
</div>
<ListBox
data={!projectIntegration.isActive && apps}
selected={integrationApp}
onChange={setIntegrationApp}
/>
</div>
</div>
<div className="flex flex-row mt-6">
{projectIntegration.isActive ? (
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
<FontAwesomeIcon
icon={faRotate}
className="text-lg mr-2.5 text-primary animate-spin"
/>
<div className="text-gray-300 font-semibold">In Sync</div>
</div>
) : (
<Button
text="Start Integration"
onButtonPressed={async () => {
const result = await startIntegration({
integrationId: projectIntegration._id,
environment: envMapping[integrationEnvironment],
appName: integrationApp,
});
if (result?.status == 200) {
let currentSecrets = await getSecretsForProject({
env: integrationEnvironment,
setFileState,
setIsKeyAvailable,
setData,
workspaceId: router.query.id,
});
let obj = Object.assign(
{},
...currentSecrets.map((row) => ({
[row[2]]: row[3],
}))
);
await pushKeysIntegration({
obj,
integrationId: projectIntegration._id,
});
router.reload();
}
}}
color="mineshaft"
size="md"
/>
)}
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
<Button
onButtonPressed={async () => {
await deleteIntegration({
integrationId: projectIntegration._id,
});
router.reload();
}}
color="red"
size="icon-md"
icon={faX}
/>
</div>
</div>
</div>
</div>
);
let obj = Object.assign(
{},
...currentSecrets.map((row) => ({
[row[2]]: row[3],
}))
);
await pushKeysIntegration({
obj,
integrationId: projectIntegration._id,
});
router.reload();
}
}}
color="mineshaft"
size="md"
/>
)}
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
<Button
onButtonPressed={async () => {
await deleteIntegration({
integrationId: projectIntegration._id,
});
router.reload();
}}
color="red"
size="icon-md"
icon={faX}
/>
</div>
</div>
</div>
</div>
);
};
export default function Integrations() {
const [integrations, setIntegrations] = useState();
const [projectIntegrations, setProjectIntegrations] = useState();
const [authorizations, setAuthorizations] = useState();
const router = useRouter();
const [csrfToken, setCsrfToken] = useState("");
const [integrations, setIntegrations] = useState();
const [projectIntegrations, setProjectIntegrations] = useState();
const [authorizations, setAuthorizations] = useState();
const router = useRouter();
const [csrfToken, setCsrfToken] = useState("");
useEffect(async () => {
const tempCSRFToken = crypto.randomBytes(16).toString("hex");
setCsrfToken(tempCSRFToken);
localStorage.setItem("latestCSRFToken", tempCSRFToken);
useEffect(async () => {
const tempCSRFToken = crypto.randomBytes(16).toString("hex");
setCsrfToken(tempCSRFToken);
localStorage.setItem("latestCSRFToken", tempCSRFToken);
let projectAuthorizations = await getWorkspaceAuthorizations({
workspaceId: router.query.id,
});
setAuthorizations(projectAuthorizations);
let projectAuthorizations = await getWorkspaceAuthorizations({
workspaceId: router.query.id,
});
setAuthorizations(projectAuthorizations);
const projectIntegrations = await getWorkspaceIntegrations({
workspaceId: router.query.id,
});
setProjectIntegrations(projectIntegrations);
const projectIntegrations = await getWorkspaceIntegrations({
workspaceId: router.query.id,
});
setProjectIntegrations(projectIntegrations);
try {
const integrationsData = await getIntegrations();
setIntegrations(integrationsData);
} catch (error) {
console.log("Error", error);
}
}, []);
try {
const integrationsData = await getIntegrations();
setIntegrations(integrationsData);
} catch (error) {
console.log("Error", error);
}
}, []);
return integrations ? (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Dashboard</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="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head>
<div className="flex flex-row">
<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="Project Integrations"
isProjectRelated={true}
/>
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">
Current Project Integrations
</p>
</div>
<p className="mr-4 text-base text-gray-400">
Manage your integrations of Infisical with
third-party services.
</p>
</div>
{projectIntegrations.length > 0 ? (
projectIntegrations.map((projectIntegration) => (
<Integration
key={guidGenerator()}
projectIntegration={projectIntegration}
/>
))
) : (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
<div className="mb-1">
You {"don't"} have any integrations set up
yet. When you do, they will appear here.
</div>
<div className="">
To start, click on any of the options below.
It takes 5 clicks to set up.
</div>
</div>
</div>
)}
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">
Available Integrations
</p>
</div>
<p className="mr-4 text-base text-gray-400">
Click on the itegration you want to connect. This
will let your environment variables flow
automatically into selected third-party services.
</p>
<p className="mr-4 text-xs text-gray-600 mt-1">
Note: during an integration with Heroku, for
security reasons, it is impossible to maintain
end-to-end encryption. In theory, this lets
Infisical decrypt yor environment variables. In
practice, we can assure you that this will never be
done, and it allows us to protect your secrets from
bad actors online. The core Infisical service will
always stay end-to-end encrypted. With any
questions, reach out support@infisical.com.
</p>
</div>
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
{Object.keys(integrations).map((integration) => (
<div
className={`relative ${
["Heroku"].includes(
integrations[integration].name
)
? ""
: "opacity-50"
}`}
key={integrations[integration].name}
>
<a
href={`${
["Heroku"].includes(
integrations[integration].name
)
? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}`
: "#"
}`}
rel="noopener"
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${
["Heroku"].includes(
integrations[integration].name
)
? "hover:bg-white/10 duration-200 cursor-pointer"
: "cursor-default grayscale"
}`}
>
<Image
src={`/images/integrations/${integrations[integration].name}.png`}
height={100}
width={100}
alt="integration logo"
></Image>
{integrations[integration].name.split(" ")
.length > 2 ? (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
<div>
{
integrations[
integration
].name.split(" ")[0]
}
</div>
<div className="text-base">
{
integrations[
integration
].name.split(" ")[1]
}{" "}
{
integrations[
integration
].name.split(" ")[2]
}
</div>
</div>
) : (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">
{integrations[integration].name}
</div>
)}
</a>
{["Heroku"].includes(
integrations[integration].name
) &&
authorizations
.map(
(authorization) =>
authorization.integration
)
.includes(
integrations[
integration
].name.toLowerCase()
) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div
onClick={() => {
deleteIntegrationAuth({
integrationAuthId:
authorizations
.filter(
(
authorization
) =>
authorization.integration ==
integrations[
integration
].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>
)}
{!["Heroku"].includes(
integrations[integration].name
) && (
<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>
</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>
<Image
src="/images/loading/loading.gif"
height={70}
width={120}
alt="loading animation"
></Image>
</div>
);
return integrations ? (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Dashboard</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="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head>
<div className="flex flex-row">
<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="Project Integrations" isProjectRelated={true} />
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">Current Project Integrations</p>
</div>
<p className="mr-4 text-base text-gray-400">
Manage your integrations of Infisical with third-party services.
</p>
</div>
{projectIntegrations.length > 0 ? (
projectIntegrations.map((projectIntegration) => (
<Integration
key={guidGenerator()}
projectIntegration={projectIntegration}
/>
))
) : (
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
<div className="mb-1">
You {"don't"} have any integrations set up yet. When you do,
they will appear here.
</div>
<div className="">
To start, click on any of the options below. It takes 5 clicks
to set up.
</div>
</div>
</div>
)}
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<p className="font-semibold mr-4">Available Integrations</p>
</div>
<p className="mr-4 text-base text-gray-400">
Click on the itegration you want to connect. This will let your
environment variables flow automatically into selected third-party
services.
</p>
<p className="mr-4 text-xs text-gray-600 mt-1">
Note: during an integration with Heroku, for security reasons, it
is impossible to maintain end-to-end encryption. In theory, this
lets Infisical decrypt yor environment variables. In practice, we
can assure you that this will never be done, and it allows us to
protect your secrets from bad actors online. The core Infisical
service will always stay end-to-end encrypted. With any questions,
reach out support@infisical.com.
</p>
</div>
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
{Object.keys(integrations).map((integration) => (
<div
className={`relative ${
["Heroku"].includes(integrations[integration].name)
? ""
: "opacity-50"
}`}
key={integrations[integration].name}
>
<a
href={`${
["Heroku"].includes(integrations[integration].name)
? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}`
: "#"
}`}
rel="noopener"
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${
["Heroku"].includes(integrations[integration].name)
? "hover:bg-white/10 duration-200 cursor-pointer"
: "cursor-default grayscale"
}`}
>
<Image
src={`/images/integrations/${integrations[integration].name}.png`}
height={100}
width={100}
alt="integration logo"
></Image>
{integrations[integration].name.split(" ").length > 2 ? (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
<div>{integrations[integration].name.split(" ")[0]}</div>
<div className="text-base">
{integrations[integration].name.split(" ")[1]}{" "}
{integrations[integration].name.split(" ")[2]}
</div>
</div>
) : (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">
{integrations[integration].name}
</div>
)}
</a>
{["Heroku"].includes(integrations[integration].name) &&
authorizations
.map((authorization) => authorization.integration)
.includes(integrations[integration].name.toLowerCase()) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div
onClick={() => {
deleteIntegrationAuth({
integrationAuthId: authorizations
.filter(
(authorization) =>
authorization.integration ==
integrations[integration].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>
)}
{!["Heroku"].includes(integrations[integration].name) && (
<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>
</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>
<Image
src="/images/loading/loading.gif"
height={70}
width={120}
alt="loading animation"
></Image>
</div>
);
}
Integrations.requireAuth = true;

@ -1,4 +1,4 @@
import React, { useEffect,useState } from "react";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
@ -14,130 +14,124 @@ import attemptLogin from "~/utilities/attemptLogin";
import getWorkspaces from "./api/workspace/getWorkspaces";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
useEffect(async () => {
let userWorkspace;
try {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
router.push("/dashboard/" + userWorkspace);
} catch (error) {
console.log("Error - Not logged in yet");
}
}, []);
useEffect(async () => {
let userWorkspace;
try {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
router.push("/dashboard/" + userWorkspace);
} catch (error) {
console.log("Error - Not logged in yet");
}
}, []);
/**
* This function check if the user entered the correct credentials and should be allowed to log in.
*/
const loginCheck = async () => {
setIsLoading(true);
await attemptLogin(
email,
password,
setErrorLogin,
router,
false,
true
).then(() => {
setTimeout(function () {
setIsLoading(false);
}, 2000);
});
};
/**
* This function check if the user entered the correct credentials and should be allowed to log in.
*/
const loginCheck = async () => {
setIsLoading(true);
await attemptLogin(
email,
password,
setErrorLogin,
router,
false,
true
).then(() => {
setTimeout(function () {
setIsLoading(false);
}, 2000);
});
};
return (
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
<Head>
<title>Login</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta property="og:title" content="Log In to Infisical" />
<meta
name="og:description"
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head>
<Link href="/">
<div className="flex justify-center mb-8 mt-20 cursor-pointer">
<Image
src="/images/biglogo.png"
height={90}
width={120}
alt="long logo"
/>
</div>
</Link>
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<p className="text-4xl flex justify-center font-semibold text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Log In
</p>
<div className="flex flex-row items-center justify-center">
<p className="text-md flex justify-center mt-2 text-gray-400">
Need an Infisical account?
</p>
</div>
<div className="flex flex-col items-center justify-center w-full md:pb-4 max-h-24 max-w-md mx-auto">
<Link href="/signup">
<button className="w-full pb-3 hover:opacity-90 duration-200">
<u className="font-normal text-md text-sky-500">
Create an account
</u>
</button>
</Link>
</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">
<InputField
label="Email"
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
/>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
<InputField
label="Password"
onChangeHandler={setPassword}
type="password"
value={password}
placeholder=""
isRequired
/>
</div>
{errorLogin && (
<Error text="Your email and/or password are wrong." />
)}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
text="Log In"
onButtonPressed={loginCheck}
loading={isLoading}
size="lg"
/>
</div>
</div>
{/* <div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
return (
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
<Head>
<title>Login</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta property="og:title" content="Log In to Infisical" />
<meta
name="og:description"
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
/>
</Head>
<Link href="/">
<div className="flex justify-center mb-8 mt-20 cursor-pointer">
<Image
src="/images/biglogo.png"
height={90}
width={120}
alt="long logo"
/>
</div>
</Link>
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<p className="text-4xl flex justify-center font-semibold text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
Log In
</p>
<div className="flex flex-row items-center justify-center">
<p className="text-md flex justify-center mt-2 text-gray-400">
Need an Infisical account?
</p>
</div>
<div className="flex flex-col items-center justify-center w-full md:pb-4 max-h-24 max-w-md mx-auto">
<Link href="/signup">
<button className="w-full pb-3 hover:opacity-90 duration-200">
<u className="font-normal text-md text-sky-500">
Create an account
</u>
</button>
</Link>
</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">
<InputField
label="Email"
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
/>
</div>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
<InputField
label="Password"
onChangeHandler={setPassword}
type="password"
value={password}
placeholder=""
isRequired
/>
</div>
{errorLogin && <Error text="Your email and/or password are wrong." />}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
text="Log In"
onButtonPressed={loginCheck}
loading={isLoading}
size="lg"
/>
</div>
</div>
{/* <div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
<p className="text-gray-400">I may have <Link href="/login"><u className="text-sky-500 cursor-pointer">forgotten my password.</u></Link></p>
</div> */}
</div>
{false && (
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
<FontAwesomeIcon
icon={faWarning}
className="ml-2 mr-6 text-6xl"
/>
We are experiencing minor technical difficulties. We are
working on solving it right now. Please come back in a few
minutes.
</div>
)}
</div>
);
</div>
{false && (
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
We are experiencing minor technical difficulties. We are working on
solving it right now. Please come back in a few minutes.
</div>
)}
</div>
);
}

@ -3,18 +3,22 @@ import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
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">
<FontAwesomeIcon icon={faFolderOpen} className="text-7xl mb-8 w-full px-auto" />
<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>
);
return (
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
<FontAwesomeIcon
icon={faFolderOpen}
className="text-7xl mb-8 w-full px-auto"
/>
<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>
);
}
NoProjects.requireAuth = true;

@ -2,21 +2,21 @@ import React from "react";
import Head from "next/head";
export default function Activity() {
return (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
<Head>
<title>Request a New Invite</title>
<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-6xl">Oops.</p>
<p className="mt-2 mb-1 text-xl">Your invite has expired. </p>
<p className="text-xl">Ask the administrator for a new one.</p>
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
Note: If it still {"doesn't work"}, please reach out to us
at support@infisical.com
</p>
</div>
</div>
);
return (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
<Head>
<title>Request a New Invite</title>
<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-6xl">Oops.</p>
<p className="mt-2 mb-1 text-xl">Your invite has expired. </p>
<p className="text-xl">Ask the administrator for a new one.</p>
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
Note: If it still {"doesn't work"}, please reach out to us at
support@infisical.com
</p>
</div>
</div>
);
}

@ -1,4 +1,4 @@
import React, { useEffect,useState } from "react";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Plan from "~/components/billing/Plan";
@ -9,102 +9,101 @@ import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscript
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
export default function SettingsBilling() {
let [currentPlan, setCurrentPlan] = useState("");
let [numUsers, setNumUsers] = useState("");
let [currentPlan, setCurrentPlan] = useState("");
let [numUsers, setNumUsers] = useState("");
const plans = [
{
key: 1,
name: "Starter",
price: "Free",
priceExplanation: "up to 5 team members",
text: "Manage any project with 5 members for free!",
subtext: "$5 per member/month afterwards.",
buttonTextMain: "Downgrade",
buttonTextSecondary: "Learn More",
current: currentPlan == STRIPE_PRODUCT_STARTER,
},
{
key: 2,
name: "Professional",
price: "$9",
priceExplanation: "/member/month",
subtext: "Includes unlimited projects & members.",
text: "Keep up with key management as you grow.",
buttonTextMain: "Upgrade",
buttonTextSecondary: "Learn More",
current: currentPlan == STRIPE_PRODUCT_PRO,
},
{
key: 3,
name: "Enterprise",
price: "Custom Pricing",
text: "More control for advanced organizations.",
buttonTextMain: "Schedule a Demo",
buttonTextSecondary: "Learn More",
current: false,
},
];
const plans = [
{
key: 1,
name: "Starter",
price: "Free",
priceExplanation: "up to 5 team members",
text: "Manage any project with 5 members for free!",
subtext: "$5 per member/month afterwards.",
buttonTextMain: "Downgrade",
buttonTextSecondary: "Learn More",
current: currentPlan == STRIPE_PRODUCT_STARTER,
},
{
key: 2,
name: "Professional",
price: "$9",
priceExplanation: "/member/month",
subtext: "Includes unlimited projects & members.",
text: "Keep up with key management as you grow.",
buttonTextMain: "Upgrade",
buttonTextSecondary: "Learn More",
current: currentPlan == STRIPE_PRODUCT_PRO,
},
{
key: 3,
name: "Enterprise",
price: "Custom Pricing",
text: "More control for advanced organizations.",
buttonTextMain: "Schedule a Demo",
buttonTextSecondary: "Learn More",
current: false,
},
];
useEffect(async () => {
const subscriptions = await getOrganizationSubscriptions({
orgId: localStorage.getItem("orgData.id"),
});
setCurrentPlan(subscriptions.data[0].plan.product);
const orgUsers = await getOrganizationUsers({
orgId: localStorage.getItem("orgData.id"),
});
setNumUsers(orgUsers.length);
}, []);
useEffect(async () => {
const subscriptions = await getOrganizationSubscriptions({
orgId: localStorage.getItem("orgData.id"),
});
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Settings - Billing</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="Usage & Billing" />
<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">
Usage & Billing
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your {"organization's"}{" "}
subscription here.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<p className="text-xl font-semibold">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">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">
setCurrentPlan(subscriptions.data[0].plan.product);
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>Settings - Billing</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="Usage & Billing" />
<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">
Usage & Billing
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your {"organization's"} subscription here.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<p className="text-xl font-semibold">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">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>
);
</div>
</div>
</div>
</div>
</div>
);
}
SettingsBilling.requireAuth = true;

@ -1,10 +1,10 @@
import React, { useEffect,useState } from "react";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import {
faMagnifyingGlass,
faPlus,
faX,
faMagnifyingGlass,
faPlus,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -29,340 +29,313 @@ import deleteWorkspace from "../../api/workspace/deleteWorkspace";
import getWorkspaces from "../../api/workspace/getWorkspaces";
export default function SettingsOrg() {
const [buttonReady, setButtonReady] = useState(false);
const router = useRouter();
const [orgName, setOrgName] = useState("");
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 [searchIncidentContact, setSearchIncidentContact] = useState("");
const [userList, setUserList] = useState();
const [personalEmail, setPersonalEmail] = useState("");
let workspaceIdTemp;
const [email, setEmail] = useState("");
const [currentPlan, setCurrentPlan] = useState("");
const [buttonReady, setButtonReady] = useState(false);
const router = useRouter();
const [orgName, setOrgName] = useState("");
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 [searchIncidentContact, setSearchIncidentContact] = useState("");
const [userList, setUserList] = useState();
const [personalEmail, setPersonalEmail] = useState("");
let workspaceIdTemp;
const [email, setEmail] = useState("");
const [currentPlan, setCurrentPlan] = useState("");
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 () => {
let org = await getOrganization({
orgId: localStorage.getItem("orgData.id"),
});
let orgData = org;
setOrgName(orgData.name);
let incidentContactsData = await getIncidentContacts(
localStorage.getItem("orgData.id")
);
setIncidentContacts(
incidentContactsData?.map((contact) => contact.email)
);
setIncidentContacts(incidentContactsData?.map((contact) => contact.email));
const user = await getUser();
setPersonalEmail(user.email);
const user = await getUser();
setPersonalEmail(user.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);
}, []);
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 modifyOrgName = (newName) => {
setButtonReady(true);
setOrgName(newName);
};
const modifyOrgName = (newName) => {
setButtonReady(true);
setOrgName(newName);
};
const submitChanges = (newOrgName) => {
renameOrg(localStorage.getItem("orgData.id"), newOrgName);
setButtonReady(false);
};
const submitChanges = (newOrgName) => {
renameOrg(localStorage.getItem("orgData.id"), newOrgName);
setButtonReady(false);
};
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
function closeAddUserModal() {
setIsAddUserOpen(false);
}
function closeAddUserModal() {
setIsAddUserOpen(false);
}
function closeAddIncidentContactModal() {
setIsAddIncidentContactOpen(false);
}
function closeAddIncidentContactModal() {
setIsAddIncidentContactOpen(false);
}
function openAddUserModal() {
setIsAddUserOpen(true);
}
function openAddUserModal() {
setIsAddUserOpen(true);
}
function openAddIncidentContactModal() {
setIsAddIncidentContactOpen(true);
}
function openAddIncidentContactModal() {
setIsAddIncidentContactOpen(true);
}
async function submitAddUserModal(email) {
await addUserToOrg(email, localStorage.getItem("orgData.id"));
setEmail("");
setIsAddUserOpen(false);
router.reload();
}
async function submitAddUserModal(email) {
await addUserToOrg(email, localStorage.getItem("orgData.id"));
setEmail("");
setIsAddUserOpen(false);
router.reload();
}
const deleteIncidentContactFully = (incidentContact) => {
setIncidentContacts(
incidentContacts.filter((contact) => contact != incidentContact)
);
deleteIncidentContact(
localStorage.getItem("orgData.id"),
incidentContact
);
};
const deleteIncidentContactFully = (incidentContact) => {
setIncidentContacts(
incidentContacts.filter((contact) => contact != incidentContact)
);
deleteIncidentContact(localStorage.getItem("orgData.id"), incidentContact);
};
/**
* This function deleted a workspace.
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
* It then deletes the workspace and forwards the user to another aviable workspace.
*/
const executeDeletingWorkspace = async () => {
let userWorkspaces = await getWorkspaces();
/**
* This function deleted a workspace.
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
* It then deletes the workspace and forwards the user to another aviable workspace.
*/
const executeDeletingWorkspace = async () => {
let userWorkspaces = await getWorkspaces();
if (userWorkspaces.length > 1) {
if (
userWorkspaces.filter(
(workspace) => workspace._id == router.query.id
)[0].name == workspaceToBeDeletedName
) {
await deleteWorkspace(router.query.id);
let userWorkspaces = await getWorkspaces();
router.push("/dashboard/" + userWorkspaces[0]._id);
}
}
};
if (userWorkspaces.length > 1) {
if (
userWorkspaces.filter(
(workspace) => workspace._id == router.query.id
)[0].name == workspaceToBeDeletedName
) {
await deleteWorkspace(router.query.id);
let 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">
<Head>
<title>Settings</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="Organization Settings" />
<AddIncidentContactDialog
isOpen={isAddIncidentContactOpen}
closeModal={closeAddIncidentContactModal}
workspaceId={workspaceId}
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">
Organization Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your organization here.
</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">
Display Name
</p>
<InputField
// label="Organization Name"
onChangeHandler={modifyOrgName}
type="varName"
value={orgName}
placeholder=""
isRequired
/>
</div>
<div className="flex justify-start w-full">
<div
className={`flex justify-start max-w-sm mt-4 mb-2`}
>
<Button
text="Save Changes"
onButtonPressed={() =>
submitChanges(orgName)
}
color="mineshaft"
size="md"
active={buttonReady}
iconDisabled={faCheck}
textDisabled="Saved"
/>
</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">
Organization Members
</p>
<p className="mr-4 text-gray-400 mt-2 mb-2">
Manage members of your organization. These users
could afterwards be formed into projects.
</p>
<AddUserDialog
isOpen={isAddUserOpen}
closeModal={closeAddUserModal}
submitModal={submitAddUserModal}
email={emailUser}
workspaceId={workspaceId}
setEmail={setEmailUser}
currentPlan={currentPlan}
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">
<FontAwesomeIcon
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"
value={searchUsers}
onChange={(e) =>
setSearchUsers(e.target.value)
}
placeholder={"Search members..."}
/>
</div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
<Button
text="Add Member"
onButtonPressed={openAddUserModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
</div>
</div>
{userList && (
<div className="overflow-y-auto max-w-6xl w-full">
<UserTable
userData={userList}
changeData={setUserList}
myUser={personalEmail}
filter={searchUsers.toLowerCase()}
resendInvite={submitAddUserModal}
isOrg={true}
// onClick={openDeleteModal}
// deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted}
className="w-full"
/>
</div>
)}
</div>
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Settings</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="Organization Settings" />
<AddIncidentContactDialog
isOpen={isAddIncidentContactOpen}
closeModal={closeAddIncidentContactModal}
workspaceId={workspaceId}
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">
Organization Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your organization here.
</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">
Display Name
</p>
<InputField
// label="Organization Name"
onChangeHandler={modifyOrgName}
type="varName"
value={orgName}
placeholder=""
isRequired
/>
</div>
<div className="flex justify-start w-full">
<div className={`flex justify-start max-w-sm mt-4 mb-2`}>
<Button
text="Save Changes"
onButtonPressed={() => submitChanges(orgName)}
color="mineshaft"
size="md"
active={buttonReady}
iconDisabled={faCheck}
textDisabled="Saved"
/>
</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">
Organization Members
</p>
<p className="mr-4 text-gray-400 mt-2 mb-2">
Manage members of your organization. These users could
afterwards be formed into projects.
</p>
<AddUserDialog
isOpen={isAddUserOpen}
closeModal={closeAddUserModal}
submitModal={submitAddUserModal}
email={emailUser}
workspaceId={workspaceId}
setEmail={setEmailUser}
currentPlan={currentPlan}
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">
<FontAwesomeIcon
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"
value={searchUsers}
onChange={(e) => setSearchUsers(e.target.value)}
placeholder={"Search members..."}
/>
</div>
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
<Button
text="Add Member"
onButtonPressed={openAddUserModal}
color="mineshaft"
size="md"
icon={faPlus}
/>
</div>
</div>
{userList && (
<div className="overflow-y-auto max-w-6xl w-full">
<UserTable
userData={userList}
changeData={setUserList}
myUser={personalEmail}
filter={searchUsers.toLowerCase()}
resendInvite={submitAddUserModal}
isOrg={true}
// onClick={openDeleteModal}
// deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted}
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">
Incident Contacts
</p>
<p className="text-xs text-gray-500 mb-2 min-w-max">
These contacts will be notified in the
unlikely event of a severe incident.
</p>
</div>
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
<Button
text="Add Contact"
onButtonPressed={
openAddIncidentContactModal
}
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">
<FontAwesomeIcon
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"
value={searchIncidentContact}
onChange={(e) =>
setSearchIncidentContact(e.target.value)
}
placeholder={"Search..."}
/>
</div>
{incidentContacts?.filter((email) =>
email.includes(searchIncidentContact)
).length > 0 ? (
incidentContacts
.filter((email) =>
email.includes(searchIncidentContact)
)
.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"
>
<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"
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">
No incident contacts found.
</p>
</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">
Incident Contacts
</p>
<p className="text-xs text-gray-500 mb-2 min-w-max">
These contacts will be notified in the unlikely event of a
severe incident.
</p>
</div>
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
<Button
text="Add Contact"
onButtonPressed={openAddIncidentContactModal}
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">
<FontAwesomeIcon
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"
value={searchIncidentContact}
onChange={(e) => setSearchIncidentContact(e.target.value)}
placeholder={"Search..."}
/>
</div>
{incidentContacts?.filter((email) =>
email.includes(searchIncidentContact)
).length > 0 ? (
incidentContacts
.filter((email) => email.includes(searchIncidentContact))
.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"
>
<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"
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">No incident contacts found.</p>
</div>
)}
</div>
{/* <div className="border-l border-red pb-4 pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pt-2 max-w-6xl">
{/* <div className="border-l border-red pb-4 pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pt-2 max-w-6xl">
<p className="text-xl font-bold text-red">
Danger Zone
</p>
@ -397,11 +370,11 @@ export default function SettingsOrg() {
have more than one.
</p>
</div> */}
</div>
</div>
</div>
</div>
);
</div>
</div>
</div>
</div>
);
}
SettingsOrg.requireAuth = true;

@ -1,4 +1,4 @@
import React, { useEffect,useState } from "react";
import React, { useEffect, useState } from "react";
import Head from "next/head";
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -7,57 +7,54 @@ import Button from "~/components/basic/buttons/Button";
import InputField from "~/components/basic/InputField";
import NavHeader from "~/components/navigation/NavHeader";
import changePassword from "~/components/utilities/cryptography/changePassword";
import passwordCheck from "~/utilities/checks/PasswordCheck";
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
import passwordCheck from "~/utilities/checks/PasswordCheck";
import getUser from "../../api/user/getUser";
export default function PersonalSettings() {
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 [passwordChanged, setPasswordChanged] = useState(false);
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
const [backupKeyError, setBackupKeyError] = useState(false);
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 [passwordChanged, setPasswordChanged] = useState(false);
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
const [backupKeyError, setBackupKeyError] = useState(false);
useEffect(async () => {
let user = await getUser();
setPersonalEmail(user.email);
setPersonalName(user.firstName + " " + user.lastName);
}, []);
useEffect(async () => {
let user = await getUser();
setPersonalEmail(user.email);
setPersonalName(user.firstName + " " + user.lastName);
}, []);
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Personal Settings</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="Personal Settings"
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">
Personal Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your personal information here.
</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="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Personal Settings</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="Personal Settings" 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">
Personal Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
View and manage your personal information here.
</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="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">
Display Name
@ -101,235 +98,223 @@ export default function PersonalSettings() {
</div>
</div>
</div> */}
</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">
Change password
</p>
</div>
</div>
<div className="max-w-xl w-full">
<InputField
label="Current Password"
onChangeHandler={(password) => {
setCurrentPassword(password);
}}
type="password"
value={currentPassword}
isRequired
error={currentPasswordError}
errorText="The current password may be wrong"
/>
<div className="py-2"></div>
<InputField
label="New Password"
onChangeHandler={(password) => {
setNewPassword(password);
passwordCheck(
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
false
);
}}
type="password"
value={newPassword}
isRequired
error={
passwordErrorLength &&
passwordErrorLowerCase &&
passwordErrorNumber
}
/>
</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={`text-gray-400 text-sm mb-1`}
>
Password should contain at least:
</div>
<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={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLength
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
14 characters
</div>
</div>
<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={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
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">
{passwordErrorNumber ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorNumber
? "text-gray-400"
: "text-gray-600"
} text-sm`}
>
1 number
</div>
</div>
</div>
) : (
<div className="py-2"></div>
)}
<div className="flex flex-row items-center mt-3 w-52 pr-3">
<Button
text="Change Password"
onButtonPressed={() => {
if (
!passwordErrorLength &&
!passwordErrorLowerCase &&
!passwordErrorNumber
) {
changePassword(
personalEmail,
currentPassword,
newPassword,
setCurrentPasswordError,
setPasswordChanged,
setCurrentPassword,
setNewPassword
);
}
}}
color="mineshaft"
size="md"
active={
newPassword != "" &&
currentPassword != "" &&
!(
passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber
)
}
textDisabled="Change Password"
/>
<FontAwesomeIcon
icon={faCheck}
className={`ml-4 text-primary text-3xl ${
passwordChanged
? "opacity-100"
: "opacity-0"
} duration-300`}
/>
</div>
</div>
</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">
Change password
</p>
</div>
</div>
<div className="max-w-xl w-full">
<InputField
label="Current Password"
onChangeHandler={(password) => {
setCurrentPassword(password);
}}
type="password"
value={currentPassword}
isRequired
error={currentPasswordError}
errorText="The current password may be wrong"
/>
<div className="py-2"></div>
<InputField
label="New Password"
onChangeHandler={(password) => {
setNewPassword(password);
passwordCheck(
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
false
);
}}
type="password"
value={newPassword}
isRequired
error={
passwordErrorLength &&
passwordErrorLowerCase &&
passwordErrorNumber
}
/>
</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={`text-gray-400 text-sm mb-1`}>
Password should contain at least:
</div>
<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={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorLength ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
14 characters
</div>
</div>
<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={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
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">
{passwordErrorNumber ? (
<FontAwesomeIcon
icon={faX}
className="text-md text-red mr-2.5"
/>
) : (
<FontAwesomeIcon
icon={faCheck}
className="text-md text-primary mr-2"
/>
)}
<div
className={`${
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
1 number
</div>
</div>
</div>
) : (
<div className="py-2"></div>
)}
<div className="flex flex-row items-center mt-3 w-52 pr-3">
<Button
text="Change Password"
onButtonPressed={() => {
if (
!passwordErrorLength &&
!passwordErrorLowerCase &&
!passwordErrorNumber
) {
changePassword(
personalEmail,
currentPassword,
newPassword,
setCurrentPasswordError,
setPasswordChanged,
setCurrentPassword,
setNewPassword
);
}
}}
color="mineshaft"
size="md"
active={
newPassword != "" &&
currentPassword != "" &&
!(
passwordErrorLength ||
passwordErrorLowerCase ||
passwordErrorNumber
)
}
textDisabled="Change Password"
/>
<FontAwesomeIcon
icon={faCheck}
className={`ml-4 text-primary text-3xl ${
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">
Emergency Kit
</p>
<p className="text-sm text-mineshaft-300 min-w-max">
Your Emergency Kit contains the
information youll need to sign in to
your Infisical account.
</p>
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
Only the latest issued Emergency Kit
remains valid. To get a new Emergency
Kit, verify your password.
</p>
</div>
</div>
<div className="w-full max-w-xl mb-4">
<InputField
label="Current Password"
onChangeHandler={setBackupPassword}
type="password"
value={backupPassword}
isRequired
error={backupKeyError}
errorText="The current password is wrong"
/>
</div>
<div className="flex flex-row items-center mt-3 w-full w-60">
<Button
text="Download Emergency Kit"
onButtonPressed={() => {
issueBackupKey({
email: personalEmail,
password: backupPassword,
personalName,
setBackupKeyError,
setBackupKeyIssued,
});
}}
color="mineshaft"
size="md"
active={backupPassword != ""}
textDisabled="Download Emergency Kit"
/>
<FontAwesomeIcon
icon={faCheck}
className={`ml-4 text-primary text-3xl ${
backupKeyIssued
? "opacity-100"
: "opacity-0"
} duration-300`}
/>
</div>
</div>
</div>
</div>
</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">
Emergency Kit
</p>
<p className="text-sm text-mineshaft-300 min-w-max">
Your Emergency Kit contains the information youll need to
sign in to your Infisical account.
</p>
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
Only the latest issued Emergency Kit remains valid. To get a
new Emergency Kit, verify your password.
</p>
</div>
</div>
<div className="w-full max-w-xl mb-4">
<InputField
label="Current Password"
onChangeHandler={setBackupPassword}
type="password"
value={backupPassword}
isRequired
error={backupKeyError}
errorText="The current password is wrong"
/>
</div>
<div className="flex flex-row items-center mt-3 w-full w-60">
<Button
text="Download Emergency Kit"
onButtonPressed={() => {
issueBackupKey({
email: personalEmail,
password: backupPassword,
personalName,
setBackupKeyError,
setBackupKeyIssued,
});
}}
color="mineshaft"
size="md"
active={backupPassword != ""}
textDisabled="Download Emergency Kit"
/>
<FontAwesomeIcon
icon={faCheck}
className={`ml-4 text-primary text-3xl ${
backupKeyIssued ? "opacity-100" : "opacity-0"
} duration-300`}
/>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
PersonalSettings.requireAuth = true;

@ -1,4 +1,4 @@
import React, { useEffect,useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
@ -15,212 +15,191 @@ import getWorkspaces from "../../api/workspace/getWorkspaces";
import renameWorkspace from "../../api/workspace/renameWorkspace";
export default function SettingsBasic() {
const [buttonReady, setButtonReady] = useState(false);
const router = useRouter();
const [workspaceName, setWorkspaceName] = useState("");
const [serviceTokens, setServiceTokens] = useState([]);
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] =
useState("");
const [workspaceId, setWorkspaceId] = useState("");
const [isAddOpen, setIsAddOpen] = useState(false);
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] =
useState(false);
const [buttonReady, setButtonReady] = useState(false);
const router = useRouter();
const [workspaceName, setWorkspaceName] = useState("");
const [serviceTokens, setServiceTokens] = useState([]);
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState("");
const [workspaceId, setWorkspaceId] = useState("");
const [isAddOpen, setIsAddOpen] = useState(false);
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] =
useState(false);
useEffect(async () => {
let userWorkspaces = await getWorkspaces();
userWorkspaces.map((userWorkspace) => {
if (userWorkspace._id == router.query.id) {
setWorkspaceName(userWorkspace.name);
}
});
let tempServiceTokens = await getServiceTokens({
workspaceId: router.query.id,
});
setServiceTokens(tempServiceTokens);
}, []);
useEffect(async () => {
let userWorkspaces = await getWorkspaces();
userWorkspaces.map((userWorkspace) => {
if (userWorkspace._id == router.query.id) {
setWorkspaceName(userWorkspace.name);
}
});
let tempServiceTokens = await getServiceTokens({
workspaceId: router.query.id,
});
setServiceTokens(tempServiceTokens);
}, []);
const modifyWorkspaceName = (newName) => {
setButtonReady(true);
setWorkspaceName(newName);
};
const modifyWorkspaceName = (newName) => {
setButtonReady(true);
setWorkspaceName(newName);
};
const submitChanges = (newWorkspaceName) => {
renameWorkspace(router.query.id, newWorkspaceName);
setButtonReady(false);
};
const submitChanges = (newWorkspaceName) => {
renameWorkspace(router.query.id, newWorkspaceName);
setButtonReady(false);
};
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
useEffect(async () => {
setWorkspaceId(router.query.id);
}, []);
function closeAddModal() {
setIsAddOpen(false);
}
function closeAddModal() {
setIsAddOpen(false);
}
function openAddModal() {
setIsAddOpen(true);
}
function openAddModal() {
setIsAddOpen(true);
}
const closeAddServiceTokenModal = () => {
setIsAddServiceTokenDialogOpen(false);
};
const closeAddServiceTokenModal = () => {
setIsAddServiceTokenDialogOpen(false);
};
/**
* This function deleted a workspace.
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
* It then deletes the workspace and forwards the user to another aviable workspace.
*/
const executeDeletingWorkspace = async () => {
let userWorkspaces = await getWorkspaces();
/**
* This function deleted a workspace.
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
* It then deletes the workspace and forwards the user to another aviable workspace.
*/
const executeDeletingWorkspace = async () => {
let userWorkspaces = await getWorkspaces();
if (userWorkspaces.length > 1) {
if (
userWorkspaces.filter(
(workspace) => workspace._id == router.query.id
)[0].name == workspaceToBeDeletedName
) {
await deleteWorkspace(router.query.id);
let userWorkspaces = await getWorkspaces();
router.push("/dashboard/" + userWorkspaces[0]._id);
}
}
};
if (userWorkspaces.length > 1) {
if (
userWorkspaces.filter(
(workspace) => workspace._id == router.query.id
)[0].name == workspaceToBeDeletedName
) {
await deleteWorkspace(router.query.id);
let 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">
<Head>
<title>Settings</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<AddServiceTokenDialog
isOpen={isAddServiceTokenDialogOpen}
workspaceId={router.query.id}
closeModal={closeAddServiceTokenModal}
workspaceName={workspaceName}
/>
<div className="flex flex-row mr-6 max-w-5xl">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader
pageName="Project Settings"
isProjectRelated={true}
/>
<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">
Project Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
These settings only apply to the currently
selected Project.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<div className="flex flex-col">
<div className="min-w-md mt-2 flex flex-col items-start">
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
<p className="text-xl font-semibold mb-4">
Display Name
</p>
<div className="max-h-28 w-full max-w-md mr-auto">
<InputField
onChangeHandler={
modifyWorkspaceName
}
type="varName"
value={workspaceName}
placeholder=""
isRequired
/>
</div>
<div className="flex justify-start w-full">
<div
className={`flex justify-start max-w-sm mt-4 mb-2`}
>
<Button
text="Save Changes"
onButtonPressed={() =>
submitChanges(workspaceName)
}
color="mineshaft"
size="md"
active={buttonReady}
iconDisabled={faCheck}
textDisabled="Saved"
/>
</div>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
<p className="text-xl font-semibold self-start">
Project ID
</p>
<p className="text-base text-gray-400 font-normal self-start mt-4">
To integrate Infisical into your code
base and get automatic injection of
environmental variables, you should use
the following Project ID.
</p>
<p className="text-base text-gray-400 font-normal self-start">
For more guidance, including code
snipets for various languages and
frameworks, see{" "}
{/* eslint-disable-next-line react/jsx-no-target-blank */}
<a
href="https://infisical.com/docs/getting-started/introduction"
target="_blank"
rel="noopener"
className="text-primary hover:opacity-80 duration-200"
>
Infisical Docs.
</a>
</p>
<div className="max-h-28 w-ful">
<InputField
type="varName"
value={router.query.id}
placeholder=""
isRequired
static
text="This is your project's auto-generated unique identifier. It can't be changed."
/>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
<div className="flex flex-row justify-between w-full">
<div className="flex flex-col w-full">
<p className="text-xl font-semibold mb-3">
Service Tokens
</p>
<p className="text-base text-gray-400 mb-4">
Every service token is specific
to you, a certain project and a
certain environment within this
project.
</p>
</div>
<div className="w-48">
<Button
text="Add New Token"
onButtonPressed={() => {
setIsAddServiceTokenDialogOpen(
true
);
}}
color="mineshaft"
icon={faPlus}
size="md"
/>
</div>
</div>
<ServiceTokenTable
data={serviceTokens}
workspaceName={workspaceName}
/>
</div>
return (
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
<Head>
<title>Settings</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<AddServiceTokenDialog
isOpen={isAddServiceTokenDialogOpen}
workspaceId={router.query.id}
closeModal={closeAddServiceTokenModal}
workspaceName={workspaceName}
/>
<div className="flex flex-row mr-6 max-w-5xl">
<div className="w-full max-h-screen pb-2 overflow-y-auto">
<NavHeader pageName="Project Settings" isProjectRelated={true} />
<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">
Project Settings
</p>
<p className="font-normal mr-4 text-gray-400 text-base">
These settings only apply to the currently selected Project.
</p>
</div>
</div>
<div className="flex flex-col ml-6 text-mineshaft-50">
<div className="flex flex-col">
<div className="min-w-md mt-2 flex flex-col items-start">
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
<p className="text-xl font-semibold mb-4">Display Name</p>
<div className="max-h-28 w-full max-w-md mr-auto">
<InputField
onChangeHandler={modifyWorkspaceName}
type="varName"
value={workspaceName}
placeholder=""
isRequired
/>
</div>
<div className="flex justify-start w-full">
<div className={`flex justify-start max-w-sm mt-4 mb-2`}>
<Button
text="Save Changes"
onButtonPressed={() => submitChanges(workspaceName)}
color="mineshaft"
size="md"
active={buttonReady}
iconDisabled={faCheck}
textDisabled="Saved"
/>
</div>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
<p className="text-xl font-semibold self-start">Project ID</p>
<p className="text-base text-gray-400 font-normal self-start mt-4">
To integrate Infisical into your code base and get automatic
injection of environmental variables, you should use the
following Project ID.
</p>
<p className="text-base text-gray-400 font-normal self-start">
For more guidance, including code snipets for various
languages and frameworks, see{" "}
{/* eslint-disable-next-line react/jsx-no-target-blank */}
<a
href="https://infisical.com/docs/getting-started/introduction"
target="_blank"
rel="noopener"
className="text-primary hover:opacity-80 duration-200"
>
Infisical Docs.
</a>
</p>
<div className="max-h-28 w-ful">
<InputField
type="varName"
value={router.query.id}
placeholder=""
isRequired
static
text="This is your project's auto-generated unique identifier. It can't be changed."
/>
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
<div className="flex flex-row justify-between w-full">
<div className="flex flex-col w-full">
<p className="text-xl font-semibold mb-3">
Service Tokens
</p>
<p className="text-base text-gray-400 mb-4">
Every service token is specific to you, a certain
project and a certain environment within this project.
</p>
</div>
<div className="w-48">
<Button
text="Add New Token"
onButtonPressed={() => {
setIsAddServiceTokenDialogOpen(true);
}}
color="mineshaft"
icon={faPlus}
size="md"
/>
</div>
</div>
<ServiceTokenTable
data={serviceTokens}
workspaceName={workspaceName}
/>
</div>
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">
<p className="text-xl font-semibold self-start">
Project Environments
</p>
@ -255,47 +234,42 @@ export default function SettingsBasic() {
</div>
</div>
</div> */}
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
<p className="text-xl font-bold text-red">
Danger Zone
</p>
<p className="mt-2 text-md text-gray-400">
As soon as you delete this project, you will not
be able to undo it. This will immediately remove
all the keys. If you still want to do that,
please enter the name of the project below.
</p>
<div className="max-h-28 w-full max-w-md mr-auto mt-4">
<InputField
label="Project to be Deleted"
onChangeHandler={
setWorkspaceToBeDeletedName
}
type="varName"
value={workspaceToBeDeletedName}
placeholder=""
isRequired
/>
</div>
<button
type="button"
className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={executeDeletingWorkspace}
>
Delete Project
</button>
<p className="mt-0.5 ml-1 text-xs text-gray-500">
Note: You can only delete a project in case you
have more than one.
</p>
</div>
</div>
</div>
</div>
</div>
);
</div>
</div>
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
<p className="text-xl font-bold text-red">Danger Zone</p>
<p className="mt-2 text-md text-gray-400">
As soon as you delete this project, you will not be able to undo
it. This will immediately remove all the keys. If you still want
to do that, please enter the name of the project below.
</p>
<div className="max-h-28 w-full max-w-md mr-auto mt-4">
<InputField
label="Project to be Deleted"
onChangeHandler={setWorkspaceToBeDeletedName}
type="varName"
value={workspaceToBeDeletedName}
placeholder=""
isRequired
/>
</div>
<button
type="button"
className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={executeDeletingWorkspace}
>
Delete Project
</button>
<p className="mt-0.5 ml-1 text-xs text-gray-500">
Note: You can only delete a project in case you have more than
one.
</p>
</div>
</div>
</div>
</div>
</div>
);
}
SettingsBasic.requireAuth = true;

File diff suppressed because it is too large Load Diff

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

Loading…
Cancel
Save