GeneralTabPanel cleanup

This commit is contained in:
Vivian Phung 2024-05-11 20:45:22 -04:00 committed by Vivian Phung
parent 146b904556
commit fb77433bea
5 changed files with 156 additions and 126 deletions

View File

@ -0,0 +1,20 @@
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const CopyUnfilledIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
{...props}
>
<path
d="M6 5.625V5.025C6 4.18492 6 3.76488 6.16349 3.44401C6.3073 3.16177 6.53677 2.9323 6.81901 2.78849C7.13988 2.625 7.55992 2.625 8.4 2.625H12.975C13.8151 2.625 14.2351 2.625 14.556 2.78849C14.8382 2.9323 15.0677 3.16177 15.2115 3.44401C15.375 3.76488 15.375 4.18492 15.375 5.025V9.975C15.375 10.8151 15.375 11.2351 15.2115 11.556C15.0677 11.8382 14.8382 12.0677 14.556 12.2115C14.2351 12.375 13.8151 12.375 12.975 12.375H12.375M12.375 8.025V12.975C12.375 13.8151 12.375 14.2351 12.2115 14.556C12.0677 14.8382 11.8382 15.0677 11.556 15.2115C11.2351 15.375 10.8151 15.375 9.975 15.375H5.025C4.18492 15.375 3.76488 15.375 3.44401 15.2115C3.16177 15.0677 2.9323 14.8382 2.78849 14.556C2.625 14.2351 2.625 13.8151 2.625 12.975V8.025C2.625 7.18492 2.625 6.76488 2.78849 6.44401C2.9323 6.16177 3.16177 5.9323 3.44401 5.78849C3.76488 5.625 4.18492 5.625 5.025 5.625H9.975C10.8151 5.625 11.2351 5.625 11.556 5.78849C11.8382 5.9323 12.0677 6.16177 12.2115 6.44401C12.375 6.76488 12.375 7.18492 12.375 8.025Z"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
/>
</CustomIcon>
);
};

View File

@ -0,0 +1,20 @@
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const TrashIcon: React.FC<CustomIconProps> = (props) => {
return (
<CustomIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
{...props}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.05612 3.33398C5.52062 2.16265 6.66341 1.33398 8.00079 1.33398C9.33816 1.33398 10.481 2.16265 10.9455 3.33398H14.1668C14.443 3.33398 14.6668 3.55784 14.6668 3.83398C14.6668 4.11013 14.443 4.33398 14.1668 4.33398H13.3023L12.7463 12.952C12.684 13.9167 11.8834 14.6673 10.9167 14.6673H5.08358C4.11688 14.6673 3.31629 13.9167 3.25405 12.952L2.69805 4.33398H1.8335C1.55735 4.33398 1.3335 4.11013 1.3335 3.83398C1.3335 3.55784 1.55735 3.33398 1.8335 3.33398H5.05612ZM6.17457 3.33398C6.55973 2.73248 7.23408 2.33398 8.00079 2.33398C8.76749 2.33398 9.44184 2.73248 9.827 3.33398H6.17457ZM7.00016 7.16732C7.00016 6.89118 6.77631 6.66732 6.50016 6.66732C6.22402 6.66732 6.00016 6.89118 6.00016 7.16732V10.834C6.00016 11.1101 6.22402 11.334 6.50016 11.334C6.77631 11.334 7.00016 11.1101 7.00016 10.834V7.16732ZM9.50016 6.66732C9.77631 6.66732 10.0002 6.89118 10.0002 7.16732V10.834C10.0002 11.1101 9.77631 11.334 9.50016 11.334C9.22402 11.334 9.00016 11.1101 9.00016 10.834V7.16732C9.00016 6.89118 9.22402 6.66732 9.50016 6.66732Z"
fill="currentColor"
/>
</CustomIcon>
);
};

View File

@ -67,6 +67,8 @@ export * from './AppleIcon';
export * from './CalendarDaysIcon'; export * from './CalendarDaysIcon';
export * from './GoogleIcon'; export * from './GoogleIcon';
export * from './KeyIcon'; export * from './KeyIcon';
export * from './TrashIcon';
export * from './CopyUnfilledIcon';
// Templates // Templates
export * from './templates'; export * from './templates';

View File

@ -1,54 +1,35 @@
import { useState, useEffect, useCallback, useMemo } from 'react'; import { useState, useEffect, useCallback, useMemo } from 'react';
import { Link, useOutletContext } from 'react-router-dom'; import { useOutletContext } from 'react-router-dom';
import { useForm, Controller } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { Organization } from 'gql-client';
import {
Button,
Typography,
Input,
Option,
} from '@snowballtools/material-tailwind-react-fork';
import DeleteProjectDialog from 'components/projects/project/settings/DeleteProjectDialog'; import DeleteProjectDialog from 'components/projects/project/settings/DeleteProjectDialog';
import { useGQLClient } from 'context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import AsyncSelect from 'components/shared/AsyncSelect'; import { OutletContextType } from '../../../../../types';
import { OutletContextType } from '../../../../../types/types';
import { TransferProjectDialog } from 'components/projects/Dialog/TransferProjectDialog'; import { TransferProjectDialog } from 'components/projects/Dialog/TransferProjectDialog';
import { Input } from 'components/shared/Input';
const CopyIcon = ({ value }: { value: string }) => { import { Heading } from 'components/shared/Heading';
return ( import { Button } from 'components/shared/Button';
<span import { Select, SelectOption } from 'components/shared/Select';
onClick={() => { import { TrashIcon, CopyUnfilledIcon } from 'components/shared/CustomIcon';
navigator.clipboard.writeText(value); import { useToast } from 'components/shared/Toast';
toast.success('Project ID copied');
}}
className="cursor-pointer"
>
^
</span>
);
};
const GeneralTabPanel = () => { const GeneralTabPanel = () => {
const client = useGQLClient(); const client = useGQLClient();
const { toast } = useToast();
const { project, onUpdate } = useOutletContext<OutletContextType>(); const { project, onUpdate } = useOutletContext<OutletContextType>();
const [transferOrganizations, setTransferOrganizations] = useState< const [transferOrganizations, setTransferOrganizations] = useState<
Organization[] SelectOption[]
>([]); >([]);
const [selectedTransferOrganization, setSelectedTransferOrganization] = const [selectedTransferOrganization, setSelectedTransferOrganization] =
useState(''); useState<SelectOption>();
const { const { handleSubmit: handleTransfer, reset: transferFormReset } = useForm({
handleSubmit: handleTransfer,
control,
formState,
reset: transferFormReset,
} = useForm({
defaultValues: { defaultValues: {
orgId: '', org: {
value: '',
label: '',
},
}, },
}); });
@ -75,33 +56,47 @@ const GeneralTabPanel = () => {
const orgsToTransfer = organizations.filter( const orgsToTransfer = organizations.filter(
(org) => org.id !== project.organization.id, (org) => org.id !== project.organization.id,
); );
setTransferOrganizations(orgsToTransfer); const selectableOrgs: SelectOption[] = orgsToTransfer.map((org) => ({
value: org.id,
label: org.name,
}));
setTransferOrganizations(selectableOrgs);
}, [project]); }, [project]);
const handleTransferProject = useCallback(async () => { const handleTransferProject = useCallback(async () => {
const { updateProject: isTransferred } = await client.updateProject( const { updateProject: isTransferred } = await client.updateProject(
project.id, project.id,
{ {
organizationId: selectedTransferOrganization, organizationId: selectedTransferOrganization?.value,
}, },
); );
setOpenTransferDialog(!openTransferDialog); setOpenTransferDialog(!openTransferDialog);
if (isTransferred) { if (isTransferred) {
toast.success('Project transferred'); toast({
id: 'project_transferred',
title: 'Project transferred successfully',
variant: 'success',
onDismiss() {},
});
await fetchUserOrganizations(); await fetchUserOrganizations();
await onUpdate(); await onUpdate();
transferFormReset(); transferFormReset();
} else { } else {
toast.error('Project not transrfered'); toast({
id: 'project_transfer_failed',
title: 'Project transfer failed',
variant: 'error',
onDismiss() {},
});
} }
}, [project, selectedTransferOrganization]); }, [project, selectedTransferOrganization]);
const selectedUserOrgName = useMemo(() => { const selectedUserOrgName = useMemo(() => {
return ( return (
transferOrganizations.find( transferOrganizations.find((org) => org === selectedTransferOrganization)
(org) => org.id === selectedTransferOrganization, ?.label || ''
)?.name || ''
); );
}, [transferOrganizations, selectedTransferOrganization]); }, [transferOrganizations, selectedTransferOrganization]);
@ -114,7 +109,7 @@ const GeneralTabPanel = () => {
}, [project]); }, [project]);
return ( return (
<> <div className="flex-col justify-start items-start gap-6 inline-flex">
<form <form
onSubmit={handleSubmit(async ({ appName, description }) => { onSubmit={handleSubmit(async ({ appName, description }) => {
const { updateProject } = await client.updateProject(project.id, { const { updateProject } = await client.updateProject(project.id, {
@ -125,110 +120,100 @@ const GeneralTabPanel = () => {
await onUpdate(); await onUpdate();
} }
})} })}
className="self-stretch space-y-3 px-2"
> >
<Typography variant="h6">Project info</Typography> <Heading className="text-sky-950 text-lg font-medium leading-normal">
<Typography variant="small" className="font-medium text-gray-800"> Project Info
App name </Heading>
</Typography>
<Input <Input
variant="outlined"
// TODO: Debug issue: https://github.com/creativetimofficial/material-tailwind/issues/427 // TODO: Debug issue: https://github.com/creativetimofficial/material-tailwind/issues/427
label="App name"
size="md" size="md"
{...register('appName')} {...register('appName')}
/> />
<Typography variant="small" className="font-medium text-gray-800">
Description (Optional)
</Typography>
<Input variant="outlined" size="md" {...register('description')} />
<Typography variant="small" className="font-medium text-gray-800">
Project ID
</Typography>
<Input <Input
variant="outlined"
value={project.id}
size="md" size="md"
disabled label="Description (Optional)"
icon={<CopyIcon value={project.id} />} {...register('description')}
/> />
<div
onClick={() => {
navigator.clipboard.writeText(project.id);
toast({
id: 'copied_project_id',
title: 'Project ID copied to clipboard',
variant: 'success',
onDismiss() {},
});
}}
>
<Input
value={project.id}
size="md"
disabled
label="Project ID"
rightIcon={<CopyUnfilledIcon />}
/>
</div>
<Button <Button
type="submit" type="submit"
variant="gradient" size="md"
size="sm"
className="mt-1"
disabled={!updateProjectFormState.isDirty} disabled={!updateProjectFormState.isDirty}
> >
Save Save
</Button> </Button>
</form> </form>
<div className="mb-1"> <form
<Typography variant="h6">Transfer project</Typography> onSubmit={handleTransfer((org) => {
<Typography variant="small"> setSelectedTransferOrganization(org.org);
setOpenTransferDialog(!openTransferDialog);
})}
className="self-stretch space-y-3 px-2"
>
<Heading className="text-sky-950 text-lg font-medium leading-normal">
Transfer project
</Heading>
<p className="text-slate-600 text-sm font-normal leading-tight">
Transfer this app to your personal account or a team you are a member Transfer this app to your personal account or a team you are a member
of.{' '} of.
<Link to="" className="text-blue-500"> </p>
Learn more <Select
</Link> disabled
</Typography> size="md"
<form placeholder="Select an account / team"
onSubmit={handleTransfer(({ orgId }) => { options={transferOrganizations}
setSelectedTransferOrganization(orgId); value={selectedTransferOrganization}
setOpenTransferDialog(!openTransferDialog); onChange={(value) =>
})} setSelectedTransferOrganization(value as SelectOption)
> }
<Typography variant="small" className="font-medium text-gray-800">
Choose team
</Typography>
<Controller
name="orgId"
rules={{ required: 'This field is required' }}
control={control}
render={({ field }) => (
<AsyncSelect
{...field}
// TODO: Implement placeholder for select
label={!field.value ? 'Select an account / team' : ''}
>
{transferOrganizations.map((org, key) => (
<Option key={key} value={org.id}>
^ {org.name}
</Option>
))}
</AsyncSelect>
)}
/>
<Button
variant="gradient"
size="sm"
className="mt-1"
disabled={!formState.isValid}
type="submit"
>
Transfer
</Button>
</form>
<TransferProjectDialog
handleCancel={() => setOpenTransferDialog(!openTransferDialog)}
open={openTransferDialog}
handleConfirm={handleTransferProject}
projectName={project.name}
from={project.organization.name}
to={selectedUserOrgName}
/> />
</div> <Button disabled type="submit" size="md">
<div className="mb-1"> Transfer
<Typography variant="h6">Delete project</Typography> </Button>
<Typography variant="small"> </form>
<TransferProjectDialog
handleCancel={() => setOpenTransferDialog(!openTransferDialog)}
open={openTransferDialog}
handleConfirm={handleTransferProject}
projectName={project.name}
from={project.organization.name}
to={selectedUserOrgName}
/>
<div className="self-stretch space-y-3 px-2">
<Heading className="text-sky-950 text-lg font-medium leading-normal">
Delete project
</Heading>
<p className="text-slate-600 text-sm font-normal leading-tight">
The project will be permanently deleted, including its deployments and The project will be permanently deleted, including its deployments and
domains. This action is irreversible and can not be undone. domains. This action is irreversible and can not be undone.
</Typography> </p>
<Button <Button
variant="gradient" size="md"
size="sm" variant="danger"
color="red"
onClick={handleDeleteProjectDialog} onClick={handleDeleteProjectDialog}
leftIcon={<TrashIcon />}
> >
^ Delete project Delete project
</Button> </Button>
<DeleteProjectDialog <DeleteProjectDialog
handleOpen={handleDeleteProjectDialog} handleOpen={handleDeleteProjectDialog}
@ -236,7 +221,7 @@ const GeneralTabPanel = () => {
project={project} project={project}
/> />
</div> </div>
</> </div>
); );
}; };

View File

@ -0,0 +1,3 @@
export * from './common';
export * from './types';
export * from './vendor';