From b5192c596107d8f00c2eeb37a55712f65300ee0d Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Fri, 31 Jul 2026 00:05:28 +0000 Subject: [PATCH 1/2] refactor: migrate settings form off MUI TextField --- .../DeleteOrganizationSection.tsx | 51 +++ .../OrganizationInfoForm.tsx | 160 ++++++++++ .../OrganizationSettingsPageView.stories.tsx | 64 +--- .../OrganizationSettingsPageView.tsx | 291 ++---------------- .../WorkspaceSharingSection.stories.tsx | 71 +++++ .../WorkspaceSharingSection.tsx | 164 ++++++++++ 6 files changed, 475 insertions(+), 326 deletions(-) create mode 100644 site/src/pages/OrganizationSettingsPage/DeleteOrganizationSection.tsx create mode 100644 site/src/pages/OrganizationSettingsPage/OrganizationInfoForm.tsx create mode 100644 site/src/pages/OrganizationSettingsPage/WorkspaceSharingSection.stories.tsx create mode 100644 site/src/pages/OrganizationSettingsPage/WorkspaceSharingSection.tsx diff --git a/site/src/pages/OrganizationSettingsPage/DeleteOrganizationSection.tsx b/site/src/pages/OrganizationSettingsPage/DeleteOrganizationSection.tsx new file mode 100644 index 0000000000000..c0b49669e634b --- /dev/null +++ b/site/src/pages/OrganizationSettingsPage/DeleteOrganizationSection.tsx @@ -0,0 +1,51 @@ +import { type FC, useState } from "react"; +import { Button } from "#/components/Button/Button"; +import { DeleteDialog } from "#/components/Dialogs/DeleteDialog/DeleteDialog"; +import { FormSection, HorizontalForm } from "#/components/Form/Form"; + +type DeleteOrganizationSectionProps = { + organizationName: string; + onDeleteOrganization: () => void; +}; + +export const DeleteOrganizationSection: FC = ({ + organizationName, + onDeleteOrganization, +}) => { + const [isDeleting, setIsDeleting] = useState(false); + + return ( + <> + + +
+
+ Deleting an organization is irreversible. + +
+
+
+
+ + { + await onDeleteOrganization(); + setIsDeleting(false); + }} + onCancel={() => setIsDeleting(false)} + entity="organization" + name={organizationName} + /> + + ); +}; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationInfoForm.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationInfoForm.tsx new file mode 100644 index 0000000000000..eae717e04771a --- /dev/null +++ b/site/src/pages/OrganizationSettingsPage/OrganizationInfoForm.tsx @@ -0,0 +1,160 @@ +import { useFormik } from "formik"; +import type { FC } from "react"; +import * as Yup from "yup"; +import { isApiValidationError } from "#/api/errors"; +import type { + Organization, + UpdateOrganizationRequest, +} from "#/api/typesGenerated"; +import { ErrorAlert } from "#/components/Alert/ErrorAlert"; +import { Button } from "#/components/Button/Button"; +import { + FormFields, + FormFooter, + FormSection, + HorizontalForm, +} from "#/components/Form/Form"; +import { FormField } from "#/components/FormField/FormField"; +import { IconField } from "#/components/IconField/IconField"; +import { Label } from "#/components/Label/Label"; +import { Spinner } from "#/components/Spinner/Spinner"; +import { Textarea } from "#/components/Textarea/Textarea"; +import { cn } from "#/utils/cn"; +import { + displayNameValidator, + getFormHelpers, + nameValidator, + onChangeTrimmed, +} from "#/utils/formUtils"; + +const MAX_DESCRIPTION_CHAR_LIMIT = 128; +const MAX_DESCRIPTION_MESSAGE = `Please enter a description that is no longer than ${MAX_DESCRIPTION_CHAR_LIMIT} characters.`; + +const validationSchema = Yup.object({ + name: nameValidator("Name"), + display_name: displayNameValidator("Display name"), + description: Yup.string().max( + MAX_DESCRIPTION_CHAR_LIMIT, + MAX_DESCRIPTION_MESSAGE, + ), +}); + +type OrganizationInfoFormProps = { + organization: Organization; + error: unknown; + onSubmit: (values: UpdateOrganizationRequest) => Promise; +}; + +export const OrganizationInfoForm: FC = ({ + organization, + error, + onSubmit, +}) => { + const form = useFormik({ + initialValues: { + name: organization.name, + display_name: organization.display_name, + description: organization.description, + icon: organization.icon, + }, + validationSchema, + onSubmit, + enableReinitialize: true, + }); + const getFieldHelpers = getFormHelpers(form, error); + const descriptionField = getFieldHelpers("description", { + maxLength: MAX_DESCRIPTION_CHAR_LIMIT, + }); + const descriptionErrorId = `${descriptionField.id}-error`; + const descriptionHelperId = `${descriptionField.id}-helper`; + + return ( + <> + {Boolean(error) && !isApiValidationError(error) && ( +
+ +
+ )} + + + +
+ + + +
+ +