Fix: Format entire frontend properly

This commit is contained in:
Daniel Hougaard
2024-03-18 15:59:47 +01:00
parent b7b43858f6
commit 1ede551c3e
38 changed files with 746 additions and 723 deletions

View File

@ -6,7 +6,7 @@ import { ENV, POSTHOG_API_KEY, POSTHOG_HOST } from "../utilities/config";
export const initPostHog = () => {
// @ts-ignore
console.log("Hi there 👋")
console.log("Hi there 👋");
try {
if (typeof window !== "undefined") {
// @ts-ignore
@ -19,7 +19,7 @@ export const initPostHog = () => {
return posthog;
} catch (e) {
console.log("posthog err", e)
console.log("posthog err", e);
}
return undefined;

View File

@ -3,9 +3,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const Error = ({ text }: { text: string }): JSX.Element => {
return (
<div className="relative flex flex-row justify-center m-auto items-center w-fit rounded-full">
<FontAwesomeIcon icon={faExclamationTriangle} className="text-red mt-1.5 mb-2 mx-2" />
{text && <p className="relative top-0 text-red mr-2 text-sm py-1">{text}</p>}
<div className="relative m-auto flex w-fit flex-row items-center justify-center rounded-full">
<FontAwesomeIcon icon={faExclamationTriangle} className="mx-2 mt-1.5 mb-2 text-red" />
{text && <p className="relative top-0 mr-2 py-1 text-sm text-red">{text}</p>}
</div>
);
};

View File

@ -39,16 +39,16 @@ const InputField = ({
if (isStatic === 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">{label}</p>
{text && <p className="text-xs text-gray-400 mb-2">{text}</p>}
<div className="my-2 flex w-full max-w-md flex-col justify-center md:my-4">
<p className="mb-0.5 text-sm font-semibold text-gray-400">{label}</p>
{text && <p className="mb-2 text-xs text-gray-400">{text}</p>}
<input
onChange={(e) => onChangeHandler(e.target.value)}
type={type}
placeholder={placeholder}
value={value}
required={isRequired}
className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none"
className="text-md min-w-16 w-full rounded-md border border-gray-600 bg-bunker-800 p-2 text-gray-400 outline-none"
name={name}
readOnly
autoComplete={autoComplete}
@ -58,12 +58,12 @@ const InputField = ({
);
}
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">{label}</p>
<div className="w-full flex-col">
<div className="mb-0.5 flex flex-row items-center text-mineshaft-300">
<p className="mr-1 text-sm font-semibold">{label}</p>
</div>
<div
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
className={`group relative flex w-full max-w-2xl flex-col justify-center border ${
error ? "border-red" : "border-mineshaft-500"
} rounded-md`}
>
@ -75,11 +75,11 @@ const InputField = ({
required={isRequired}
className={`${
blurred
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
? "text-bunker-800 focus:text-gray-400 active:text-gray-400 group-hover:text-gray-400"
: ""
} ${
error ? "focus:ring-red/50" : "focus:ring-primary/50"
} relative peer bg-mineshaft-900 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
} text-md min-w-16 peer relative w-full rounded-md bg-mineshaft-900 p-2 text-gray-400 outline-none duration-200 focus:ring-4`}
name={name}
spellCheck="false"
autoComplete={autoComplete}
@ -91,7 +91,7 @@ const InputField = ({
onClick={() => {
setPasswordVisible(!passwordVisible);
}}
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
className="absolute mr-3 cursor-pointer self-end text-gray-400"
>
{passwordVisible ? (
<FontAwesomeIcon icon={faEyeSlash} />
@ -101,7 +101,7 @@ const InputField = ({
</button>
)}
{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">
<div className="peer absolute flex h-10 w-fit max-w-xl items-center overflow-hidden text-clip rounded-md text-gray-400/50 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible">
<p className="ml-2" />
{value
.split("")
@ -109,7 +109,7 @@ const InputField = ({
.map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mx-0.5"
className="mx-0.5 text-xxs"
icon={faCircle}
/>
))}
@ -121,7 +121,7 @@ const InputField = ({
</div>
)} */}
</div>
{error && <p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">{errorText}</p>}
{error && <p className="mx-0 mt-0.5 mb-2 max-w-xs text-xs text-red">{errorText}</p>}
</div>
);
};

View File

@ -34,19 +34,19 @@ const ListBox = ({
<Listbox value={isSelected} onChange={onChange}>
<div className="relative">
<Listbox.Button
className={`text-gray-400 relative ${
className={`relative text-gray-400 ${
isFull ? "w-full" : "w-52"
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`}
} focus-visible:ring-offset-orange-300 cursor-default rounded-md bg-white/[0.07] py-2.5 pl-3 pr-10 text-left shadow-md duration-200 hover:bg-white/[0.11] focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 sm:text-sm`}
>
<div className="flex flex-row">
{text}
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
<span className="ml-1 block cursor-pointer truncate font-semibold text-gray-300">
{" "}
{isSelected}
</span>
</div>
{data && (
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<div className="pointer-events-none absolute inset-y-0 right-0 flex cursor-pointer items-center pr-2">
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" />
</div>
)}
@ -58,16 +58,16 @@ const ListBox = ({
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="border border-mineshaft-700 z-[70] 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 no-scrollbar no-scrollbar::-webkit-scrollbar">
<Listbox.Options className="no-scrollbar::-webkit-scrollbar absolute z-[70] mt-1 max-h-60 w-full overflow-auto rounded-md border border-mineshaft-700 bg-bunker p-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 no-scrollbar focus:outline-none sm:text-sm">
{data.map((person, personIdx) => (
<Listbox.Option
key={`${person}.${personIdx + 1}`}
className={({ active, selected }) =>
`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" : ""
`relative my-0.5 cursor-default select-none rounded-md py-2 pl-10 pr-4 ${
selected ? "bg-white/10 font-bold text-gray-400" : ""
} ${
active && !selected
? "bg-white/5 text-mineshaft-200 cursor-pointer"
? "cursor-pointer bg-white/5 text-mineshaft-200"
: "text-gray-400"
} `
}
@ -83,7 +83,7 @@ const ListBox = ({
{person}
</span>
{selected ? (
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3">
<span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 text-primary">
<FontAwesomeIcon icon={faCheck} className="text-md ml-1" />
</span>
) : null}
@ -92,9 +92,9 @@ const ListBox = ({
</Listbox.Option>
))}
{buttonAction && (
<button type="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">
<button type="button" onClick={buttonAction} className="w-full cursor-pointer">
<div className="relative my-0.5 mt-2 flex cursor-pointer select-none justify-start rounded-md py-2 pl-10 pr-4 text-gray-400 duration-200 hover:bg-lime-300 hover:font-semibold hover:text-black">
<span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 pr-4">
<FontAwesomeIcon icon={faPlus} className="text-lg" />
</span>
Add Project

View File

@ -43,7 +43,7 @@ const Button = ({
loading,
icon,
iconDisabled,
type = "button",
type = "button"
}: ButtonProps): JSX.Element => {
// Check if the button show always be 'active' - then true;
// or if it should switch between 'active' and 'disabled' - then give the status
@ -53,9 +53,13 @@ const Button = ({
"group m-auto md:m-0 inline-block rounded-md duration-200",
// Setting background colors and hover modes
color === "mineshaft" && activityStatus && "bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60",
color === "mineshaft" &&
activityStatus &&
"bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60",
color === "mineshaft" && !activityStatus && "bg-mineshaft",
(color === "primary" || !color) && activityStatus && "bg-primary border border-primary-400 opacity-80 hover:opacity-100",
(color === "primary" || !color) &&
activityStatus &&
"bg-primary border border-primary-400 opacity-80 hover:opacity-100",
(color === "primary" || !color) && !activityStatus && "bg-primary",
color === "red" && "bg-red-800 border border-red",
@ -78,7 +82,9 @@ const Button = ({
color !== "mineshaft" && color !== "red" && color !== "none" && "text-black",
color === "red" && "text-gray-200",
color === "none" && "text-gray-200 text-xl",
activityStatus && color !== "red" && color !== "mineshaft" && color !== "none" ? "group-hover:text-black" : "",
activityStatus && color !== "red" && color !== "mineshaft" && color !== "none"
? "group-hover:text-black"
: "",
size === "icon" && "flex items-center justify-center"
);
@ -103,7 +109,7 @@ const Button = ({
<div
className={`${
loading === true ? "opacity-100" : "opacity-0"
} absolute flex items-center px-3 bg-primary duration-200 w-full`}
} absolute flex w-full items-center bg-primary px-3 duration-200`}
>
<Image
src="/images/loading/loadingblack.gif"
@ -116,7 +122,7 @@ const Button = ({
{icon && (
<FontAwesomeIcon
icon={icon}
className={`flex my-auto font-extrabold ${size === "icon-sm" ? "text-sm" : "text-sm"} ${
className={`my-auto flex font-extrabold ${size === "icon-sm" ? "text-sm" : "text-sm"} ${
(text || textDisabled) && "mr-2"
}`}
/>
@ -124,7 +130,7 @@ const Button = ({
{iconDisabled && (
<FontAwesomeIcon
icon={iconDisabled as IconProp}
className={`flex my-auto font-extrabold ${size === "icon-sm" ? "text-sm" : "text-md"} ${
className={`my-auto flex font-extrabold ${size === "icon-sm" ? "text-sm" : "text-md"} ${
(text || textDisabled) && "mr-2"
}`}
/>

View File

@ -64,7 +64,7 @@ const AddProjectMemberDialog = ({
) : (
<Dialog.Title
as="h3"
className="z-50 text-lg font-medium text-mineshaft-300 mb-4"
className="z-50 mb-4 text-lg font-medium text-mineshaft-300"
>
{t("section.members.add-dialog.already-all-invited")}
</Dialog.Title>
@ -127,7 +127,9 @@ const AddProjectMemberDialog = ({
</div>
) : (
<Button
onButtonPressed={() => router.push(`/org/${localStorage.getItem("orgData.id")}/members`)}
onButtonPressed={() =>
router.push(`/org/${localStorage.getItem("orgData.id")}/members`)
}
color="mineshaft"
text={t("section.members.add-dialog.add-user-to-org") as string}
size="md"

View File

@ -28,11 +28,11 @@ export const AddUpdateEnvironmentDialog = ({
onCreateSubmit,
onEditSubmit,
initialValues,
isEditMode,
isEditMode
}: Props) => {
const [formInput, setFormInput] = useState<FormFields>({
name: "",
slug: "",
slug: ""
});
// This use effect can be removed when the unmount is happening from outside the component
@ -50,7 +50,7 @@ export const AddUpdateEnvironmentDialog = ({
e.preventDefault();
const data = {
name: formInput.name,
slug: formInput.slug.toLowerCase(),
slug: formInput.slug.toLowerCase()
};
if (isEditMode) {
onEditSubmit(data);
@ -62,75 +62,70 @@ export const AddUpdateEnvironmentDialog = ({
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative z-20' onClose={onClose}>
<Dialog as="div" className="relative z-20" onClose={onClose}>
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='ease-out duration-150'
leaveFrom='opacity-100'
leaveTo='opacity-0'
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-out duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className='fixed inset-0 bg-black bg-opacity-70' />
<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'>
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0 scale-95'
enterTo='opacity-100 scale-100'
leave='ease-in duration-200'
leaveFrom='opacity-100 scale-100'
leaveTo='opacity-0 scale-95'
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'
>
{isEditMode
? "Update environment"
: "Create a new environment"}
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
{isEditMode ? "Update environment" : "Create a new environment"}
</Dialog.Title>
<form onSubmit={onFormSubmit}>
<div className='max-h-28 mt-4'>
<div className="mt-4 max-h-28">
<InputField
label='Environment Name'
label="Environment Name"
onChangeHandler={(val) => onInputChange("name", val)}
type='varName'
type="varName"
value={formInput.name}
placeholder=''
placeholder=""
isRequired
// error={error.length > 0}
// errorText={error}
/>
</div>
<div className='max-h-28 mt-4'>
<div className="mt-4 max-h-28">
<InputField
label='Environment Slug'
label="Environment Slug"
onChangeHandler={(val) => onInputChange("slug", val)}
type='varName'
type="varName"
value={formInput.slug}
placeholder=''
placeholder=""
isRequired
// error={error.length > 0}
// errorText={error}
/>
</div>
<p className='text-xs text-gray-500 mt-2'>
<p className="mt-2 text-xs text-gray-500">
Slugs are shorthands used in cli to access environment
</p>
<div className='mt-4 max-w-min'>
<div className="mt-4 max-w-min">
<Button
onButtonPressed={() => null}
type='submit'
color='mineshaft'
type="submit"
color="mineshaft"
text={isEditMode ? "Update" : "Create"}
active={formInput.name !== "" && formInput.slug !== ""}
size='md'
size="md"
/>
</div>
</form>

View File

@ -13,76 +13,63 @@ type Props = {
orgName: string;
};
const AddUserDialog = ({
isOpen,
closeModal,
submitModal,
email,
setEmail,
orgName,
}: Props) => {
const AddUserDialog = ({ isOpen, closeModal, submitModal, email, setEmail, orgName }: Props) => {
const submit = () => {
submitModal(email);
};
return (
<div className='z-50'>
<div className="z-50">
<Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative' onClose={closeModal}>
<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'
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' />
<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'>
<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'
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.Panel className="w-full max-w-lg transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as='h3'
className='text-lg font-medium leading-6 text-gray-400 z-50'
as="h3"
className="z-50 text-lg font-medium leading-6 text-gray-400"
>
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.
<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'>
<div className="max-h-28">
<InputField
label='Email'
label="Email"
onChangeHandler={setEmail}
type='varName'
type="varName"
value={email}
placeholder=''
placeholder=""
isRequired
/>
</div>
<div className='mt-4 max-w-max'>
<Button
onButtonPressed={submit}
color='mineshaft'
text='Invite'
size='md'
/>
<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">

View File

@ -5,7 +5,6 @@ import Button from "../buttons/Button";
import InputField from "../InputField";
import { Checkbox } from "../table/Checkbox";
type Props = {
isOpen: boolean;
closeModal: () => void;
@ -26,8 +25,8 @@ const AddWorkspaceDialog = ({
workspaceName,
setWorkspaceName,
error,
loading,
}:Props) => {
loading
}: Props) => {
const [addAllUsers, setAddAllUsers] = useState(true);
const submit = () => {
submitModal(workspaceName, addAllUsers);
@ -60,11 +59,8 @@ const AddWorkspaceDialog = ({
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"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
Create a new project
</Dialog.Title>
<div className="mt-2">
@ -72,7 +68,7 @@ const AddWorkspaceDialog = ({
This project will contain your secrets and configs.
</p>
</div>
<div className="max-h-28 mt-4">
<div className="mt-4 max-h-28">
<InputField
label="Project Name"
onChangeHandler={setWorkspaceName}
@ -84,10 +80,7 @@ const AddWorkspaceDialog = ({
/>
</div>
<div className="mt-4 ml-1">
<Checkbox
addAllUsers={addAllUsers}
setAddAllUsers={setAddAllUsers}
/>
<Checkbox addAllUsers={addAllUsers} setAddAllUsers={setAddAllUsers} />
</div>
<div className="mt-4 max-w-min">
<Button

View File

@ -3,89 +3,76 @@ import { Dialog, Transition } from "@headlessui/react";
import InputField from "../InputField";
// REFACTOR: Move all these modals into one reusable one
// REFACTOR: Move all these modals into one reusable one
type Props = {
isOpen?: boolean;
onClose: ()=>void;
onClose: () => void;
title: string;
onSubmit:()=>void;
deleteKey?:string;
}
onSubmit: () => void;
deleteKey?: string;
};
const DeleteActionModal = ({
isOpen,
onClose,
title,
onSubmit,
deleteKey
}:Props) => {
const [deleteInputField, setDeleteInputField] = useState("")
const DeleteActionModal = ({ isOpen, onClose, title, onSubmit, deleteKey }: Props) => {
const [deleteInputField, setDeleteInputField] = useState("");
useEffect(() => {
setDeleteInputField("");
}, [isOpen]);
useEffect(() => {
setDeleteInputField("");
}, [isOpen]);
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative z-10' onClose={onClose}>
<Dialog as="div" className="relative z-10" onClose={onClose}>
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='ease-in duration-150'
leaveFrom='opacity-100'
leaveTo='opacity-0'
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className='fixed inset-0 bg-black bg-opacity-70' />
<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'>
<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'
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-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'
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-grey p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
{title}
</Dialog.Title>
<div className='mt-2'>
<p className='text-sm text-gray-500'>
This action is irrevertible.
</p>
<div className="mt-2">
<p className="text-sm text-gray-500">This action is irrevertible.</p>
</div>
<div className='mt-2'>
<div className="mt-2">
<InputField
isRequired
label={`Type ${deleteKey} to delete the resource`}
onChangeHandler={(val) => setDeleteInputField(val)}
value={deleteInputField}
type='text'
type="text"
/>
</div>
<div className='mt-6'>
<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'
type="button"
className="hover:bg-alizarin hover:text-semibold inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit}
disabled={
Boolean(deleteKey) && deleteInputField !== deleteKey
}
disabled={Boolean(deleteKey) && deleteInputField !== deleteKey}
>
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'
type="button"
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:border-white hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onClose}
>
Cancel

View File

@ -5,13 +5,13 @@ import { Dialog, Transition } from "@headlessui/react";
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
type Props = {
isOpen: boolean
onClose: () => void
onSubmit: () => void
}
isOpen: boolean;
onClose: () => void;
onSubmit: () => void;
};
export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
@ -45,7 +45,7 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
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 border border-mineshaft-600 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-mineshaft-600 bg-bunker p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-bunker-200">
{t("dashboard:sidebar.delete-key-dialog.title")}
</Dialog.Title>
@ -57,14 +57,14 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
<div className="mt-6 flex justify-start">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-red-500 opacity-80 hover:opacity-100 px-4 py-2 text-sm font-medium text-bunker-100 text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
className="text-semibold inline-flex justify-center rounded-md border border-transparent bg-red-500 px-4 py-2 text-sm font-medium text-bunker-100 opacity-80 duration-200 hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit}
>
Delete
</button>
<button
type="button"
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-bunker-500 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-mineshaft-500 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"
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-bunker-500 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:bg-mineshaft-500 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onClose}
>
Cancel

View File

@ -10,66 +10,55 @@ type Props = {
userIdToBeDeleted: string;
};
const DeleteUserDialog = ({
isOpen,
closeModal,
submitModal,
userIdToBeDeleted,
}: Props) => {
const DeleteUserDialog = ({ isOpen, closeModal, submitModal, userIdToBeDeleted }: Props) => {
const submit = () => {
submitModal(userIdToBeDeleted);
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative z-10' onClose={closeModal}>
<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'
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' />
<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'>
<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'
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'
>
Are you sure you want to remove this user from the
workspace?
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl border border-gray-700 bg-grey p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
Are you sure you want to remove this user from the workspace?
</Dialog.Title>
<div className='mt-2'>
<p className='text-sm text-gray-500'>
This action is irrevertible.
</p>
<div className="mt-2">
<p className="text-sm text-gray-500">This action is irrevertible.</p>
</div>
<div className='mt-6'>
<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'
type="button"
className="hover:bg-alizarin hover:text-semibold inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit}
>
Delete
</button>
<button
type='button'
className='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'
type="button"
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:border-white hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={submit}
>
Cancel

View File

@ -34,27 +34,27 @@ const BottonRightPopup = ({
}: PopupProps): JSX.Element => {
return (
<div
className="z-[100] drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-md absolute bottom-0 right-0 mr-6 mb-6"
className="absolute bottom-0 right-0 z-[100] mr-6 mb-6 flex max-w-xl flex-col items-start rounded-md border border-gray-600/50 bg-bunker pt-3 pb-4 text-gray-200 drop-shadow-xl"
role="alert"
>
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6">
<div className="font-bold text-xl mr-2 mt-0.5 flex flex-row">
<div className="flex w-full flex-row items-center justify-between border-b border-gray-600/70 px-6 pb-3">
<div className="mr-2 mt-0.5 flex flex-row text-xl font-bold">
<div>{titleText}</div>
<div className="ml-2.5">{emoji}</div>
</div>
<button className="mt-1" onClick={() => setCheckDocsPopUpVisible(false)} type="button">
<FontAwesomeIcon
icon={faXmark}
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
className="cursor-pointer text-2xl text-gray-400 duration-200 hover:text-red"
/>
</button>
</div>
<div className="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">{textLine1}</div>
<div className="block sm:inline mb-4 px-6">{textLine2}</div>
<div className="flex flex-row px-6 w-full">
<div className="mt-4 mb-0.5 block px-6 text-gray-300 sm:inline">{textLine1}</div>
<div className="mb-4 block px-6 sm:inline">{textLine2}</div>
<div className="flex w-full flex-row px-6">
{/* eslint-disable-next-line react/jsx-no-target-blank */}
<a
className="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center"
className="flex w-full justify-center rounded-md bg-white/10 p-2 font-bold duration-200 hover:bg-primary hover:text-black"
href={buttonLink}
target="_blank"
rel="noopener"

View File

@ -9,7 +9,7 @@ export const Checkbox = ({ addAllUsers, setAddAllUsers }: Props) => (
{addAllUsers === true ? (
<input
type="checkbox"
className="accent-primary h-4 w-4"
className="h-4 w-4 accent-primary"
checked
readOnly
onClick={() => setAddAllUsers(!addAllUsers)}
@ -20,12 +20,12 @@ export const Checkbox = ({ addAllUsers, setAddAllUsers }: Props) => (
role="button"
tabIndex={0}
aria-label="add all users"
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
className="h-4 w-4 rounded-sm border border-gray-600 bg-bunker"
onClick={() => setAddAllUsers(!addAllUsers)}
/>
)}
<label className="ml-2 text-gray-500 text-sm">
<label className="ml-2 text-sm text-gray-500">
Add all members of my organization to this project.
</label>
</div>

View File

@ -36,25 +36,28 @@ const Notification = ({ notification, clearNotification }: NotificationProps) =>
return (
<div
className="relative w-full flex items-center justify-between px-6 py-4 rounded-md border border-bunker-500 pointer-events-auto bg-mineshaft-700 mb-3 right-3"
className="pointer-events-auto relative right-3 mb-3 flex w-full items-center justify-between rounded-md border border-bunker-500 bg-mineshaft-700 px-6 py-4"
role="alert"
>
{notification.type === "error" && (
<div className="absolute w-full h-1 bg-red top-0 left-0 rounded-t-md" />
<div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-red" />
)}
{notification.type === "success" && (
<div className="absolute w-full h-1 bg-green top-0 left-0 rounded-t-md" />
<div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-green" />
)}
{notification.type === "info" && (
<div className="absolute w-full h-1 bg-yellow top-0 left-0 rounded-t-md" />
<div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-yellow" />
)}
<p className="text-bunker-200 text-md font-base mt-0.5">{notification.text}</p>
<p className="text-md font-base mt-0.5 text-bunker-200">{notification.text}</p>
<button
type="button"
className="rounded-lg"
onClick={() => clearNotification(notification.text)}
>
<FontAwesomeIcon className="absolute right-2 top-3 text-bunker-300 pl-2 w-4 h-4 hover:text-white" icon={faXmark} />
<FontAwesomeIcon
className="absolute right-2 top-3 h-4 w-4 pl-2 text-bunker-300 hover:text-white"
icon={faXmark}
/>
</button>
</div>
);

View File

@ -11,7 +11,7 @@ const Notifications = ({ notifications, clearNotification }: NoticationsProps) =
}
return (
<div className="hidden fixed z-50 md:flex md:flex-col-reverse gap-y-2 w-96 h-full right-2 bottom-2 pointer-events-none">
<div className="pointer-events-none fixed right-2 bottom-2 z-50 hidden h-full w-96 gap-y-2 md:flex md:flex-col-reverse">
{notifications.map((notif) => (
<Notification key={notif.text} notification={notif} clearNotification={clearNotification} />
))}

View File

@ -30,9 +30,9 @@ const ConfirmEnvOverwriteModal = ({
onClose={onClose}
>
<div className="flex flex-col gap-2">
<p className='text-gray-400'>Your file contains the following duplicate secrets:</p>
<p className="text-gray-400">Your file contains the following duplicate secrets:</p>
<p className="text-sm text-gray-500">{duplicateKeys.join(", ")}</p>
<p className='text-md text-gray-400'>Are you sure you want to overwrite these secrets?</p>
<p className="text-md text-gray-400">Are you sure you want to overwrite these secrets?</p>
</div>
</ModalContent>
</Modal>

View File

@ -1,5 +1,10 @@
import { memo, SyntheticEvent, useRef } from "react";
import { faCircle, faCodeBranch, faExclamationCircle, faEye } from "@fortawesome/free-solid-svg-icons";
import {
faCircle,
faCodeBranch,
faExclamationCircle,
faEye
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import guidGenerator from "../utilities/randomId";
@ -31,8 +36,8 @@ interface DashboardInputFieldProps {
* @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard
* @param {boolean} obj.isDuplicate - if the key name is duplicated
* @param {boolean} obj.override - whether a secret/row should be displalyed as overriden
*
*
*
*
* @returns
*/
@ -61,29 +66,31 @@ const DashboardInputField = ({
const error = startsWithNumber || isDuplicate;
return (
<div className={`relative flex-col w-full h-10 ${
error && value !== "" ? "bg-red/[0.15]" : ""
} ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}>
<div
className={`relative h-10 w-full flex-col ${error && value !== "" ? "bg-red/[0.15]" : ""} ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}
>
<div
className={`group relative flex flex-col justify-center items-center h-full ${
className={`group relative flex h-full flex-col items-center justify-center ${
error ? "w-max" : "w-full"
}`}
>
<input
onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)}
onChange={(e) =>
onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)
}
type={type}
value={value}
className={`z-10 peer font-mono ph-no-capture bg-transparent h-full caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none ${
className={`ph-no-capture min-w-16 peer z-10 h-full w-full bg-transparent px-2 font-mono text-sm caret-bunker-200 outline-none ${
error ? "text-red-600 focus:text-red-500" : "text-bunker-300 focus:text-bunker-100"
} duration-200`}
spellCheck="false"
/>
</div>
{startsWithNumber && (
<div className='absolute right-2 top-2 text-red z-50'>
<HoverObject
<div className="absolute right-2 top-2 z-50 text-red">
<HoverObject
text="Secret names should not start with a number"
icon={faExclamationCircle}
color="red"
@ -91,33 +98,44 @@ const DashboardInputField = ({
</div>
)}
{isDuplicate && value !== "" && !startsWithNumber && (
<div className='absolute right-2 top-2 text-red z-50'>
<HoverObject
<div className="absolute right-2 top-2 z-50 text-red">
<HoverObject
text="Secret names should be unique"
icon={faExclamationCircle}
color="red"
/>
</div>
)}
{!error && <div className={`absolute right-0 top-0 text-red z-50 bg-mineshaft-800 group-hover:bg-mineshaft-700 ${
overrideEnabled ? "visible" : "invisible group-hover:visible"
} cursor-pointer duration-0 h-10 flex items-center px-2`}>
<button type="button" onClick={() => {
if (modifyValueOverride) {
if (overrideEnabled === false) {
modifyValueOverride("", id);
} else {
modifyValueOverride(undefined, id);
}
}
}}>
<HoverObject
text={overrideEnabled ? "This secret is overriden with your personal value" : "You can override this secret with a personal value"}
icon={faCodeBranch}
color={overrideEnabled ? "primary" : "bunker-400"}
/>
</button>
</div>}
{!error && (
<div
className={`absolute right-0 top-0 z-50 bg-mineshaft-800 text-red group-hover:bg-mineshaft-700 ${
overrideEnabled ? "visible" : "invisible group-hover:visible"
} duration-0 flex h-10 cursor-pointer items-center px-2`}
>
<button
type="button"
onClick={() => {
if (modifyValueOverride) {
if (overrideEnabled === false) {
modifyValueOverride("", id);
} else {
modifyValueOverride(undefined, id);
}
}
}}
>
<HoverObject
text={
overrideEnabled
? "This secret is overriden with your personal value"
: "You can override this secret with a personal value"
}
icon={faCodeBranch}
color={overrideEnabled ? "primary" : "bunker-400"}
/>
</button>
</div>
)}
</div>
);
}
@ -127,20 +145,29 @@ const DashboardInputField = ({
return (
<PopoverObject text={value || ""} onChangeHandler={onChangeHandler} id={id}>
<div title={value} className={`relative flex-col w-full h-10 overflow-hidden ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}>
<div
title={value}
className={`relative h-10 w-full flex-col overflow-hidden ${
isSideBarOpen && "bg-mineshaft-700 duration-200"
}`}
>
<div
className={`group relative flex flex-col justify-center items-center h-full ${
className={`group relative flex h-full flex-col items-center justify-center ${
error ? "w-max" : "w-full"
}`}
>
{value?.split("\n")[0] ? <span className='ph-no-capture truncate break-all bg-transparent leading-tight text-xs px-2 w-full min-w-16 outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'>
{value?.split("\n")[0]}
</span> : <span className='text-bunker-400'>-</span> }
{value?.split("\n")[1] && <span className='ph-no-capture truncate break-all bg-transparent leading-tight text-xs px-2 w-full min-w-16 outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'>
{value?.split("\n")[1]}
</span>}
{value?.split("\n")[0] ? (
<span className="ph-no-capture min-w-16 placeholder w-full truncate break-all bg-transparent px-2 text-xs leading-tight text-bunker-300 outline-none duration-200 placeholder:text-bunker-400 focus:text-bunker-100 placeholder:focus:text-transparent">
{value?.split("\n")[0]}
</span>
) : (
<span className="text-bunker-400">-</span>
)}
{value?.split("\n")[1] && (
<span className="ph-no-capture min-w-16 placeholder w-full truncate break-all bg-transparent px-2 text-xs leading-tight text-bunker-300 outline-none duration-200 placeholder:text-bunker-400 focus:text-bunker-100 placeholder:focus:text-transparent">
{value?.split("\n")[1]}
</span>
)}
</div>
</div>
</PopoverObject>
@ -148,10 +175,10 @@ const DashboardInputField = ({
}
if (type === "value") {
return (
<div className="flex-col w-full">
<div className="group relative whitespace-pre flex flex-col justify-center w-full">
<div className="w-full flex-col">
<div className="group relative flex w-full flex-col justify-center whitespace-pre">
{overrideEnabled === true && (
<div className="bg-primary-500 rounded-sm absolute top-[0.1rem] right-[0.1rem] z-0 w-min text-xxs px-1 text-black opacity-80">
<div className="absolute top-[0.1rem] right-[0.1rem] z-0 w-min rounded-sm bg-primary-500 px-1 text-xxs text-black opacity-80">
Override enabled
</div>
)}
@ -160,20 +187,20 @@ const DashboardInputField = ({
onChange={(e) => onChangeHandler(e.target.value, id)}
onScroll={syncScroll}
className={`${
blurred
? "text-transparent focus:text-transparent active:text-transparent"
: ""
} z-10 peer font-mono ph-no-capture bg-transparent caret-white text-transparent text-sm px-2 py-2 w-full min-w-16 outline-none duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
blurred ? "text-transparent focus:text-transparent active:text-transparent" : ""
} ph-no-capture min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full bg-transparent px-2 py-2 font-mono text-sm text-transparent caret-white outline-none duration-200 no-scrollbar`}
spellCheck="false"
/>
<div
ref={ref}
className={`${
blurred && !overrideEnabled
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400 duration-200"
? "text-bunker-800 duration-200 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400"
: ""
} ${overrideEnabled ? "text-primary-300" : "text-gray-400"}
absolute flex flex-row whitespace-pre font-mono z-0 ${blurred ? "invisible" : "visible"} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
absolute z-0 flex flex-row whitespace-pre font-mono ${
blurred ? "invisible" : "visible"
} ph-no-capture min-w-16 no-scrollbar::-webkit-scrollbar mt-0.5 h-10 w-full overflow-x-scroll bg-transparent px-2 py-2 text-sm outline-none duration-100 no-scrollbar peer-focus:visible`}
>
{value?.split(REGEX).map((word) => {
if (word.match(REGEX) !== null) {
@ -203,20 +230,24 @@ const DashboardInputField = ({
})}
</div>
{blurred && (
<div className={`absolute flex flex-row justify-between items-center z-0 peer pr-2 ${
isSideBarOpen ? "bg-mineshaft-700 duration-200" : "bg-mineshaft-800"
} peer-active:hidden peer-focus:hidden group-hover:bg-white/[0.00] duration-100 h-10 w-full text-bunker-400 text-clip`}>
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
<div
className={`peer absolute z-0 flex flex-row items-center justify-between pr-2 ${
isSideBarOpen ? "bg-mineshaft-700 duration-200" : "bg-mineshaft-800"
} h-10 w-full text-clip text-bunker-400 duration-100 group-hover:bg-white/[0.00] peer-focus:hidden peer-active:hidden`}
>
<div className="no-scrollbar::-webkit-scrollbar flex flex-row items-center overflow-x-scroll px-2 no-scrollbar">
{value?.split("").map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="text-xxs mr-0.5"
className="mr-0.5 text-xxs"
icon={faCircle}
/>
))}
{value?.split("").length === 0 && <span className='text-bunker-400/80'>EMPTY</span>}
{value?.split("").length === 0 && <span className="text-bunker-400/80">EMPTY</span>}
</div>
<div className="invisible z-[100] cursor-default group-hover:visible">
<FontAwesomeIcon icon={faEye} />
</div>
<div className='invisible group-hover:visible cursor-default z-[100]'><FontAwesomeIcon icon={faEye} /></div>
</div>
)}
</div>

View File

@ -1,4 +1,4 @@
import React from "react"
import React from "react";
import { useTranslation } from "react-i18next";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -8,32 +8,35 @@ import Button from "../basic/buttons/Button";
type Props = {
onSubmit: () => void;
isPlain?: boolean;
}
};
export const DeleteActionButton = ({ onSubmit, isPlain }: Props) => {
const { t } = useTranslation();
return (
<div className={`${
!isPlain
? "bg-[#9B3535] opacity-70 hover:opacity-100 w-[4.5rem] h-[2.5rem] rounded-md duration-200 ml-2"
: "cursor-pointer w-[1.5rem] h-[2.35rem] mr-2 flex items-center justfy-center"}`}>
{isPlain
? <div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={onSubmit}
className="invisible group-hover:visible"
>
<FontAwesomeIcon className="text-bunker-300 hover:text-red pl-2 pr-6 text-lg mt-0.5" icon={faXmark} />
</div>
: <Button
text={String(t("Delete"))}
color="red"
size="md"
onButtonPressed={onSubmit}
/>}
<div
className={`${
!isPlain
? "ml-2 h-[2.5rem] w-[4.5rem] rounded-md bg-[#9B3535] opacity-70 duration-200 hover:opacity-100"
: "justfy-center mr-2 flex h-[2.35rem] w-[1.5rem] cursor-pointer items-center"
}`}
>
{isPlain ? (
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={onSubmit}
className="invisible group-hover:visible"
>
<FontAwesomeIcon
className="mt-0.5 pl-2 pr-6 text-lg text-bunker-300 hover:text-red"
icon={faXmark}
/>
</div>
) : (
<Button text={String(t("Delete"))} color="red" size="md" onButtonPressed={onSubmit} />
)}
</div>
)
}
);
};

View File

@ -18,7 +18,7 @@ const DownloadSecretMenu = ({ data, env }: { data: SecretDataProps[]; env: strin
<Menu as="div" className="relative inline-block text-left">
<Menu.Button
as="div"
className="inline-flex w-full justify-center 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"
className="inline-flex w-full justify-center rounded-md text-sm font-medium text-gray-200 duration-200 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
>
<Button color="mineshaft" size="icon-md" icon={faDownload} onButtonPressed={() => {}} />
</Menu.Button>
@ -31,7 +31,7 @@ const DownloadSecretMenu = ({ data, env }: { data: SecretDataProps[]; env: strin
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute z-[90] drop-shadow-xl right-0 mt-0.5 w-[12rem] origin-top-right rounded-md bg-bunker border border-mineshaft-500 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none p-2 space-y-2">
<Menu.Items className="absolute right-0 z-[90] mt-0.5 w-[12rem] origin-top-right space-y-2 rounded-md border border-mineshaft-500 bg-bunker p-2 shadow-lg ring-1 ring-black ring-opacity-5 drop-shadow-xl focus:outline-none">
<Menu.Item>
<Button
color="mineshaft"

View File

@ -56,7 +56,7 @@ export default function NavHeader({
return (
<div className="flex flex-row items-center pt-6">
<div className="mr-2 flex h-5 w-5 items-center justify-center rounded-md bg-primary text-sm text-black min-w-[1.25rem]">
<div className="mr-2 flex h-5 w-5 min-w-[1.25rem] items-center justify-center rounded-md bg-primary text-sm text-black">
{currentOrg?.name?.charAt(0)}
</div>
<Link passHref legacyBehavior href={`/org/${currentOrg?.id}/overview`}>

View File

@ -6,7 +6,6 @@ import { useOrganization, useWorkspace } from "@app/context";
import { Select, SelectItem, Tooltip } from "../v2";
/**
* This is the component at the top of almost every page.
* It shows how to navigate to a certain page.
@ -39,10 +38,12 @@ export default function NavHeaderSecrets({
}): JSX.Element {
const { currentWorkspace } = useWorkspace();
const { currentOrg } = useOrganization();
const router = useRouter()
const router = useRouter();
return (
<div className={`${!isSnapshot && "absolute"} ml-6 flex flex-row items-center pt-6 cursor-default`}>
<div
className={`${!isSnapshot && "absolute"} ml-6 flex cursor-default flex-row items-center pt-6`}
>
<div className="mr-3 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
{currentOrg?.name?.charAt(0)}
</div>
@ -60,31 +61,39 @@ export default function NavHeaderSecrets({
</>
)}
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
{pageName === "Secrets"
? <a className="text-md font-medium text-primary/80 hover:text-primary" href={`${router.asPath.split("?")[0]}`}>{pageName}</a>
: <div className="text-md text-gray-400">{pageName}</div>}
{currentEnv &&
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-sm text-gray-400" />
<div className='pl-3 rounded-md hover:bg-bunker-100/10'>
<Tooltip content="Select environment">
<Select
value={userAvailableEnvs?.filter(uae => uae.name === currentEnv)[0]?.slug}
onValueChange={(value) => {
if (value && onEnvChange) onEnvChange(value);
}}
className="text-md pl-0 font-medium text-primary/80 hover:text-primary bg-transparent"
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
>
{userAvailableEnvs?.map(({ name, slug }) => (
<SelectItem value={slug} key={slug}>
{name}
</SelectItem>
))}
</Select>
</Tooltip>
</div>
</>}
{pageName === "Secrets" ? (
<a
className="text-md font-medium text-primary/80 hover:text-primary"
href={`${router.asPath.split("?")[0]}`}
>
{pageName}
</a>
) : (
<div className="text-md text-gray-400">{pageName}</div>
)}
{currentEnv && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-sm text-gray-400" />
<div className="rounded-md pl-3 hover:bg-bunker-100/10">
<Tooltip content="Select environment">
<Select
value={userAvailableEnvs?.filter((uae) => uae.name === currentEnv)[0]?.slug}
onValueChange={(value) => {
if (value && onEnvChange) onEnvChange(value);
}}
className="text-md bg-transparent pl-0 font-medium text-primary/80 hover:text-primary"
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
>
{userAvailableEnvs?.map(({ name, slug }) => (
<SelectItem value={slug} key={slug}>
{name}
</SelectItem>
))}
</Select>
</Tooltip>
</div>
</>
)}
</div>
);
}

View File

@ -3,9 +3,7 @@ import React, { useState } from "react";
import ReactCodeInput from "react-code-input";
import { useTranslation } from "react-i18next";
import {
useSendVerificationEmail
} from "@app/hooks/api";
import { useSendVerificationEmail } from "@app/hooks/api";
import Error from "../basic/Error";
import { Button } from "../v2";
@ -90,8 +88,8 @@ export default function CodeInputStep({
return (
<div className="mx-auto h-full w-full pb-4 md:px-8">
<p className="text-md flex justify-center text-bunker-200">{t("signup.step2-message")}</p>
<p className="text-md flex justify-center font-semibold my-1 text-bunker-200">{email} </p>
<div className="hidden md:block w-max min-w-[20rem] mx-auto">
<p className="text-md my-1 flex justify-center font-semibold text-bunker-200">{email} </p>
<div className="mx-auto hidden w-max min-w-[20rem] md:block">
<ReactCodeInput
name=""
inputMode="tel"
@ -102,7 +100,7 @@ export default function CodeInputStep({
className="mt-6 mb-2"
/>
</div>
<div className="block md:hidden w-max mt-4 mx-auto">
<div className="mx-auto mt-4 block w-max md:hidden">
<ReactCodeInput
name=""
inputMode="tel"
@ -114,26 +112,29 @@ export default function CodeInputStep({
/>
</div>
{codeError && <Error text={t("signup.step2-code-error")} />}
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
<div className="text-l py-1 text-lg w-full">
<div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
<div className="text-l w-full py-1 text-lg">
<Button
type="submit"
onClick={incrementStep}
size="sm"
isFullWidth
className='h-14'
className="h-14"
colorSchema="primary"
variant="outline_bg"
isLoading={isCodeInputCheckLoading}
> {String(t("signup.verify"))} </Button>
>
{" "}
{String(t("signup.verify"))}{" "}
</Button>
</div>
</div>
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
<div className="mx-auto flex max-h-24 w-full max-w-md flex-col items-center justify-center pt-2">
<div className="flex flex-row items-baseline gap-1 text-sm">
<span className="text-bunker-400">{t("signup.step2-resend-alert")}</span>
<div className="mt-2 text-bunker-400 text-md flex flex-row">
<div className="text-md mt-2 flex flex-row text-bunker-400">
<button disabled={isLoading} onClick={resendVerificationEmail} type="button">
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
{isResendingVerificationEmail
? t("signup.step2-resend-progress")
: t("signup.step2-resend-submit")}
@ -141,7 +142,7 @@ export default function CodeInputStep({
</button>
</div>
</div>
<p className="text-sm text-bunker-400 pb-2">{t("signup.step2-spam-alert")}</p>
<p className="pb-2 text-sm text-bunker-400">{t("signup.step2-spam-alert")}</p>
</div>
</div>
);

View File

@ -57,19 +57,22 @@ export default function DonwloadBackupPDFStep({
};
return (
<div className="flex flex-col items-center w-full h-full md:px-6 mx-auto mb-36 md:mb-16">
<p className="text-xl text-center font-medium flex flex-col justify-center items-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 mb-6 text-6xl text-bunker-200" />
<div className="mx-auto mb-36 flex h-full w-full flex-col items-center md:mb-16 md:px-6">
<p className="flex flex-col items-center justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
<FontAwesomeIcon
icon={faWarning}
className="ml-2 mr-3 mb-6 pt-1 text-6xl text-bunker-200"
/>
{t("signup.step4-message")}
</p>
<div className="flex flex-col pb-2 bg-mineshaft-800 border border-mineshaft-600 items-center justify-center text-center lg:w-1/6 w-full md:min-w-[24rem] mt-8 max-w-md text-bunker-300 text-md rounded-md">
<div className="w-full mt-4 md:mt-8 flex flex-row text-center items-center m-2 text-bunker-300 rounded-md lg:w-1/6 lg:w-1/6 w-full md:min-w-[23rem] px-3 mx-auto">
<div className="text-md mt-8 flex w-full max-w-md flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 pb-2 text-center text-bunker-300 md:min-w-[24rem] lg:w-1/6">
<div className="m-2 mx-auto mt-4 flex w-full w-full flex-row items-center rounded-md px-3 text-center text-bunker-300 md:mt-8 md:min-w-[23rem] lg:w-1/6 lg:w-1/6">
<span className="mb-2">
{t("signup.step4-description1")} {t("signup.step4-description3")}
</span>
</div>
<div className="flex flex-col items-center px-3 justify-center mt-0 md:mt-4 mb-2 md:mb-4 lg:w-1/6 w-full md:min-w-[20rem] mt-2 md:max-w-md mx-auto text-sm text-center md:text-left">
<div className="text-l py-1 text-lg w-full">
<div className="mx-auto mt-0 mb-2 mt-2 flex w-full flex-col items-center justify-center px-3 text-center text-sm md:mt-4 md:mb-4 md:min-w-[20rem] md:max-w-md md:text-left lg:w-1/6">
<div className="text-l w-full py-1 text-lg">
<Button
onClick={handleBackupKeyGenerate}
size="sm"

View File

@ -52,13 +52,13 @@ export default function EnterEmailStep({
try {
await mutateAsync({ email });
incrementStep();
} catch(e) {
} catch (e) {
if (axios.isAxiosError(e)) {
const { message = "Something went wrong" } = e.response?.data as { message: string};
const { message = "Something went wrong" } = e.response?.data as { message: string };
createNotification({
type: "error",
text: message
})
text: message
});
}
}
}
@ -66,11 +66,11 @@ export default function EnterEmailStep({
return (
<div>
<div className="w-full md:px-6 mx-auto">
<p className="text-xl font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
<div className="mx-auto w-full md:px-6">
<p className="flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-xl font-medium text-transparent">
{t("signup.step1-start")}
</p>
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] m-auto rounded-lg mt-8">
<div className="m-auto mt-8 flex w-1/4 min-w-[20rem] flex-col items-center justify-center rounded-lg lg:w-1/6">
<Input
placeholder="Enter your email address..."
onChange={(e) => setEmail(e.target.value)}
@ -79,28 +79,35 @@ export default function EnterEmailStep({
autoComplete="username"
className="h-12"
/>
{emailError && <p className="text-red-600 text-xs text-left w-full ml-1.5 mt-1.5">Please enter a valid email.</p>}
{emailError && (
<p className="ml-1.5 mt-1.5 w-full text-left text-xs text-red-600">
Please enter a valid email.
</p>
)}
</div>
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
<div className="text-l py-1 text-lg w-full">
<div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-1/6">
<div className="text-l w-full py-1 text-lg">
<Button
type="submit"
onClick={emailCheck}
size="sm"
isFullWidth
className='h-14'
className="h-14"
colorSchema="primary"
variant="outline_bg"
isLoading={isLoading}
isDisabled={isLoading}
> {String(t("signup.step1-submit"))} </Button>
>
{" "}
{String(t("signup.step1-submit"))}{" "}
</Button>
</div>
</div>
</div>
<div className="mx-auto mb-48 mt-2 flex w-full max-w-md flex-col items-center justify-center pt-2 md:mb-16 md:pb-2">
<Link href="/login">
<button type="button" className="w-max pb-3 duration-200 hover:opacity-90">
<span className="text-sm text-mineshaft-400 hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer">
<span className="cursor-pointer text-sm text-mineshaft-400 duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
{t("signup.already-have-account")}
</span>
</button>

View File

@ -16,7 +16,7 @@ export default function TeamInviteStep(): JSX.Element {
const router = useRouter();
const [emails, setEmails] = useState("");
const { data: serverDetails } = useFetchServerStatus();
const { mutateAsync } = useAddUserToOrg();
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const);
@ -40,55 +40,61 @@ export default function TeamInviteStep(): JSX.Element {
};
return (
<div className="w-max mx-auto min-w-lg h-full pb-4 px-8 mb-64 md:mb-32">
<p className="text-2xl font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
<div className="min-w-lg mx-auto mb-64 h-full w-max px-8 pb-4 md:mb-32">
<p className="flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-2xl font-semibold text-transparent">
{t("signup.step5-invite-team")}
</p>
<p className="text-center flex justify-center text-bunker-400 md:mx-8 mb-6 mt-4">
<p className="mb-6 mt-4 flex justify-center text-center text-bunker-400 md:mx-8">
{t("signup.step5-subtitle")}
</p>
<div className="bg-mineshaft-800 border border-mineshaft-500 w-max mx-auto pt-6 pb-4 px-8 rounded-xl drop-shadow-xl mb-6">
<div className="mx-auto mb-6 w-max rounded-xl border border-mineshaft-500 bg-mineshaft-800 px-8 pt-6 pb-4 drop-shadow-xl">
<div>
<div className="text-bunker-300 font-medium pl-1 pb-1 text-sm">
<div className="pl-1 pb-1 text-sm font-medium text-bunker-300">
<span>Emails</span>
</div>
<textarea
className="bg-mineshaft-900/70 min-w-[30rem] h-20 w-full placeholder:text-bunker-400 py-1 px-2 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
className="h-20 w-full min-w-[30rem] rounded-md border border-mineshaft-500 bg-mineshaft-900/70 py-1 px-2 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2"
value={emails}
onChange={(e) => setEmails(e.target.value)}
placeholder="email@example.com, email2@example.com..."
/>
</div>
<div className="flex flex-row items-end justify-end mt-0 md:mt-4 md:mb-2 w-full md:min-w-[30rem] mt-2 md:max-w-md mx-auto text-sm">
<div className="mx-auto mt-0 mt-2 flex w-full flex-row items-end justify-end text-sm md:mt-4 md:mb-2 md:min-w-[30rem] md:max-w-md">
<Button
onClick={() => {
if (serverDetails?.emailConfigured) {
inviteUsers({ emails })
inviteUsers({ emails });
} else {
handlePopUpOpen("setUpEmail");
}
}}
size="sm"
// isFullWidth
className='h-10'
className="h-10"
colorSchema="primary"
variant="solid"
> {t("signup.step5-send-invites") ?? ""} </Button>
>
{" "}
{t("signup.step5-send-invites") ?? ""}{" "}
</Button>
</div>
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
/>
</div>
<div className="flex flex-row max-w-max min-w-28 items-center justify-center md:p-2 min-w-[20rem] max-h-24 mx-auto text-lg px-4 mt-4 mb-2">
<div className="min-w-28 mx-auto mt-4 mb-2 flex max-h-24 min-w-[20rem] max-w-max flex-row items-center justify-center px-4 text-lg md:p-2">
<Button
onClick={redirectToHome}
size="sm"
isFullWidth
className='h-12'
className="h-12"
colorSchema="secondary"
variant="outline"
> {t("signup.step5-skip") ?? "Skip"} </Button>
>
{" "}
{t("signup.step5-skip") ?? "Skip"}{" "}
</Button>
</div>
</div>
);

View File

@ -1,17 +1,11 @@
import {
getAuthToken,
setAuthToken,
setMfaTempToken,
setSignupTempToken} from "@app/reactQuery";
import { getAuthToken, setAuthToken, setMfaTempToken, setSignupTempToken } from "@app/reactQuery";
export const PROVIDER_AUTH_TOKEN_KEY = "infisical__provider-auth-token";
// depreciated: go for apiRequest module in config/api
export default class SecurityClient {
static setProviderAuthToken(tokenStr: string) {
localStorage.setItem(PROVIDER_AUTH_TOKEN_KEY, tokenStr || "")
localStorage.setItem(PROVIDER_AUTH_TOKEN_KEY, tokenStr || "");
}
static getProviderAuthToken() {

View File

@ -3,9 +3,7 @@ import crypto from "crypto";
import jsrp from "jsrp";
import {
changePassword,
srp1} from "@app/hooks/api/auth/queries";
import { changePassword, srp1 } from "@app/hooks/api/auth/queries";
import Aes256Gcm from "./cryptography/aes-256-gcm";
import { deriveArgonKey } from "./cryptography/crypto";
@ -15,95 +13,95 @@ const clientOldPassword = new jsrp.client();
const clientNewPassword = new jsrp.client();
type Params = {
email: string;
currentPassword: string;
newPassword: string;
}
email: string;
currentPassword: string;
newPassword: string;
};
const attemptChangePassword = ({ email, currentPassword, newPassword }: Params): Promise<void> => {
return new Promise((resolve, reject) => {
clientOldPassword.init({ username: email, password: currentPassword }, async () => {
let serverPublicKey; let salt;
return new Promise((resolve, reject) => {
clientOldPassword.init({ username: email, password: currentPassword }, async () => {
let serverPublicKey;
let salt;
try {
const clientPublicKey = clientOldPassword.getPublicKey();
const res = await srp1({ clientPublicKey });
serverPublicKey = res.serverPublicKey;
salt = res.salt;
clientOldPassword.setSalt(salt);
clientOldPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientOldPassword.getProof();
clientNewPassword.init({ username: email, password: newPassword }, async () => {
clientNewPassword.createVerifier(async (err, result) => {
try {
const clientPublicKey = clientOldPassword.getPublicKey();
const derivedKey = await deriveArgonKey({
password: newPassword,
salt: result.salt,
mem: 65536,
time: 3,
parallelism: 1,
hashLen: 32
});
const res = await srp1({ clientPublicKey });
if (!derivedKey) throw new Error("Failed to derive key from password");
serverPublicKey = res.serverPublicKey;
salt = res.salt;
const key = crypto.randomBytes(32);
clientOldPassword.setSalt(salt);
clientOldPassword.setServerPublicKey(serverPublicKey);
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: localStorage.getItem("PRIVATE_KEY") as string,
secret: key
});
const clientProof = clientOldPassword.getProof();
const {
ciphertext: protectedKey,
iv: protectedKeyIV,
tag: protectedKeyTag
} = Aes256Gcm.encrypt({
text: key.toString("hex"),
secret: Buffer.from(derivedKey.hash)
});
clientNewPassword.init({ username: email, password: newPassword }, async () => {
clientNewPassword.createVerifier(async (err, result) => {
try {
const derivedKey = await deriveArgonKey({
password: newPassword,
salt: result.salt,
mem: 65536,
time: 3,
parallelism: 1,
hashLen: 32
});
await changePassword({
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt: result.salt,
verifier: result.verifier
});
if (!derivedKey) throw new Error("Failed to derive key from password");
saveTokenToLocalStorage({
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
});
const key = crypto.randomBytes(32);
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: localStorage.getItem("PRIVATE_KEY") as string,
secret: key
});
const {
ciphertext: protectedKey,
iv: protectedKeyIV,
tag: protectedKeyTag
} = Aes256Gcm.encrypt({
text: key.toString("hex"),
secret: Buffer.from(derivedKey.hash)
});
await changePassword({
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt: result.salt,
verifier: result.verifier
});
saveTokenToLocalStorage({
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
});
resolve();
} catch (err2) {
console.error(err2);
reject(err2);
}
});
});
} catch (err) {
console.error(err);
reject(err);
resolve();
} catch (err2) {
console.error(err2);
reject(err2);
}
});
});
} catch (err) {
console.error(err);
reject(err);
}
});
}
});
};
export default attemptChangePassword;
export default attemptChangePassword;

View File

@ -1,7 +1,7 @@
/* eslint-disable prefer-destructuring */
import jsrp from "jsrp";
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
import KeyService from "@app/services/KeyService";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
@ -11,11 +11,11 @@ import SecurityClient from "./SecurityClient";
const client = new jsrp.client();
interface IsMfaLoginSuccessful {
success: boolean;
loginResponse:{
privateKey: string;
JTWToken: string;
}
success: boolean;
loginResponse: {
privateKey: string;
JTWToken: string;
};
}
/**
@ -26,81 +26,84 @@ interface IsMfaLoginSuccessful {
* @param {String} obj.mfaToken - MFA code/token
*/
const attemptLoginMfa = async ({
email,
password,
providerAuthToken,
mfaToken
email,
password,
providerAuthToken,
mfaToken
}: {
email: string;
password: string;
providerAuthToken?: string,
mfaToken: string;
email: string;
password: string;
providerAuthToken?: string;
mfaToken: string;
}): Promise<IsMfaLoginSuccessful> => {
return new Promise((resolve, reject) => {
client.init({
username: email,
password
}, async () => {
try {
const clientPublicKey = client.getPublicKey();
const { salt } = await login1({
email,
clientPublicKey,
providerAuthToken,
});
return new Promise((resolve, reject) => {
client.init(
{
username: email,
password
},
async () => {
try {
const clientPublicKey = client.getPublicKey();
const { salt } = await login1({
email,
clientPublicKey,
providerAuthToken
});
const {
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await verifyMfaToken({
email,
mfaCode: mfaToken
});
const {
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await verifyMfaToken({
email,
mfaCode: mfaToken
});
// unset temporary (MFA) JWT token and set JWT token
SecurityClient.setMfaToken("");
SecurityClient.setToken(token);
SecurityClient.setProviderAuthToken("");
// unset temporary (MFA) JWT token and set JWT token
SecurityClient.setMfaToken("");
SecurityClient.setToken(token);
SecurityClient.setProviderAuthToken("");
const privateKey = await KeyService.decryptPrivateKey({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
const privateKey = await KeyService.decryptPrivateKey({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
resolve({
success: true,
loginResponse:{
privateKey,
JTWToken: token
}
});
} catch (err) {
reject(err);
resolve({
success: true,
loginResponse: {
privateKey,
JTWToken: token
}
});
});
}
});
} catch (err) {
reject(err);
}
}
);
});
};
export default attemptLoginMfa;
export default attemptLoginMfa;

View File

@ -1,7 +1,7 @@
/* eslint-disable prefer-destructuring */
import jsrp from "jsrp";
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
import KeyService from "@app/services/KeyService";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
@ -18,75 +18,78 @@ const client = new jsrp.client();
* @param {String} obj.mfaToken - MFA code/token
*/
const attemptLoginMfa = async ({
email,
password,
providerAuthToken,
mfaToken
email,
password,
providerAuthToken,
mfaToken
}: {
email: string;
password: string;
providerAuthToken?: string,
mfaToken: string;
email: string;
password: string;
providerAuthToken?: string;
mfaToken: string;
}): Promise<Boolean> => {
return new Promise((resolve, reject) => {
client.init({
username: email,
password
}, async () => {
try {
const clientPublicKey = client.getPublicKey();
const { salt } = await login1({
email,
clientPublicKey,
providerAuthToken,
});
const {
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await verifyMfaToken({
email,
mfaCode: mfaToken
});
return new Promise((resolve, reject) => {
client.init(
{
username: email,
password
},
async () => {
try {
const clientPublicKey = client.getPublicKey();
const { salt } = await login1({
email,
clientPublicKey,
providerAuthToken
});
// unset temporary (MFA) JWT token and set JWT token
SecurityClient.setMfaToken("");
SecurityClient.setToken(token);
SecurityClient.setProviderAuthToken("");
const {
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await verifyMfaToken({
email,
mfaCode: mfaToken
});
const privateKey = await KeyService.decryptPrivateKey({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
// unset temporary (MFA) JWT token and set JWT token
SecurityClient.setMfaToken("");
SecurityClient.setToken(token);
SecurityClient.setProviderAuthToken("");
resolve(true);
} catch (err) {
reject(err);
}
});
});
}
const privateKey = await KeyService.decryptPrivateKey({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
export default attemptLoginMfa;
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
resolve(true);
} catch (err) {
reject(err);
}
}
);
});
};
export default attemptLoginMfa;

View File

@ -1,5 +1,11 @@
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes";
import {
escapeCharRegex,
letterCharRegex,
lowEntropyRegexes,
numAndSpecialCharRegex,
repeatedCharRegex
} from "./passwordRegexes";
interface PasswordCheckProps {
password: string;
@ -29,40 +35,38 @@ const passwordCheck = async ({
{
name: "tooShort",
validator: (pwd: string) => pwd.length >= 14,
setError: setPasswordErrorTooShort,
setError: setPasswordErrorTooShort
},
{
name: "tooLong",
validator: (pwd: string) => pwd.length < 101,
setError: setPasswordErrorTooLong,
setError: setPasswordErrorTooLong
},
{
name: "noLetterChar",
validator: (pwd: string) => letterCharRegex.test(pwd),
setError: setPasswordErrorNoLetterChar,
setError: setPasswordErrorNoLetterChar
},
{
name: "noNumOrSpecialChar",
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
setError: setPasswordErrorNoNumOrSpecialChar,
setError: setPasswordErrorNoNumOrSpecialChar
},
{
name: "repeatedChar",
validator: (pwd: string) => !repeatedCharRegex.test(pwd),
setError: setPasswordErrorRepeatedChar,
setError: setPasswordErrorRepeatedChar
},
{
name: "escapeChar",
validator: (pwd: string) => !escapeCharRegex.test(pwd),
setError: setPasswordErrorEscapeChar,
setError: setPasswordErrorEscapeChar
},
{
name: "lowEntropy",
validator: (pwd: string) => (
!lowEntropyRegexes.some(regex => regex.test(pwd))
),
setError: setPasswordErrorLowEntropy,
},
validator: (pwd: string) => !lowEntropyRegexes.some((regex) => regex.test(pwd)),
setError: setPasswordErrorLowEntropy
}
];
const isBreached = await checkIsPasswordBreached(password);
@ -73,7 +77,7 @@ const passwordCheck = async ({
} else {
setPasswordErrorBreached(false);
}
tests.forEach((test) => {
if (!test.validator(password)) {
errorCheck = true;
@ -81,7 +85,7 @@ const passwordCheck = async ({
} else {
test.setError(false);
}
})
});
return errorCheck;
};

View File

@ -17,25 +17,25 @@ function bufferToHex(buffer: ArrayBuffer): string {
return hexParts.join("");
}
// see API details here: https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange
// in short, the pending password is hashed (SHA-1), the first 5 chars are sliced and compared against a ranged hash table
// this hash table is formed from the 5 char hash prefix (ie. 00000-FFFFF) so 16^5 results
// returns a hash table of 800-1000 results
// padding has been added to prevent MitM attacker determining which hash table was called by the response size
// the last 35 chars of the password hash are compared client-side against the table
// if there is a match, that password has been involved in a password breach (ie. pwnd) and should NOT be accepted
// the database consists of ~700 mln breached passwords and is continuously updated, including with law enforcement ingestion
// https://www.troyhunt.com/open-source-pwned-passwords-with-fbi-feed-and-225m-new-nca-passwords-is-now-live/
// see API details here: https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange
// in short, the pending password is hashed (SHA-1), the first 5 chars are sliced and compared against a ranged hash table
// this hash table is formed from the 5 char hash prefix (ie. 00000-FFFFF) so 16^5 results
// returns a hash table of 800-1000 results
// padding has been added to prevent MitM attacker determining which hash table was called by the response size
// the last 35 chars of the password hash are compared client-side against the table
// if there is a match, that password has been involved in a password breach (ie. pwnd) and should NOT be accepted
// the database consists of ~700 mln breached passwords and is continuously updated, including with law enforcement ingestion
// https://www.troyhunt.com/open-source-pwned-passwords-with-fbi-feed-and-225m-new-nca-passwords-is-now-live/
// The HIBP API follows NIST guidance (pg.14) https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf
// "When processing requests to establish and change memorized secrets, verifiers SHALL compare
// the prospective secrets against a list that contains values known to be commonly-used, expected,
// or compromised. For example, the list MAY include, but is not limited to:
// • Passwords obtained from previous breach corpuses.
// • Dictionary words.
// • Repetitive or sequential characters (e.g. aaaaaa, 1234abcd).
// • Context-specific words, such as the name of the service, the username, and derivatives
// thereof."
// The HIBP API follows NIST guidance (pg.14) https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf
// "When processing requests to establish and change memorized secrets, verifiers SHALL compare
// the prospective secrets against a list that contains values known to be commonly-used, expected,
// or compromised. For example, the list MAY include, but is not limited to:
// • Passwords obtained from previous breach corpuses.
// • Dictionary words.
// • Repetitive or sequential characters (e.g. aaaaaa, 1234abcd).
// • Context-specific words, such as the name of the service, the username, and derivatives
// thereof."
export const checkIsPasswordBreached = async (password: string): Promise<boolean> => {
const HAVE_I_BEEN_PWNED_API_URL = "https://api.pwnedpasswords.com";
@ -66,8 +66,8 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
response = await axios.get(rangedHashTableUri, {
headers: {
"Add-Padding": "true", // see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
"Content-Type": "text/plain",
},
"Content-Type": "text/plain"
}
});
if (response.status === 200) {
@ -76,9 +76,8 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
// check the last 35 hash chars to see if there's a match
const isBreachedPassword: boolean = responseData.includes(hashedPwd.slice(5, 40));
return isBreachedPassword;
}
retryAttempt += 1;
}
retryAttempt += 1;
} catch (err) {
if (!axios.isAxiosError(err)) {
throw err;
@ -88,14 +87,15 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
}
console.error(
`Received a non-200 response (${response ? response.status : "unknown"}) from the Pwnd Passwords API`
`Received a non-200 response (${
response ? response.status : "unknown"
}) from the Pwnd Passwords API`
);
return false;
} catch (err: any) {
console.error("An unexpected error has occurred:", err.message);
return false;
} finally {
// Clear the UTF-8 encoded password from memory
if (encodedPwd) {

View File

@ -1,5 +1,11 @@
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes";
import {
escapeCharRegex,
letterCharRegex,
lowEntropyRegexes,
numAndSpecialCharRegex,
repeatedCharRegex
} from "./passwordRegexes";
type Errors = {
tooShort?: string;
@ -44,40 +50,38 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom
{
name: "tooShort",
validator: (pwd: string) => pwd.length >= 14,
errorText: "at least 14 characters",
errorText: "at least 14 characters"
},
{
name: "tooLong",
validator: (pwd: string) => pwd.length < 101,
errorText: "at most 100 characters",
errorText: "at most 100 characters"
},
{
name: "noLetterChar",
validator: (pwd: string) => letterCharRegex.test(pwd),
errorText: "at least 1 letter character",
errorText: "at least 1 letter character"
},
{
name: "noNumOrSpecialChar",
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
errorText: "at least 1 number or special character",
errorText: "at least 1 number or special character"
},
{
name: "repeatedChar",
validator: (pwd: string) => !repeatedCharRegex.test(pwd),
errorText: "at most 3 repeated, consecutive characters",
errorText: "at most 3 repeated, consecutive characters"
},
{
name: "escapeChar",
validator: (pwd: string) => !escapeCharRegex.test(pwd),
errorText: "No escape characters allowed.",
errorText: "No escape characters allowed."
},
{
name: "lowEntropy",
validator: (pwd: string) => (
!lowEntropyRegexes.some(regex => regex.test(pwd))
),
errorText: "Password contains personal info.",
},
validator: (pwd: string) => !lowEntropyRegexes.some((regex) => regex.test(pwd)),
errorText: "Password contains personal info."
}
];
const isBreached = await checkIsPasswordBreached(password);
@ -96,4 +100,4 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom
return Object.keys(errors).length > 0;
};
export default checkPassword;
export default checkPassword;

View File

@ -1,6 +1,7 @@
// This regex covers letters (case insensitive) for the top 50 most spoken languages
/* eslint-disable no-misleading-character-class */
export const letterCharRegex = /[A-Za-z\u00C0-\u00D6\u00D8-\u00DE\u00DF-\u00F6\u00F8-\u00FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00C7\u00FC\u00FB\u00EB\u00E7]/u;
export const letterCharRegex =
/[A-Za-z\u00C0-\u00D6\u00D8-\u00DE\u00DF-\u00F6\u00F8-\u00FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00C7\u00FC\u00FB\u00EB\u00E7]/u;
// This regex covers digits, special characters, symbols, and emojis.
export const numAndSpecialCharRegex = /[\d!@#$%^&*(),.?":{}|<>]|[^\p{L}\p{N}\s]/u;
@ -32,5 +33,5 @@ export const lowEntropyRegexes = [
/\b(?:[A-Z0-9]{7,10}|[A-Z0-9]{10,11}|[A-Z0-9]{7,10})\b/,
// US social security number
/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/,
];
/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/
];

View File

@ -3,9 +3,7 @@ import crypto from "crypto";
import jsrp from "jsrp";
import { issueBackupPrivateKey ,
srp1
} from "@app/hooks/api/auth/queries";
import { issueBackupPrivateKey, srp1 } from "@app/hooks/api/auth/queries";
import generateBackupPDF from "../generateBackupPDF";
import Aes256Gcm from "./aes-256-gcm";
@ -97,7 +95,6 @@ const issueBackupKey = async ({
generatedKey
});
setBackupKeyIssued(true);
} catch {
setBackupKeyError(true);
}

View File

@ -3,11 +3,7 @@ import { useRouter } from "next/router";
import { useUser } from "@app/context";
import {
boot as bootIntercom,
load as loadIntercom,
update as updateIntercom,
} from "./intercom";
import { boot as bootIntercom, load as loadIntercom, update as updateIntercom } from "./intercom";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const IntercomProvider = ({ children }: { children: any }) => {
@ -16,7 +12,11 @@ export const IntercomProvider = ({ children }: { children: any }) => {
if (typeof window !== "undefined") {
loadIntercom();
bootIntercom({name: `${user?.firstName || ""} ${user?.lastName || ""}`, email: user?.email || "", created_at: Math.floor(((new Date(user?.createdAt))?.getTime() || 0) / 1000)});
bootIntercom({
name: `${user?.firstName || ""} ${user?.lastName || ""}`,
email: user?.email || "",
created_at: Math.floor((new Date(user?.createdAt)?.getTime() || 0) / 1000)
});
}
useEffect(() => {
@ -37,4 +37,4 @@ export const IntercomProvider = ({ children }: { children: any }) => {
}, [router.events]);
return children;
};
};

View File

@ -1,5 +1,5 @@
export const isValidHexColor = (hexColor: string) => {
const hexColorPattern = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
return hexColorPattern.test(hexColor);
}
export const isValidHexColor = (hexColor: string) => {
const hexColorPattern = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
return hexColorPattern.test(hexColor);
};

View File

@ -17,10 +17,9 @@ export const saveTokenToLocalStorage = ({
encryptedPrivateKey,
iv,
tag,
privateKey,
privateKey
}: Props) => {
try {
if (protectedKey) {
localStorage.removeItem("protectedKey");
localStorage.setItem("protectedKey", protectedKey);
@ -62,9 +61,7 @@ export const saveTokenToLocalStorage = ({
}
} catch (err) {
if (err instanceof Error) {
throw new Error(
`Unable to send the tokens in local storage:${ err.message}`
);
throw new Error(`Unable to send the tokens in local storage:${err.message}`);
}
}
};