Files
twenty/packages/twenty-front/src/pages/settings/data-model/SettingsObjectEdit.tsx
gitstart-app[bot] c42ea57b97 New Settings Layout (#6867)
#### \
Description

- **Added "Exit Settings" Back Button**: Introduced a new back button
labeled "Exit Settings" for easy navigation back to the app content.
- **Implemented Settings Navbar Breadcrumb**: A breadcrumb navigation
bar for each settings page has been added to improve navigation within
the settings. This ensures users can easily trace their location within
the settings.
- **Persistent CTA Zone**: The Call-to-Action (CTA) zone at the top of
the page now remains visible even when the user scrolls down, preventing
unresponsive behavior and improving accessibility.
- **Page Title**: The page title has been added to each settings page
and separated from the breadcrumb, following the app's layout standards.
- we could not reproduce the Billing and CMR Migrations settings on the
app, but we updated the files according, please let's us know if is
there any way to log in with access to these pages, or if is everything
ok.
- Some breadcrumbs are not following the Figma, are following the
current app sections isntead, because we would need to change the
sidebar structure, and we should not do it on this PR

### Demo


<https://www.loom.com/share/21b20a2cd2f3471e94d61563c9901b19?sid=9dc49456-6cae-48e1-9149-8d706f00ab65>

### Refs:

#6144

Fixes #6144

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2024-09-17 18:29:00 +02:00

176 lines
6.5 KiB
TypeScript

/* eslint-disable react/jsx-props-no-spreading */
import { zodResolver } from '@hookform/resolvers/zod';
import pick from 'lodash.pick';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
import { H2Title, IconArchive, IconHierarchy2 } from 'twenty-ui';
import { z } from 'zod';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import {
SettingsDataModelObjectAboutForm,
settingsDataModelObjectAboutFormSchema,
} from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm';
import { settingsDataModelObjectIdentifiersFormSchema } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm';
import { SettingsDataModelObjectSettingsFormCard } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectSettingsFormCard';
import { settingsUpdateObjectInputSchema } from '@/settings/data-model/validation-schemas/settingsUpdateObjectInputSchema';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
const objectEditFormSchema = z
.object({})
.merge(settingsDataModelObjectAboutFormSchema)
.merge(settingsDataModelObjectIdentifiersFormSchema);
type SettingsDataModelObjectEditFormValues = z.infer<
typeof objectEditFormSchema
>;
export const SettingsObjectEdit = () => {
const navigate = useNavigate();
const { enqueueSnackBar } = useSnackBar();
const { objectSlug = '' } = useParams();
const { findActiveObjectMetadataItemBySlug } =
useFilteredObjectMetadataItems();
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
const activeObjectMetadataItem =
findActiveObjectMetadataItemBySlug(objectSlug);
const settingsObjectsPagePath = getSettingsPagePath(SettingsPath.Objects);
const formConfig = useForm<SettingsDataModelObjectEditFormValues>({
mode: 'onTouched',
resolver: zodResolver(objectEditFormSchema),
});
useEffect(() => {
if (!activeObjectMetadataItem) navigate(AppPath.NotFound);
}, [activeObjectMetadataItem, navigate]);
if (!activeObjectMetadataItem) return null;
const { isDirty, isValid, isSubmitting } = formConfig.formState;
const canSave = isDirty && isValid && !isSubmitting;
const handleSave = async (
formValues: SettingsDataModelObjectEditFormValues,
) => {
const dirtyFieldKeys = Object.keys(
formConfig.formState.dirtyFields,
) as (keyof SettingsDataModelObjectEditFormValues)[];
try {
await updateOneObjectMetadataItem({
idToUpdate: activeObjectMetadataItem.id,
updatePayload: settingsUpdateObjectInputSchema.parse(
pick(formValues, dirtyFieldKeys),
),
});
navigate(
`${settingsObjectsPagePath}/${getObjectSlug({
...formValues,
namePlural: formValues.labelPlural,
})}`,
);
} catch (error) {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
});
}
};
const handleDisable = async () => {
await updateOneObjectMetadataItem({
idToUpdate: activeObjectMetadataItem.id,
updatePayload: { isActive: false },
});
navigate(settingsObjectsPagePath);
};
return (
<RecordFieldValueSelectorContextProvider>
<FormProvider {...formConfig}>
<SubMenuTopBarContainer
Icon={IconHierarchy2}
title="Edit"
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Objects',
href: settingsObjectsPagePath,
},
{
children: activeObjectMetadataItem.labelPlural,
href: `${settingsObjectsPagePath}/${objectSlug}`,
},
{ children: 'Edit Object' },
]}
actionButton={
activeObjectMetadataItem.isCustom && (
<SaveAndCancelButtons
isSaveDisabled={!canSave}
isCancelDisabled={isSubmitting}
onCancel={() =>
navigate(`${settingsObjectsPagePath}/${objectSlug}`)
}
onSave={formConfig.handleSubmit(handleSave)}
/>
)
}
>
<SettingsPageContainer>
<Section>
<H2Title
title="About"
description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
/>
<SettingsDataModelObjectAboutForm
disabled={!activeObjectMetadataItem.isCustom}
disableNameEdit
objectMetadataItem={activeObjectMetadataItem}
/>
</Section>
<Section>
<H2Title
title="Settings"
description="Choose the fields that will identify your records"
/>
<SettingsDataModelObjectSettingsFormCard
objectMetadataItem={activeObjectMetadataItem}
/>
</Section>
<Section>
<H2Title title="Danger zone" description="Deactivate object" />
<Button
Icon={IconArchive}
title="Deactivate"
size="small"
onClick={handleDisable}
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</FormProvider>
</RecordFieldValueSelectorContextProvider>
);
};