mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
feat(frontend): migrated to ts completed.
This commit is contained in:
@ -1,8 +1,8 @@
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { publicPaths } from "~/const";
|
||||
import checkAuth from "~/pages/api/auth/CheckAuth";
|
||||
import { publicPaths } from '~/const';
|
||||
import checkAuth from '~/pages/api/auth/CheckAuth';
|
||||
|
||||
// #TODO: finish spinner only when the data loads fully
|
||||
// #TODO: Redirect somewhere if the page does not exist
|
||||
@ -11,7 +11,7 @@ type Prop = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function RouteGuard({ children }: Prop) {
|
||||
export default function RouteGuard({ children }: Prop): JSX.Element {
|
||||
const router = useRouter();
|
||||
const [authorized, setAuthorized] = useState(false);
|
||||
|
||||
@ -25,16 +25,16 @@ export default function RouteGuard({ children }: Prop) {
|
||||
// #TODO: add the loading page when not yet authorized.
|
||||
const hideContent = () => setAuthorized(false);
|
||||
// const onError = () => setAuthorized(true)
|
||||
router.events.on("routeChangeStart", hideContent);
|
||||
router.events.on('routeChangeStart', hideContent);
|
||||
// router.events.on("routeChangeError", onError);
|
||||
|
||||
// on route change complete - run auth check
|
||||
router.events.on("routeChangeComplete", authCheck);
|
||||
router.events.on('routeChangeComplete', authCheck);
|
||||
|
||||
// unsubscribe from events in useEffect return function
|
||||
return () => {
|
||||
router.events.off("routeChangeStart", hideContent);
|
||||
router.events.off("routeChangeComplete", authCheck);
|
||||
router.events.off('routeChangeStart', hideContent);
|
||||
router.events.off('routeChangeComplete', authCheck);
|
||||
// router.events.off("routeChangeError", onError);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -43,9 +43,9 @@ export default function RouteGuard({ children }: Prop) {
|
||||
/**
|
||||
* redirect to login page if accessing a private page and not logged in
|
||||
*/
|
||||
async function authCheck(url:string) {
|
||||
async function authCheck(url: string) {
|
||||
// Make sure that we don't redirect when the user is on the following pages.
|
||||
const path = "/" + url.split("?")[0].split("/")[1];
|
||||
const path = '/' + url.split('?')[0].split('/')[1];
|
||||
|
||||
// Check if the user is authenticated
|
||||
const response = await checkAuth();
|
||||
@ -54,21 +54,21 @@ export default function RouteGuard({ children }: Prop) {
|
||||
if (!publicPaths.includes(path)) {
|
||||
try {
|
||||
if (response.status !== 200) {
|
||||
router.push("/login");
|
||||
console.log("Unauthorized to access.");
|
||||
router.push('/login');
|
||||
console.log('Unauthorized to access.');
|
||||
setAuthorized(false);
|
||||
} else {
|
||||
setAuthorized(true);
|
||||
console.log("Authorized to access.");
|
||||
console.log('Authorized to access.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"Error (probably the authCheck route is stuck again...):",
|
||||
'Error (probably the authCheck route is stuck again...):',
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return children;
|
||||
return children as JSX.Element;
|
||||
}
|
||||
|
@ -1,88 +0,0 @@
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import Button from "../buttons/Button";
|
||||
|
||||
const ActivateBotDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
selectedIntegrationOption,
|
||||
handleBotActivate,
|
||||
handleIntegrationOption
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
// 1. activate bot
|
||||
await handleBotActivate();
|
||||
|
||||
// 2. start integration
|
||||
await handleIntegrationOption({
|
||||
integrationOption: selectedIntegrationOption
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
closeModal();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400"
|
||||
>
|
||||
{t("integrations:grant-access-to-secrets")}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("integrations:why-infisical-needs-access")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text={t("integrations:grant-access-button")}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ActivateBotDialog;
|
98
frontend/components/basic/dialog/ActivateBotDialog.tsx
Normal file
98
frontend/components/basic/dialog/ActivateBotDialog.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import { Fragment } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
|
||||
import Button from '../buttons/Button';
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
selectedIntegrationOption: never[] | null;
|
||||
handleBotActivate: () => Promise<void>;
|
||||
handleIntegrationOption: (arg: { integrationOption: never[] }) => void;
|
||||
};
|
||||
|
||||
const ActivateBotDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
selectedIntegrationOption,
|
||||
handleBotActivate,
|
||||
handleIntegrationOption,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
// 1. activate bot
|
||||
await handleBotActivate();
|
||||
|
||||
// type check
|
||||
if (!selectedIntegrationOption) return;
|
||||
// 2. start integration
|
||||
await handleIntegrationOption({
|
||||
integrationOption: selectedIntegrationOption,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
closeModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as='div' className='relative z-10' onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0'
|
||||
enterTo='opacity-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
>
|
||||
<div className='fixed inset-0 bg-black bg-opacity-70' />
|
||||
</Transition.Child>
|
||||
<div className='fixed inset-0 overflow-y-auto'>
|
||||
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0 scale-95'
|
||||
enterTo='opacity-100 scale-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100 scale-100'
|
||||
leaveTo='opacity-0 scale-95'
|
||||
>
|
||||
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'>
|
||||
<Dialog.Title
|
||||
as='h3'
|
||||
className='text-lg font-medium leading-6 text-gray-400'
|
||||
>
|
||||
{t('integrations:grant-access-to-secrets')}
|
||||
</Dialog.Title>
|
||||
<div className='mt-2 mb-2'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
{t('integrations:why-infisical-needs-access')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='mt-6 max-w-max'>
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color='mineshaft'
|
||||
text={t('integrations:grant-access-button') as string}
|
||||
size='md'
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActivateBotDialog;
|
@ -1,217 +0,0 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import addAPIKey from "~/pages/api/apiKey/addAPIKey";
|
||||
|
||||
import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
import ListBox from "../Listbox";
|
||||
|
||||
const expiryMapping = {
|
||||
"1 day": 86400,
|
||||
"7 days": 604800,
|
||||
"1 month": 2592000,
|
||||
"6 months": 15552000,
|
||||
"12 months": 31104000,
|
||||
};
|
||||
|
||||
const crypto = require('crypto');
|
||||
|
||||
// TODO: convert to TS
|
||||
const AddApiKeyDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
apiKeys,
|
||||
setApiKeys
|
||||
}) => {
|
||||
const [apiKey, setApiKey] = useState("");
|
||||
const [apiKeyName, setApiKeyName] = useState("");
|
||||
const [apiKeyExpiresIn, setApiKeyExpiresIn] = useState("1 day");
|
||||
const [apiKeyCopied, setApiKeyCopied] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const generateAPIKey = async () => {
|
||||
const newApiKey = await addAPIKey({
|
||||
name: apiKeyName,
|
||||
expiresIn: expiryMapping[apiKeyExpiresIn]
|
||||
});
|
||||
|
||||
setApiKeys([...apiKeys, newApiKey.apiKeyData])
|
||||
setApiKey(newApiKey.apiKey);
|
||||
};
|
||||
|
||||
function copyToClipboard() {
|
||||
// Get the text field
|
||||
var copyText = document.getElementById("apiKey");
|
||||
|
||||
// Select the text field
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
||||
|
||||
// Copy the text inside the text field
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
|
||||
setApiKeyCopied(true);
|
||||
setTimeout(() => setApiKeyCopied(false), 2000);
|
||||
// Alert the copied text
|
||||
// alert("Copied the text: " + copyText.value);
|
||||
}
|
||||
|
||||
const closeAddApiKeyModal = () => {
|
||||
closeModal();
|
||||
setApiKeyName("");
|
||||
setApiKey("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-bunker-700 bg-opacity-80" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
{apiKey == "" ? (
|
||||
<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"
|
||||
>
|
||||
{t("section-api-key:add-dialog.title")}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("section-api-key:add-dialog.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-28 mb-2">
|
||||
<InputField
|
||||
label={t("section-api-key:add-dialog.name")}
|
||||
onChangeHandler={setApiKeyName}
|
||||
type="varName"
|
||||
value={apiKeyName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<ListBox
|
||||
selected={apiKeyExpiresIn}
|
||||
onChange={setApiKeyExpiresIn}
|
||||
data={[
|
||||
"1 day",
|
||||
"7 days",
|
||||
"1 month",
|
||||
"6 months",
|
||||
"12 months",
|
||||
]}
|
||||
isFull={true}
|
||||
text={`${t("common:expired-in")}: `}
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() => generateAPIKey()}
|
||||
color="mineshaft"
|
||||
text={t("section-api-key:add-dialog.add")}
|
||||
textDisabled={t("section-api-key:add-dialog.add")}
|
||||
size="md"
|
||||
active={apiKeyName == "" ? false : true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
) : (
|
||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
{t("section-api-key: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">
|
||||
{t(
|
||||
"section-api-key: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">
|
||||
<input
|
||||
type="text"
|
||||
value={apiKey}
|
||||
disabled={true}
|
||||
id="apiKey"
|
||||
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">
|
||||
{apiKey}
|
||||
</div>
|
||||
<div className="group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200">
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200"
|
||||
>
|
||||
{apiKeyCopied ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="pr-0.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCopy} />
|
||||
)}
|
||||
</button>
|
||||
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
||||
{t("common:click-to-copy")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={() => closeAddApiKeyModal()}
|
||||
color="mineshaft"
|
||||
text="Close"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
)}
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddApiKeyDialog;
|
225
frontend/components/basic/dialog/AddApiKeyDialog.tsx
Normal file
225
frontend/components/basic/dialog/AddApiKeyDialog.tsx
Normal file
@ -0,0 +1,225 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
|
||||
import addAPIKey from '~/pages/api/apiKey/addAPIKey';
|
||||
|
||||
import Button from '../buttons/Button';
|
||||
import InputField from '../InputField';
|
||||
import ListBox from '../Listbox';
|
||||
|
||||
const expiryMapping = {
|
||||
'1 day': 86400,
|
||||
'7 days': 604800,
|
||||
'1 month': 2592000,
|
||||
'6 months': 15552000,
|
||||
'12 months': 31104000,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
// TODO: These never and any will be filled by single folder that contains types and hooks about the API
|
||||
apiKeys: any[];
|
||||
setApiKeys: (arg: any[]) => void;
|
||||
};
|
||||
|
||||
// TODO: convert to TS
|
||||
const AddApiKeyDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
apiKeys,
|
||||
setApiKeys,
|
||||
}: Props) => {
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [apiKeyName, setApiKeyName] = useState('');
|
||||
const [apiKeyExpiresIn, setApiKeyExpiresIn] = useState('1 day');
|
||||
const [apiKeyCopied, setApiKeyCopied] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const generateAPIKey = async () => {
|
||||
const newApiKey = await addAPIKey({
|
||||
name: apiKeyName,
|
||||
expiresIn: expiryMapping[apiKeyExpiresIn as keyof typeof expiryMapping],
|
||||
});
|
||||
|
||||
setApiKeys([...apiKeys, newApiKey.apiKeyData]);
|
||||
setApiKey(newApiKey.apiKey);
|
||||
};
|
||||
|
||||
function copyToClipboard() {
|
||||
// Get the text field
|
||||
const copyText = document.getElementById('apiKey') as HTMLInputElement;
|
||||
|
||||
// Select the text field
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
||||
|
||||
// Copy the text inside the text field
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
|
||||
setApiKeyCopied(true);
|
||||
setTimeout(() => setApiKeyCopied(false), 2000);
|
||||
// Alert the copied text
|
||||
// alert("Copied the text: " + copyText.value);
|
||||
}
|
||||
|
||||
const closeAddApiKeyModal = () => {
|
||||
closeModal();
|
||||
setApiKeyName('');
|
||||
setApiKey('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='z-50'>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as='div' className='relative' onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0'
|
||||
enterTo='opacity-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
>
|
||||
<div className='fixed inset-0 bg-bunker-700 bg-opacity-80' />
|
||||
</Transition.Child>
|
||||
|
||||
<div className='fixed inset-0 overflow-y-auto'>
|
||||
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0 scale-95'
|
||||
enterTo='opacity-100 scale-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100 scale-100'
|
||||
leaveTo='opacity-0 scale-95'
|
||||
>
|
||||
{apiKey == '' ? (
|
||||
<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'
|
||||
>
|
||||
{t('section-api-key:add-dialog.title')}
|
||||
</Dialog.Title>
|
||||
<div className='mt-2 mb-4'>
|
||||
<div className='flex flex-col'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
{t('section-api-key:add-dialog.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='max-h-28 mb-2'>
|
||||
<InputField
|
||||
label={t('section-api-key:add-dialog.name')}
|
||||
onChangeHandler={setApiKeyName}
|
||||
type='varName'
|
||||
value={apiKeyName}
|
||||
placeholder=''
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className='max-h-28'>
|
||||
<ListBox
|
||||
selected={apiKeyExpiresIn}
|
||||
onChange={setApiKeyExpiresIn}
|
||||
data={[
|
||||
'1 day',
|
||||
'7 days',
|
||||
'1 month',
|
||||
'6 months',
|
||||
'12 months',
|
||||
]}
|
||||
isFull={true}
|
||||
text={`${t('common:expired-in')}: `}
|
||||
/>
|
||||
</div>
|
||||
<div className='max-w-max'>
|
||||
<div className='mt-6 flex flex-col justify-start w-max'>
|
||||
<Button
|
||||
onButtonPressed={() => generateAPIKey()}
|
||||
color='mineshaft'
|
||||
text={t('section-api-key:add-dialog.add') as string}
|
||||
textDisabled={
|
||||
t('section-api-key:add-dialog.add') as string
|
||||
}
|
||||
size='md'
|
||||
active={apiKeyName == '' ? false : true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
) : (
|
||||
<Dialog.Panel className='w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'>
|
||||
<Dialog.Title
|
||||
as='h3'
|
||||
className='text-lg font-medium leading-6 text-gray-400 z-50'
|
||||
>
|
||||
{t('section-api-key: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'>
|
||||
{t(
|
||||
'section-api-key: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'>
|
||||
<input
|
||||
type='text'
|
||||
value={apiKey}
|
||||
disabled={true}
|
||||
id='apiKey'
|
||||
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'>
|
||||
{apiKey}
|
||||
</div>
|
||||
<div className='group font-normal h-full relative inline-block text-gray-400 underline hover:text-primary duration-200'>
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className='h-full pl-3.5 pr-4 border-l border-white/20 py-2 hover:bg-white/[0.12] duration-200'
|
||||
>
|
||||
{apiKeyCopied ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className='pr-0.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCopy} />
|
||||
)}
|
||||
</button>
|
||||
<span className='absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm'>
|
||||
{t('common:click-to-copy')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-6 flex flex-col justify-start w-max'>
|
||||
<Button
|
||||
onButtonPressed={() => closeAddApiKeyModal()}
|
||||
color='mineshaft'
|
||||
text='Close'
|
||||
size='md'
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
)}
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddApiKeyDialog;
|
@ -1,99 +0,0 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import addIncidentContact from "~/pages/api/organization/addIncidentContact";
|
||||
|
||||
import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
|
||||
const AddIncidentContactDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
workspaceId,
|
||||
incidentContacts,
|
||||
setIncidentContacts,
|
||||
}) => {
|
||||
let [incidentContactEmail, setIncidentContactEmail] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const submit = () => {
|
||||
setIncidentContacts(
|
||||
incidentContacts?.length > 0
|
||||
? incidentContacts.concat([incidentContactEmail])
|
||||
: [incidentContactEmail]
|
||||
);
|
||||
addIncidentContact(
|
||||
localStorage.getItem("orgData.id"),
|
||||
incidentContactEmail
|
||||
);
|
||||
closeModal();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400"
|
||||
>
|
||||
{t("section-incident:add-dialog.title")}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("section-incident:add-dialog.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label={t("common:email")}
|
||||
onChangeHandler={setIncidentContactEmail}
|
||||
type="varName"
|
||||
value={incidentContactEmail}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 max-w-max">
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text={t("section-incident:add-dialog.add-incident")}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddIncidentContactDialog;
|
108
frontend/components/basic/dialog/AddIncidentContactDialog.tsx
Normal file
108
frontend/components/basic/dialog/AddIncidentContactDialog.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
|
||||
import addIncidentContact from '~/pages/api/organization/addIncidentContact';
|
||||
|
||||
import Button from '../buttons/Button';
|
||||
import InputField from '../InputField';
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
workspaceId: string;
|
||||
incidentContacts: string[];
|
||||
setIncidentContacts: (arg: string[]) => void;
|
||||
};
|
||||
|
||||
const AddIncidentContactDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
incidentContacts,
|
||||
setIncidentContacts,
|
||||
}: Props) => {
|
||||
const [incidentContactEmail, setIncidentContactEmail] = useState('');
|
||||
const { t } = useTranslation();
|
||||
|
||||
const submit = () => {
|
||||
setIncidentContacts(
|
||||
incidentContacts?.length > 0
|
||||
? incidentContacts.concat([incidentContactEmail])
|
||||
: [incidentContactEmail]
|
||||
);
|
||||
addIncidentContact(
|
||||
localStorage.getItem('orgData.id') as string,
|
||||
incidentContactEmail
|
||||
);
|
||||
closeModal();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as='div' className='relative z-10' onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0'
|
||||
enterTo='opacity-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
>
|
||||
<div className='fixed inset-0 bg-black bg-opacity-70' />
|
||||
</Transition.Child>
|
||||
|
||||
<div className='fixed inset-0 overflow-y-auto'>
|
||||
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0 scale-95'
|
||||
enterTo='opacity-100 scale-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100 scale-100'
|
||||
leaveTo='opacity-0 scale-95'
|
||||
>
|
||||
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'>
|
||||
<Dialog.Title
|
||||
as='h3'
|
||||
className='text-lg font-medium leading-6 text-gray-400'
|
||||
>
|
||||
{t('section-incident:add-dialog.title')}
|
||||
</Dialog.Title>
|
||||
<div className='mt-2 mb-2'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
{t('section-incident:add-dialog.description')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='max-h-28'>
|
||||
<InputField
|
||||
label={t('common:email')}
|
||||
onChangeHandler={setIncidentContactEmail}
|
||||
type='varName'
|
||||
value={incidentContactEmail}
|
||||
placeholder=''
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-6 max-w-max'>
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color='mineshaft'
|
||||
text={
|
||||
t('section-incident:add-dialog.add-incident') as string
|
||||
}
|
||||
size='md'
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddIncidentContactDialog;
|
@ -1,147 +0,0 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Trans, useTranslation } from "next-i18next";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import Button from "../buttons/Button";
|
||||
import ListBox from "../Listbox";
|
||||
|
||||
const AddProjectMemberDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
data,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
{data?.length > 0 ? (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
{t("section-members:add-dialog.add-member-to-project")}
|
||||
</Dialog.Title>
|
||||
) : (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||
>
|
||||
{t("section-members:add-dialog.already-all-invited")}
|
||||
</Dialog.Title>
|
||||
)}
|
||||
<div className="mt-2 mb-4">
|
||||
{data?.length > 0 ? (
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("section-members:add-dialog.user-will-email")}
|
||||
</p>
|
||||
<div className="">
|
||||
<Trans
|
||||
i18nKey="section-members:add-dialog.looking-add"
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" + router.query.id
|
||||
)
|
||||
}
|
||||
/>,
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
"/settings/org/" +
|
||||
router.query.id +
|
||||
"?invite"
|
||||
)
|
||||
}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">
|
||||
{t("section-members:add-dialog.add-user-org-first")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-28">
|
||||
{data?.length > 0 && (
|
||||
<ListBox
|
||||
selected={email ? email : data[0]}
|
||||
onChange={setEmail}
|
||||
data={data}
|
||||
isFull={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-max">
|
||||
{data?.length > 0 ? (
|
||||
<div className="mt-6 flex flex-col justify-start w-max">
|
||||
<Button
|
||||
onButtonPressed={submitModal}
|
||||
color="mineshaft"
|
||||
text={t("section-members:add-member")}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
router.push("/settings/org/" + router.query.id)
|
||||
}
|
||||
color="mineshaft"
|
||||
text={t("section-members:add-dialog.add-user-to-org")}
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddProjectMemberDialog;
|
159
frontend/components/basic/dialog/AddProjectMemberDialog.tsx
Normal file
159
frontend/components/basic/dialog/AddProjectMemberDialog.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
import { Fragment } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
|
||||
import Button from '../buttons/Button';
|
||||
import ListBox from '../Listbox';
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
submitModal: () => void;
|
||||
data: any;
|
||||
email: string;
|
||||
setEmail: (email: string) => void;
|
||||
};
|
||||
|
||||
const AddProjectMemberDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
data,
|
||||
email,
|
||||
setEmail,
|
||||
}: Props) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className='z-50'>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as='div' className='relative' onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0'
|
||||
enterTo='opacity-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
>
|
||||
<div className='fixed inset-0 bg-black bg-opacity-70' />
|
||||
</Transition.Child>
|
||||
|
||||
<div className='fixed inset-0 overflow-y-auto'>
|
||||
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0 scale-95'
|
||||
enterTo='opacity-100 scale-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100 scale-100'
|
||||
leaveTo='opacity-0 scale-95'
|
||||
>
|
||||
<Dialog.Panel className='w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'>
|
||||
{data?.length > 0 ? (
|
||||
<Dialog.Title
|
||||
as='h3'
|
||||
className='text-lg font-medium leading-6 text-gray-400 z-50'
|
||||
>
|
||||
{t('section-members:add-dialog.add-member-to-project')}
|
||||
</Dialog.Title>
|
||||
) : (
|
||||
<Dialog.Title
|
||||
as='h3'
|
||||
className='text-lg font-medium leading-6 text-gray-400 z-50'
|
||||
>
|
||||
{t('section-members:add-dialog.already-all-invited')}
|
||||
</Dialog.Title>
|
||||
)}
|
||||
<div className='mt-2 mb-4'>
|
||||
{data?.length > 0 ? (
|
||||
<div className='flex flex-col'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
{t('section-members:add-dialog.user-will-email')}
|
||||
</p>
|
||||
<div className=''>
|
||||
<Trans
|
||||
i18nKey='section-members:add-dialog.looking-add'
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<button
|
||||
type='button'
|
||||
className='inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/settings/org/' + router.query.id
|
||||
)
|
||||
}
|
||||
/>,
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<button
|
||||
type='button'
|
||||
className='ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
onClick={() =>
|
||||
router.push(
|
||||
'/settings/org/' +
|
||||
router.query.id +
|
||||
'?invite'
|
||||
)
|
||||
}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className='text-sm text-gray-500'>
|
||||
{t('section-members:add-dialog.add-user-org-first')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className='max-h-28'>
|
||||
{data?.length > 0 && (
|
||||
<ListBox
|
||||
selected={email ? email : data[0]}
|
||||
onChange={setEmail}
|
||||
data={data}
|
||||
isFull={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='max-w-max'>
|
||||
{data?.length > 0 ? (
|
||||
<div className='mt-6 flex flex-col justify-start w-max'>
|
||||
<Button
|
||||
onButtonPressed={submitModal}
|
||||
color='mineshaft'
|
||||
text={t('section-members:add-member') as string}
|
||||
size='md'
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
router.push('/settings/org/' + router.query.id)
|
||||
}
|
||||
color='mineshaft'
|
||||
text={
|
||||
t(
|
||||
'section-members:add-dialog.add-user-to-org'
|
||||
) as string
|
||||
}
|
||||
size='md'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddProjectMemberDialog;
|
@ -1,31 +1,39 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import nacl from "tweetnacl";
|
||||
import crypto from 'crypto';
|
||||
|
||||
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
|
||||
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||
import { Fragment, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
|
||||
import addServiceToken from '~/pages/api/serviceToken/addServiceToken';
|
||||
import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
|
||||
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
encryptSymmetric,
|
||||
} from "../../utilities/cryptography/crypto";
|
||||
import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
import ListBox from "../Listbox";
|
||||
} from '../../utilities/cryptography/crypto';
|
||||
import Button from '../buttons/Button';
|
||||
import InputField from '../InputField';
|
||||
import ListBox from '../Listbox';
|
||||
|
||||
const expiryMapping = {
|
||||
"1 day": 86400,
|
||||
"7 days": 604800,
|
||||
"1 month": 2592000,
|
||||
"6 months": 15552000,
|
||||
"12 months": 31104000,
|
||||
'1 day': 86400,
|
||||
'7 days': 604800,
|
||||
'1 month': 2592000,
|
||||
'6 months': 15552000,
|
||||
'12 months': 31104000,
|
||||
};
|
||||
|
||||
const crypto = require('crypto');
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
workspaceId: string;
|
||||
workspaceName: string;
|
||||
serviceTokens: any[];
|
||||
environments: Array<{ name: string; slug: string }>;
|
||||
setServiceTokens: (arg: any[]) => void;
|
||||
};
|
||||
|
||||
const AddServiceTokenDialog = ({
|
||||
isOpen,
|
||||
@ -34,12 +42,14 @@ const AddServiceTokenDialog = ({
|
||||
workspaceName,
|
||||
serviceTokens,
|
||||
environments,
|
||||
setServiceTokens
|
||||
}) => {
|
||||
const [serviceToken, setServiceToken] = useState("");
|
||||
const [serviceTokenName, setServiceTokenName] = useState("");
|
||||
const [selectedServiceTokenEnv, setSelectedServiceTokenEnv] = useState(environments?.[0]);
|
||||
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
||||
setServiceTokens,
|
||||
}: Props) => {
|
||||
const [serviceToken, setServiceToken] = useState('');
|
||||
const [serviceTokenName, setServiceTokenName] = useState('');
|
||||
const [selectedServiceTokenEnv, setSelectedServiceTokenEnv] = useState(
|
||||
environments?.[0]
|
||||
);
|
||||
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState('1 day');
|
||||
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -50,36 +60,37 @@ const AddServiceTokenDialog = ({
|
||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
||||
nonce: latestFileKey.latestKey.nonce,
|
||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
||||
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
||||
privateKey: localStorage.getItem('PRIVATE_KEY') as string,
|
||||
});
|
||||
|
||||
const randomBytes = crypto.randomBytes(16).toString('hex');
|
||||
const {
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
} = encryptSymmetric({
|
||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
||||
plaintext: key,
|
||||
key: randomBytes,
|
||||
});
|
||||
|
||||
let newServiceToken = await addServiceToken({
|
||||
const newServiceToken = await addServiceToken({
|
||||
name: serviceTokenName,
|
||||
workspaceId,
|
||||
environment: selectedServiceTokenEnv?.slug ? selectedServiceTokenEnv.slug : environments[0]?.name,
|
||||
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
||||
environment: selectedServiceTokenEnv?.slug
|
||||
? selectedServiceTokenEnv.slug
|
||||
: environments[0]?.name,
|
||||
expiresIn:
|
||||
expiryMapping[serviceTokenExpiresIn as keyof typeof expiryMapping],
|
||||
encryptedKey: ciphertext,
|
||||
iv,
|
||||
tag
|
||||
iv,
|
||||
tag,
|
||||
});
|
||||
|
||||
|
||||
setServiceTokens(serviceTokens.concat([newServiceToken.serviceTokenData]));
|
||||
setServiceToken(newServiceToken.serviceToken + "." + randomBytes);
|
||||
setServiceToken(newServiceToken.serviceToken + '.' + randomBytes);
|
||||
};
|
||||
|
||||
function copyToClipboard() {
|
||||
// Get the text field
|
||||
var copyText = document.getElementById("serviceToken");
|
||||
const copyText = document.getElementById(
|
||||
'serviceToken'
|
||||
) as HTMLInputElement;
|
||||
|
||||
// Select the text field
|
||||
copyText.select();
|
||||
@ -96,8 +107,8 @@ const AddServiceTokenDialog = ({
|
||||
|
||||
const closeAddServiceTokenModal = () => {
|
||||
closeModal();
|
||||
setServiceTokenName("");
|
||||
setServiceToken("");
|
||||
setServiceTokenName('');
|
||||
setServiceToken('');
|
||||
};
|
||||
|
||||
return (
|
||||
@ -156,7 +167,11 @@ const AddServiceTokenDialog = ({
|
||||
</div>
|
||||
<div className='max-h-28 mb-2'>
|
||||
<ListBox
|
||||
selected={selectedServiceTokenEnv?.name ? selectedServiceTokenEnv?.name : environments[0]?.name}
|
||||
selected={
|
||||
selectedServiceTokenEnv?.name
|
||||
? selectedServiceTokenEnv?.name
|
||||
: environments[0]?.name
|
||||
}
|
||||
data={environments.map(({ name }) => name)}
|
||||
onChange={(envName) =>
|
||||
setSelectedServiceTokenEnv(
|
||||
@ -192,8 +207,10 @@ const AddServiceTokenDialog = ({
|
||||
<Button
|
||||
onButtonPressed={() => generateServiceToken()}
|
||||
color='mineshaft'
|
||||
text={t('section-token:add-dialog.add')}
|
||||
textDisabled={t('section-token:add-dialog.add')}
|
||||
text={t('section-token:add-dialog.add') as string}
|
||||
textDisabled={
|
||||
t('section-token:add-dialog.add') as string
|
||||
}
|
||||
size='md'
|
||||
active={serviceTokenName == '' ? false : true}
|
||||
/>
|
@ -1,105 +1,115 @@
|
||||
import { Fragment } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Fragment } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
|
||||
import { STRIPE_PRODUCT_STARTER } from "../../utilities/config";
|
||||
import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
import { STRIPE_PRODUCT_STARTER } from '../../utilities/config';
|
||||
import Button from '../buttons/Button';
|
||||
import InputField from '../InputField';
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
submitModal: (email: string) => void;
|
||||
workspaceId: string;
|
||||
email: string;
|
||||
setEmail: (email: string) => void;
|
||||
currentPlan: string;
|
||||
orgName: string;
|
||||
};
|
||||
|
||||
const AddUserDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
email,
|
||||
workspaceId,
|
||||
setEmail,
|
||||
currentPlan,
|
||||
orgName,
|
||||
}) => {
|
||||
}: Props) => {
|
||||
const submit = () => {
|
||||
submitModal(email);
|
||||
};
|
||||
const router = useRouter();
|
||||
|
||||
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 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'
|
||||
>
|
||||
Invite others to {orgName}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2 mb-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
<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>
|
||||
{currentPlan == STRIPE_PRODUCT_STARTER && (
|
||||
<div className="flex flex-row">
|
||||
<div className='flex flex-row'>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
type='button'
|
||||
className='inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
onClick={() =>
|
||||
router.push("/settings/billing/" + router.query.id)
|
||||
router.push('/settings/billing/' + router.query.id)
|
||||
}
|
||||
>
|
||||
You can add up to 5 members on a Free tier.
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
type='button'
|
||||
className='ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
onClick={() =>
|
||||
router.push("/settings/billing/" + router.query.id)
|
||||
router.push('/settings/billing/' + router.query.id)
|
||||
}
|
||||
>
|
||||
Upgrade now.
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 max-w-max">
|
||||
<div className='mt-4 max-w-max'>
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color="mineshaft"
|
||||
text="Invite"
|
||||
size="md"
|
||||
color='mineshaft'
|
||||
text='Invite'
|
||||
size='md'
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
@ -1,15 +1,23 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
import { Checkbox } from "../table/Checkbox";
|
||||
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
submitModal: (email: string, addAllUser: boolean) => void;
|
||||
workspaceName: string;
|
||||
setWorkspaceName: (workspaceName: string) => void;
|
||||
error: boolean;
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* The dialog modal for when the user wants to create a new workspace
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const AddWorkspaceDialog = ({
|
||||
isOpen,
|
||||
@ -19,7 +27,7 @@ const AddWorkspaceDialog = ({
|
||||
setWorkspaceName,
|
||||
error,
|
||||
loading,
|
||||
}) => {
|
||||
}:Props) => {
|
||||
const [addAllUsers, setAddAllUsers] = useState(true);
|
||||
const submit = () => {
|
||||
submitModal(workspaceName, addAllUsers);
|
||||
@ -72,8 +80,7 @@ const AddWorkspaceDialog = ({
|
||||
value={workspaceName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
error={error.length > 0}
|
||||
errorText={error}
|
||||
error={error}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 ml-1">
|
@ -1,70 +1,75 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import InputField from "../InputField";
|
||||
import { Fragment } from 'react';
|
||||
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;
|
||||
closeModal: () => void;
|
||||
submitModal: (userIdToBeDeleted: string) => void;
|
||||
userIdToBeDeleted: string;
|
||||
};
|
||||
|
||||
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.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"
|
||||
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">
|
||||
<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='inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
onClick={submit}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
type='button'
|
||||
className='ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
onClick={submit}
|
||||
>
|
||||
Cancel
|
@ -1,22 +1,24 @@
|
||||
import { Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
|
||||
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric
|
||||
} from "../../utilities/cryptography/crypto";
|
||||
import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
submitModal: (userIdToBeDeleted: string) => void;
|
||||
selectedIntegrationOption: string;
|
||||
handleBotActivate: () => void;
|
||||
handleIntegrationOption: (arg:{integrationOption:string})=>void;
|
||||
};
|
||||
|
||||
const IntegrationAccessTokenDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
selectedIntegrationOption,
|
||||
handleBotActivate,
|
||||
handleIntegrationOption
|
||||
}) => {
|
||||
}:Props) => {
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
@ -78,16 +78,18 @@ type encryptSymmetricProps = {
|
||||
key: string;
|
||||
};
|
||||
|
||||
type encryptSymmetricReturn = {
|
||||
ciphertext:string;
|
||||
iv:string;
|
||||
tag:string;
|
||||
};
|
||||
/**
|
||||
* Return symmetrically encrypted [plaintext] using [key].
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.plaintext - plaintext to encrypt
|
||||
* @param {String} obj.key - 16-byte hex key
|
||||
*/
|
||||
const encryptSymmetric = ({
|
||||
plaintext,
|
||||
key,
|
||||
}: encryptSymmetricProps): object => {
|
||||
}: encryptSymmetricProps): encryptSymmetricReturn => {
|
||||
let ciphertext, iv, tag;
|
||||
try {
|
||||
const obj = aes.encrypt({ text: plaintext, secret: key });
|
||||
|
@ -1,3 +1,8 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
**/
|
||||
const { i18n } = require("./next-i18next.config.js");
|
||||
|
||||
const ContentSecurityPolicy = `
|
||||
|
@ -1,81 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { appWithTranslation } from "next-i18next";
|
||||
import { config } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
import Layout from "~/components/basic/Layout";
|
||||
import NotificationProvider from "~/components/context/Notifications/NotificationProvider";
|
||||
import RouteGuard from "~/components/RouteGuard";
|
||||
import { publicPaths } from "~/const";
|
||||
import Telemetry from "~/utilities/telemetry/Telemetry";
|
||||
|
||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||
import "../styles/globals.css";
|
||||
|
||||
config.autoAddCss = false;
|
||||
|
||||
const App = ({ Component, pageProps, ...appProps }) => {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const storedLang = localStorage.getItem("lang");
|
||||
if (router.locale ?? "en" !== storedLang ?? "en") {
|
||||
router.push(router.asPath, router.asPath, {
|
||||
locale: storedLang ?? "en",
|
||||
});
|
||||
}
|
||||
}, [router.locale, router.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
// Init for auto capturing
|
||||
const telemetry = new Telemetry().getInstance();
|
||||
|
||||
const handleRouteChange = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
telemetry.capture("$pageview");
|
||||
}
|
||||
};
|
||||
|
||||
router.events.on("routeChangeComplete", handleRouteChange);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}, [router.events]);
|
||||
|
||||
// If it's one of these routes, don't add the layout (e.g., these routes are external)
|
||||
if (
|
||||
publicPaths.includes("/" + appProps.router.pathname.split("/")[1]) ||
|
||||
!Component.requireAuth
|
||||
) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<RouteGuard>
|
||||
<NotificationProvider>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</NotificationProvider>
|
||||
</RouteGuard>
|
||||
);
|
||||
};
|
||||
|
||||
export default appWithTranslation(App);
|
||||
|
||||
{
|
||||
/* <Script
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-DQ1XLJJGG1"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script id="google-analytics" strategy="afterInteractive">
|
||||
{`
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){window.dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-DQ1XLJJGG1');
|
||||
`}
|
||||
</Script> */
|
||||
}
|
90
frontend/pages/_app.tsx
Normal file
90
frontend/pages/_app.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { useEffect } from 'react';
|
||||
import { AppProps } from 'next/app';
|
||||
import { useRouter } from 'next/router';
|
||||
import { appWithTranslation } from 'next-i18next';
|
||||
import { config } from '@fortawesome/fontawesome-svg-core';
|
||||
|
||||
import Layout from '~/components/basic/Layout';
|
||||
import NotificationProvider from '~/components/context/Notifications/NotificationProvider';
|
||||
import RouteGuard from '~/components/RouteGuard';
|
||||
import { publicPaths } from '~/const';
|
||||
import Telemetry from '~/utilities/telemetry/Telemetry';
|
||||
|
||||
import '@fortawesome/fontawesome-svg-core/styles.css';
|
||||
import '../styles/globals.css';
|
||||
|
||||
config.autoAddCss = false;
|
||||
|
||||
type NextAppProp = AppProps & {
|
||||
Component: AppProps['Component'] & { requireAuth: boolean };
|
||||
};
|
||||
|
||||
const App = ({
|
||||
Component,
|
||||
pageProps,
|
||||
...appProps
|
||||
}: NextAppProp): JSX.Element => {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const storedLang = localStorage.getItem('lang');
|
||||
if (router.locale ?? 'en' !== storedLang ?? 'en') {
|
||||
router.push(router.asPath, router.asPath, {
|
||||
locale: storedLang ?? 'en',
|
||||
});
|
||||
}
|
||||
}, [router.locale, router.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
// Init for auto capturing
|
||||
const telemetry = new Telemetry().getInstance();
|
||||
|
||||
const handleRouteChange = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
telemetry.capture('$pageview');
|
||||
}
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', handleRouteChange);
|
||||
};
|
||||
}, [router.events]);
|
||||
|
||||
// If it's one of these routes, don't add the layout (e.g., these routes are external)
|
||||
if (
|
||||
publicPaths.includes('/' + appProps.router.pathname.split('/')[1]) ||
|
||||
!Component.requireAuth
|
||||
) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<RouteGuard>
|
||||
<NotificationProvider>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</NotificationProvider>
|
||||
</RouteGuard>
|
||||
);
|
||||
};
|
||||
|
||||
export default appWithTranslation(App);
|
||||
|
||||
{
|
||||
/* <Script
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-DQ1XLJJGG1"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script id="google-analytics" strategy="afterInteractive">
|
||||
{`
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){window.dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-DQ1XLJJGG1');
|
||||
`}
|
||||
</Script> */
|
||||
}
|
@ -33,7 +33,7 @@ export default function PersonalSettings() {
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
const [isAddApiKeyDialogOpen, setIsAddApiKeyDialogOpen] = useState(false)
|
||||
const [apiKeys, setApiKeys] = useState([]);
|
||||
const [apiKeys, setApiKeys] = useState<any[]>([]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
@ -96,7 +96,7 @@ export default function PersonalSettings() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start w-full mb-6 mt-4">
|
||||
<p className="text-xl font-semibold self-start">
|
||||
{t("settings-personal:change-language")}
|
||||
</p>
|
||||
@ -109,7 +109,7 @@ export default function PersonalSettings() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-4 flex flex-col items-start flex flex-col items-start w-full mt-2 mb-8 pt-2">
|
||||
<div className="bg-white/5 rounded-md px-6 flex flex-col items-start w-full mt-2 mb-8 pt-2">
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="flex flex-col w-full">
|
||||
<p className="text-xl font-semibold mb-3">
|
||||
|
@ -30,7 +30,7 @@ export default function SettingsBasic() {
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const [serviceTokens, setServiceTokens] = useState([]);
|
||||
const [serviceTokens, setServiceTokens] = useState<any[]>([]);
|
||||
const [environments, setEnvironments] = useState<Array<EnvData>>([]);
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState('');
|
||||
const [isAddOpen, setIsAddOpen] = useState(false);
|
||||
|
@ -194,7 +194,6 @@ export default function Users() {
|
||||
(email) =>
|
||||
!userList?.map((user1: UserProps) => user1.email).includes(email)
|
||||
)}
|
||||
workspaceId={workspaceId}
|
||||
setEmail={setEmail}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user