Fix merge conflicts

This commit is contained in:
Tuan Dang
2023-01-14 21:27:55 +07:00
114 changed files with 7815 additions and 1292 deletions
+2 -2
View File
@@ -46,7 +46,7 @@ export default function ListBox({
>
<div className="flex flex-row">
{text}
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300 capitalize">
{' '}
{selected}
</span>
@@ -69,7 +69,7 @@ export default function ListBox({
<Listbox.Option
key={personIdx}
className={({ active, selected }) =>
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md capitalize ${
selected ? 'bg-white/10 text-gray-400 font-bold' : ''
} ${
active && !selected
@@ -8,7 +8,6 @@ import nacl from "tweetnacl";
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
import { envMapping } from "../../../public/data/frequentConstants";
import {
decryptAssymmetric,
encryptAssymmetric,
@@ -34,11 +33,12 @@ const AddServiceTokenDialog = ({
workspaceId,
workspaceName,
serviceTokens,
environments,
setServiceTokens
}) => {
const [serviceToken, setServiceToken] = useState("");
const [serviceTokenName, setServiceTokenName] = useState("");
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
const [selectedServiceTokenEnv, setSelectedServiceTokenEnv] = useState(environments?.[0]);
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
const { t } = useTranslation();
@@ -66,7 +66,7 @@ const AddServiceTokenDialog = ({
let newServiceToken = await addServiceToken({
name: serviceTokenName,
workspaceId,
environment: envMapping[serviceTokenEnv],
environment: selectedServiceTokenEnv?.slug ? selectedServiceTokenEnv.slug : environments[0]?.name,
expiresIn: expiryMapping[serviceTokenExpiresIn],
encryptedKey: ciphertext,
iv,
@@ -101,155 +101,159 @@ const AddServiceTokenDialog = ({
};
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-bunker-700 bg-opacity-80" />
<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">
<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'
>
{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">
{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"
as='h3'
className='text-lg font-medium leading-6 text-gray-400 z-50'
>
{t("section-token:add-dialog.title", {
{t('section-token:add-dialog.title', {
target: workspaceName,
})}
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
{t("section-token:add-dialog.description")}
<div className='mt-2 mb-4'>
<div className='flex flex-col'>
<p className='text-sm text-gray-500'>
{t('section-token:add-dialog.description')}
</p>
</div>
</div>
<div className="max-h-28 mb-2">
<div className='max-h-28 mb-2'>
<InputField
label={t("section-token:add-dialog.name")}
label={t('section-token:add-dialog.name')}
onChangeHandler={setServiceTokenName}
type="varName"
type='varName'
value={serviceTokenName}
placeholder=""
placeholder=''
isRequired
/>
</div>
<div className="max-h-28 mb-2">
<div className='max-h-28 mb-2'>
<ListBox
selected={serviceTokenEnv}
onChange={setServiceTokenEnv}
data={[
"Development",
"Staging",
"Production",
"Testing",
]}
selected={selectedServiceTokenEnv?.name ? selectedServiceTokenEnv?.name : environments[0]?.name}
data={environments.map(({ name }) => name)}
onChange={(envName) =>
setSelectedServiceTokenEnv(
environments.find(
({ name }) => envName === name
) || {
name: 'unknown',
slug: 'unknown',
}
)
}
isFull={true}
text={`${t("common:environment")}: `}
text={`${t('common:environment')}: `}
/>
</div>
<div className="max-h-28">
<div className='max-h-28'>
<ListBox
selected={serviceTokenExpiresIn}
onChange={setServiceTokenExpiresIn}
data={[
"1 day",
"7 days",
"1 month",
"6 months",
"12 months",
'1 day',
'7 days',
'1 month',
'6 months',
'12 months',
]}
isFull={true}
text={`${t("common:expired-in")}: `}
text={`${t('common:expired-in')}: `}
/>
</div>
<div className="max-w-max">
<div className="mt-6 flex flex-col justify-start w-max">
<div className='max-w-max'>
<div className='mt-6 flex flex-col justify-start w-max'>
<Button
onButtonPressed={() => generateServiceToken()}
color="mineshaft"
text={t("section-token:add-dialog.add")}
textDisabled={t("section-token:add-dialog.add")}
size="md"
active={serviceTokenName == "" ? false : true}
color='mineshaft'
text={t('section-token:add-dialog.add')}
textDisabled={t('section-token:add-dialog.add')}
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.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"
as='h3'
className='text-lg font-medium leading-6 text-gray-400 z-50'
>
{t("section-token:add-dialog.copy-service-token")}
{t('section-token:add-dialog.copy-service-token')}
</Dialog.Title>
<div className="mt-2 mb-4">
<div className="flex flex-col">
<p className="text-sm text-gray-500">
<div className='mt-2 mb-4'>
<div className='flex flex-col'>
<p className='text-sm text-gray-500'>
{t(
"section-token:add-dialog.copy-service-token-description"
'section-token:add-dialog.copy-service-token-description'
)}
</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-20">
<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-20'>
<input
type="text"
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"
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">
<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">
<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"
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"
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">
{t("common:click-to-copy")}
<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'>
{t('common:click-to-copy')}
</span>
</div>
</div>
</div>
<div className="mt-6 flex flex-col justify-start w-max">
<div className='mt-6 flex flex-col justify-start w-max'>
<Button
onButtonPressed={() => closeAddServiceTokenModal()}
color="mineshaft"
text="Close"
size="md"
color='mineshaft'
text='Close'
size='md'
/>
</div>
</Dialog.Panel>
@@ -0,0 +1,145 @@
import { FormEventHandler, Fragment, useEffect, useState } from 'react';
import { Dialog, Transition } from '@headlessui/react';
import Button from '../buttons/Button';
import InputField from '../InputField';
type FormFields = { name: string; slug: string };
type Props = {
isOpen?: boolean;
isEditMode?: boolean;
// on edit mode load up initial values
initialValues?: FormFields;
onClose: () => void;
onCreateSubmit: (data: FormFields) => void;
onEditSubmit: (data: FormFields) => void;
};
// TODO: Migrate to better form management and validation. Preferable react-hook-form + yup
/**
* The dialog modal for when the user wants to create a new workspace
* @param {*} param0
* @returns
*/
export const AddUpdateEnvironmentDialog = ({
isOpen,
onClose,
onCreateSubmit,
onEditSubmit,
initialValues,
isEditMode,
}: Props) => {
const [formInput, setFormInput] = useState<FormFields>({
name: '',
slug: '',
});
// This use effect can be removed when the unmount is happening from outside the component
// When unmount happens outside state gets unmounted also
useEffect(() => {
setFormInput(initialValues || { name: '', slug: '' });
}, [isOpen]);
// REFACTOR: Move to react-hook-form with yup for better form management
const onInputChange = (fieldName: string, fieldValue: string) => {
setFormInput((state) => ({ ...state, [fieldName]: fieldValue }));
};
const onFormSubmit: FormEventHandler = (e) => {
e.preventDefault();
const data = {
name: formInput.name.toLowerCase(),
slug: formInput.slug.toLowerCase(),
};
if (isEditMode) {
onEditSubmit(data);
return;
}
onCreateSubmit(data);
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative z-20' onClose={onClose}>
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='ease-out duration-150'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<div className='fixed inset-0 bg-black bg-opacity-70' />
</Transition.Child>
<div className='fixed inset-0 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'
>
{isEditMode
? 'Update environment'
: 'Create a new environment'}
</Dialog.Title>
<form onSubmit={onFormSubmit}>
<div className='max-h-28 mt-4'>
<InputField
label='Environment Name'
onChangeHandler={(val) => onInputChange('name', val)}
type='varName'
value={formInput.name}
placeholder=''
isRequired
// error={error.length > 0}
// errorText={error}
/>
</div>
<div className='max-h-28 mt-4'>
<InputField
label='Environment Slug'
onChangeHandler={(val) => onInputChange('slug', val)}
type='varName'
value={formInput.slug}
placeholder=''
isRequired
// error={error.length > 0}
// errorText={error}
/>
</div>
<p className='text-xs text-gray-500 mt-2'>
Slugs are shorthands used in cli to access environment
</p>
<div className='mt-4 max-w-min'>
<Button
onButtonPressed={() => null}
type='submit'
color='mineshaft'
text={isEditMode ? 'Update' : 'Create'}
active={formInput.name !== '' && formInput.slug !== ''}
size='md'
/>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
@@ -0,0 +1,104 @@
import { Fragment, useEffect, useState } from 'react';
import { Dialog, Transition } from '@headlessui/react';
import InputField from '../InputField';
// REFACTOR: Move all these modals into one reusable one
type Props = {
isOpen?: boolean;
onClose: ()=>void;
title: string;
onSubmit:()=>void;
deleteKey?:string;
}
const DeleteActionModal = ({
isOpen,
onClose,
title,
onSubmit,
deleteKey
}:Props) => {
const [deleteInputField, setDeleteInputField] = useState("")
useEffect(() => {
setDeleteInputField("");
}, [isOpen]);
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as='div' className='relative z-10' onClose={onClose}>
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='ease-in duration-150'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<div className='fixed inset-0 bg-black bg-opacity-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'
>
{title}
</Dialog.Title>
<div className='mt-2'>
<p className='text-sm text-gray-500'>
This action is irrevertible.
</p>
</div>
<div className='mt-2'>
<InputField
isRequired
label={`Type ${deleteKey} to delete the resource`}
onChangeHandler={(val) => setDeleteInputField(val)}
value={deleteInputField}
type='text'
/>
</div>
<div className='mt-6'>
<button
type='button'
className='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={onSubmit}
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'
onClick={onClose}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default DeleteActionModal;
@@ -0,0 +1,167 @@
import { faPencil, faPlus, faX } from '@fortawesome/free-solid-svg-icons';
import { usePopUp } from '../../../hooks/usePopUp';
import Button from '../buttons/Button';
import { AddUpdateEnvironmentDialog } from '../dialog/AddUpdateEnvironmentDialog';
import DeleteActionModal from '../dialog/DeleteActionModal';
type Env = { name: string; slug: string };
type Props = {
data: Env[];
onCreateEnv: (arg0: Env) => Promise<void>;
onUpdateEnv: (oldSlug: string, arg0: Env) => Promise<void>;
onDeleteEnv: (slug: string) => Promise<void>;
};
const EnvironmentTable = ({
data = [],
onCreateEnv,
onDeleteEnv,
onUpdateEnv,
}: Props) => {
const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
'createUpdateEnv',
'deleteEnv',
] as const);
const onEnvCreateCB = async (env: Env) => {
try {
await onCreateEnv(env);
handlePopUpClose('createUpdateEnv');
} catch (error) {
console.error(error);
}
};
const onEnvUpdateCB = async (env: Env) => {
try {
await onUpdateEnv(
(popUp.createUpdateEnv?.data as Pick<Env, 'slug'>)?.slug,
env
);
handlePopUpClose('createUpdateEnv');
} catch (error) {
console.error(error);
}
};
const onEnvDeleteCB = async () => {
try {
await onDeleteEnv(
(popUp.deleteEnv?.data as Pick<Env, 'slug'>)?.slug
);
handlePopUpClose('deleteEnv');
} catch (error) {
console.error(error);
}
};
return (
<>
<div className='flex flex-row justify-between w-full'>
<div className='flex flex-col w-full'>
<p className='text-xl font-semibold mb-3'>Project Environments</p>
<p className='text-base text-gray-400 mb-4'>
Choose which environments will show up in your dashboard like
development, staging, production
</p>
<p className='text-sm mr-1 text-gray-500 self-start'>
Note: the text in slugs shows how these environmant should be
accessed in CLI.
</p>
</div>
<div className='w-48'>
<Button
text='Add New Env'
onButtonPressed={() => handlePopUpOpen('createUpdateEnv')}
color='mineshaft'
icon={faPlus}
size='md'
/>
</div>
</div>
<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'>Name</th>
<th className='text-left pl-6 pt-2.5 pb-2'>Slug</th>
<th></th>
</tr>
</thead>
<tbody>
{data?.length > 0 ? (
data.map(({ name, slug }) => {
return (
<tr
key={name}
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 capitalize'>
{name}
</td>
<td className='pl-6 py-2 border-mineshaft-700 border-t text-gray-300'>
{slug}
</td>
<td className='py-2 border-mineshaft-700 border-t flex'>
<div className='opacity-50 hover:opacity-100 duration-200 flex items-center mr-8'>
<Button
onButtonPressed={() =>
handlePopUpOpen('createUpdateEnv', { name, slug })
}
color='red'
size='icon-sm'
icon={faPencil}
/>
</div>
<div className='opacity-50 hover:opacity-100 duration-200 flex items-center'>
<Button
onButtonPressed={() =>
handlePopUpOpen('deleteEnv', { name, slug })
}
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 environmants found
</td>
</tr>
)}
</tbody>
</table>
<DeleteActionModal
isOpen={popUp['deleteEnv'].isOpen}
title={`Are you sure want to delete ${
(popUp?.deleteEnv?.data as { name: string })?.name || ' '
}?`}
deleteKey={(popUp?.deleteEnv?.data as { slug: string })?.slug || ''}
onClose={() => handlePopUpClose('deleteEnv')}
onSubmit={onEnvDeleteCB}
/>
<AddUpdateEnvironmentDialog
isOpen={popUp.createUpdateEnv.isOpen}
isEditMode={Boolean(popUp.createUpdateEnv?.data)}
initialValues={popUp?.createUpdateEnv?.data as any}
onClose={() => handlePopUpClose('createUpdateEnv')}
onCreateSubmit={onEnvCreateCB}
onEditSubmit={onEnvUpdateCB}
/>
</div>
</>
);
};
export default EnvironmentTable;
@@ -3,7 +3,6 @@ import { faX } from '@fortawesome/free-solid-svg-icons';
import { useNotificationContext } from '~/components/context/Notifications/NotificationProvider';
import deleteServiceToken from "../../../pages/api/serviceToken/deleteServiceToken";
import { reverseEnvMapping } from '../../../public/data/frequentConstants';
import guidGenerator from '../../utilities/randomId';
import Button from '../buttons/Button';
@@ -60,7 +59,7 @@ const ServiceTokenTable = ({ data, workspaceName, setServiceTokens }: ServiceTok
{workspaceName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{reverseEnvMapping[row.environment]}
{row.environment}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{new Date(row.expiresAt).toUTCString()}