Repository: lightdash/lightdash
Pull request #14507: feat: allow saving/creating spaces in dashboard create
Author: login: joaoviana
Created: 2025-04-23T13:38:10Z | Merged: 2025-04-24T11:26:18Z

<!-- Thanks so much for your PR, your contribution is appreciated! ❤️ -->

Relates to: https://github.com/lightdash/lightdash/issues/14329

### Description:

When creating a dashboard from: 
<img width="382" alt="image" src="https://github.com/user-attachments/assets/8196d3c9-4e8c-4a95-99a7-7fb71ec21600" />

Also hides "create space" from modals when the feature is not enabled


### Reviewer actions

- [ ] I have manually tested the changes in the preview environment
- [ ] I have reviewed the code
- [ ] I understand that "request changes" will block this PR from merging



Diff:
diff --git a/packages/frontend/src/components/common/modal/DashboardCreateModal.tsx b/packages/frontend/src/components/common/modal/DashboardCreateModal.tsx
index 7a77753c37fec..a7062b5f61230 100644
--- a/packages/frontend/src/components/common/modal/DashboardCreateModal.tsx
+++ b/packages/frontend/src/components/common/modal/DashboardCreateModal.tsx
@@ -1,32 +1,35 @@
 import { subject } from '@casl/ability';
-import { type Dashboard, type Space } from '@lightdash/common';
+import { FeatureFlags, type Dashboard, type Space } from '@lightdash/common';
 import {
-    ActionIcon,
-    Box,
     Button,
     Group,
     MantineProvider,
-    Modal,
-    Select,
     Stack,
-    Text,
     TextInput,
     Textarea,
-    Title,
     type ModalProps,
 } from '@mantine/core';
 import { useForm } from '@mantine/form';
-import { IconFolder, IconX } from '@tabler/icons-react';
-import { useCallback, useEffect, useState, type FC } from 'react';
+import { IconLayoutDashboard, IconPlus } from '@tabler/icons-react';
+import { useCallback, useEffect, useMemo, type FC } from 'react';
 import { useCreateMutation } from '../../../hooks/dashboard/useDashboard';
-import {
-    useCreateMutation as useSpaceCreateMutation,
-    useSpaceSummaries,
-} from '../../../hooks/useSpaces';
+import { useFeatureFlagEnabled } from '../../../hooks/useFeatureFlagEnabled';
+import { useModalSteps } from '../../../hooks/useModalSteps';
+import { useSpaceManagement } from '../../../hooks/useSpaceManagement';
+import { useSpaceSummaries } from '../../../hooks/useSpaces';
 import useApp from '../../../providers/App/useApp';
 import MantineIcon from '../MantineIcon';
+import MantineModal from '../MantineModal';
+import SaveToSpaceForm from './ChartCreateModal/SaveToSpaceForm';
+
+enum ModalStep {
+    InitialInfo = 'initialInfo',
+    SelectDestination = 'selectDestination',
+}
 
-interface DashboardCreateModalProps extends ModalProps {
+interface DashboardCreateModalProps {
+    opened: ModalProps['opened'];
+    onClose: ModalProps['onClose'];
     projectUuid: string;
     defaultSpaceUuid?: string;
     onConfirm?: (dashboard: Dashboard) => void;
@@ -40,25 +43,33 @@ const DashboardCreateModal: FC<DashboardCreateModalProps> = ({
     ...modalProps
 }) => {
     const { user } = useApp();
+    const isNestedSpacesEnabled = useFeatureFlagEnabled(
+        FeatureFlags.NestedSpaces,
+    );
     const { mutateAsync: createDashboard, isLoading: isCreatingDashboard } =
         useCreateMutation(projectUuid);
-    const { mutateAsync: createSpace, isLoading: isCreatingSpace } =
-        useSpaceCreateMutation(projectUuid);
 
     const form = useForm({
         initialValues: {
-            isCreatingNewSpace: false,
             dashboardName: '',
             dashboardDescription: '',
             spaceUuid: '',
-            newSpaceName: '',
+            newSpaceName: null,
+        },
+    });
+
+    const modalSteps = useModalSteps<ModalStep>(ModalStep.InitialInfo, {
+        validators: {
+            [ModalStep.InitialInfo]: () => !!form.values.dashboardName,
         },
     });
 
-    const [searchValue, onSearchChange] = useState('');
-    const [spacesOptions, setSpacesOptions] = useState<
-        { value: string; label: string }[]
-    >([]);
+    const spaceManagement = useSpaceManagement({
+        projectUuid,
+        defaultSpaceUuid,
+    });
+
+    const { isCreatingNewSpace, openCreateSpaceForm } = spaceManagement;
 
     const {
         data: spaces,
@@ -80,20 +91,12 @@ const DashboardCreateModal: FC<DashboardCreateModalProps> = ({
         },
         onSuccess: (data) => {
             if (data.length > 0) {
-                setSpacesOptions(
-                    data.map((space) => ({
-                        value: space.uuid,
-                        label: space.name,
-                    })),
-                );
                 const currentSpace = defaultSpaceUuid
                     ? data.find((space) => space.uuid === defaultSpaceUuid)
                     : data[0];
                 return currentSpace?.uuid
                     ? form.setFieldValue('spaceUuid', currentSpace?.uuid)
                     : null;
-            } else {
-                form.setFieldValue('setIsCreatingNewSpace', true);
             }
         },
     });
@@ -111,7 +114,7 @@ const DashboardCreateModal: FC<DashboardCreateModalProps> = ({
                 'spaceUuid',
                 spaces?.find((space) => space.uuid === defaultSpaceUuid)
                     ?.uuid ??
-                    ((spaces && spaces[0].uuid) || ''),
+                    ((spaces && spaces[0]?.uuid) || ''),
             );
         }
     }, [defaultSpaceUuid, isSuccess, modalProps.opened, setFieldValue, spaces]);
@@ -120,11 +123,9 @@ const DashboardCreateModal: FC<DashboardCreateModalProps> = ({
         async (data: typeof form.values) => {
             let newSpace: Space | undefined;
 
-            if (form.values.isCreatingNewSpace) {
-                newSpace = await createSpace({
-                    name: data.newSpaceName,
+            if (data.newSpaceName) {
+                newSpace = await spaceManagement.handleCreateNewSpace({
                     isPrivate: false,
-                    access: [],
                 });
             }
 
@@ -136,156 +137,69 @@ const DashboardCreateModal: FC<DashboardCreateModalProps> = ({
                 tabs: [], // add default tab
             });
             onConfirm?.(dashboard);
+
             form.reset();
         },
-        [createDashboard, createSpace, onConfirm, form],
+        [createDashboard, onConfirm, form, spaceManagement],
     );
 
-    if (isLoadingSpaces || !spaces) return null;
+    const handleNextStep = () => {
+        modalSteps.goToStep(ModalStep.SelectDestination);
+    };
 
-    return (
-        <MantineProvider inherit theme={{ colorScheme: 'light' }}>
-            <Modal
-                title={
-                    <Box>
-                        <Title order={4}>Create Dashboard</Title>
-                    </Box>
-                }
-                onClose={() => handleClose()}
-                {...modalProps}
-            >
-                <form
-                    title="Create Dashboard"
-                    onSubmit={form.onSubmit((values) => handleConfirm(values))}
-                >
-                    <Stack mb="sm">
-                        <TextInput
-                            label="Name your dashboard"
-                            placeholder="eg. KPI Dashboard"
-                            disabled={isCreatingDashboard}
-                            required
-                            {...form.getInputProps('dashboardName')}
-                        />
-                        <Textarea
-                            label="Dashboard description"
-                            placeholder="A few words to give your team some context"
-                            disabled={isCreatingDashboard}
-                            autosize
-                            maxRows={3}
-                            {...form.getInputProps('dashboardDescription')}
-                        />
-                        {!isLoadingSpaces && spaces ? (
-                            <Stack spacing="xs">
-                                <Select
-                                    searchable
-                                    creatable={user.data?.ability.can(
-                                        'create',
-                                        subject('Space', {
-                                            organizationUuid:
-                                                user.data?.organizationUuid,
-                                            projectUuid,
-                                        }),
-                                    )}
-                                    clearable
-                                    withinPortal
-                                    label={
-                                        form.values.isCreatingNewSpace
-                                            ? 'Moving to new space'
-                                            : 'Select a space'
-                                    }
-                                    data={spacesOptions}
-                                    icon={<MantineIcon icon={IconFolder} />}
-                                    required
-                                    clearButtonProps={{
-                                        onClick: () => {
-                                            onSearchChange('');
-                                            setFieldValue(
-                                                'isCreatingNewSpace',
-                                                false,
-                                            );
-                                            setFieldValue('newSpaceName', '');
-                                        },
-                                    }}
-                                    onSearchChange={(query) => {
-                                        if (!query) {
-                                            setFieldValue(
-                                                'isCreatingNewSpace',
-                                                false,
-                                            );
-                                            setFieldValue('newSpaceName', '');
-                                        }
-                                        onSearchChange(query);
-                                    }}
-                                    searchValue={searchValue}
-                                    placeholder="Select space"
-                                    getCreateLabel={(query) => (
-                                        <Text component="b">
-                                            + Create new space{' '}
-                                            <Text span color="blue">
-                                                {query}
-                                            </Text>
-                                        </Text>
-                                    )}
-                                    readOnly={form.values.isCreatingNewSpace}
-                                    rightSection={
-                                        form.values.isCreatingNewSpace ||
-                                        !!form.values.spaceUuid ? (
-                                            <ActionIcon
-                                                variant="transparent"
-                                                onClick={() => {
-                                                    setSpacesOptions((prev) =>
-                                                        prev.filter(
-                                                            ({ label }) =>
-                                                                label !==
-                                                                searchValue,
-                                                        ),
-                                                    );
+    const handleBack = () => {
+        modalSteps.goToStep(ModalStep.InitialInfo);
+    };
+
+    const shouldShowNewSpaceButton = useMemo(
+        () =>
+            isNestedSpacesEnabled &&
+            modalSteps.currentStep === ModalStep.SelectDestination &&
+            !isCreatingNewSpace,
+        [isNestedSpacesEnabled, modalSteps.currentStep, isCreatingNewSpace],
+    );
 
-                                                    onSearchChange('');
-                                                    setFieldValue(
-                                                        'isCreatingNewSpace',
-                                                        false,
-                                                    );
-                                                    setFieldValue(
-                                                        'newSpaceName',
-                                                        '',
-                                                    );
-                                                    setFieldValue(
-                                                        'spaceUuid',
-                                                        '',
-                                                    );
-                                                }}
-                                            >
-                                                <MantineIcon icon={IconX} />
-                                            </ActionIcon>
-                                        ) : null
-                                    }
-                                    onCreate={(query) => {
-                                        const item = {
-                                            value: query,
-                                            label: query,
-                                        };
+    const isFormReadyToSave = useMemo(
+        () =>
+            modalSteps.currentStep === ModalStep.SelectDestination &&
+            form.values.dashboardName &&
+            (form.values.newSpaceName || form.values.spaceUuid),
+        [
+            modalSteps.currentStep,
+            form.values.dashboardName,
+            form.values.newSpaceName,
+            form.values.spaceUuid,
+        ],
+    );
 
-                                        form.setFieldValue(
-                                            'isCreatingNewSpace',
-                                            true,
-                                        );
-                                        form.setFieldValue(
-                                            'newSpaceName',
-                                            query,
-                                        );
+    const isLoading =
+        isCreatingDashboard ||
+        isLoadingSpaces ||
+        spaceManagement.createSpaceMutation.isLoading;
 
-                                        spacesOptions.push(item);
+    if (isLoadingSpaces || !spaces) return null;
 
-                                        return item;
-                                    }}
-                                    {...form.getInputProps('spaceUuid')}
-                                />
-                            </Stack>
-                        ) : null}
-                    </Stack>
+    return (
+        <MantineProvider inherit theme={{ colorScheme: 'light' }}>
+            <MantineModal
+                {...modalProps}
+                title="Create Dashboard"
+                icon={IconLayoutDashboard}
+                onClose={() => handleClose()}
+                actions={
+                    <Group position="right" w="100%">
+                        {shouldShowNewSpaceButton && (
+                            <Button
+                                variant="subtle"
+                                size="xs"
+                                leftIcon={<MantineIcon icon={IconPlus} />}
+                                onClick={openCreateSpaceForm}
+                                mr="auto"
+                            >
+                                New Space
+                            </Button>
+                        )}
 
-                    <Group position="right">
                         <Button
                             size="sm"
                             variant="outline"
@@ -294,18 +208,81 @@ const DashboardCreateModal: FC<DashboardCreateModalProps> = ({
                         >
                             Cancel
                         </Button>
-                        <Button
-                            size="sm"
-                            disabled={!form.isValid}
-                            loading={isCreatingDashboard || isCreatingSpace}
-                            type="submit"
-                            data-testid="dashboard-create-modal-create-button"
-                        >
-                            Create
-                        </Button>
+
+                        {modalSteps.currentStep === ModalStep.InitialInfo ? (
+                            <Button
+                                size="sm"
+                                onClick={handleNextStep}
+                                disabled={!form.values.dashboardName}
+                            >
+                                Next
+                            </Button>
+                        ) : (
+                            <>
+                                <Button
+                                    size="sm"
+                                    variant="outline"
+                                    onClick={handleBack}
+                                >
+                                    Back
+                                </Button>
+                                <Button
+                                    size="sm"
+                                    disabled={!isFormReadyToSave}
+                                    loading={isCreatingDashboard}
+                                    type="submit"
+                                    form="dashboard-create-modal"
+                                    data-testid="dashboard-create-modal-create-button"
+                                >
+                                    Create
+                                </Button>
+                            </>
+                        )}
                     </Group>
+                }
+            >
+                <form
+                    id="dashboard-create-modal"
+                    title="Create Dashboard"
+                    onSubmit={form.onSubmit((values) => handleConfirm(values))}
+                >
+                    {modalSteps.currentStep === ModalStep.InitialInfo && (
+                        <Stack>
+                            <TextInput
+                                label="Name your dashboard"
+                                placeholder="eg. KPI Dashboard"
+                                disabled={isCreatingDashboard}
+                                required
+                                {...form.getInputProps('dashboardName')}
+                            />
+                            <Textarea
+                                label="Dashboard description"
+                                placeholder="A few words to give your team some context"
+                                disabled={isCreatingDashboard}
+                                autosize
+                                maxRows={3}
+                                {...form.getInputProps('dashboardDescription')}
+                            />
+                        </Stack>
+                    )}
+
+                    {modalSteps.currentStep === ModalStep.SelectDestination && (
+                        <SaveToSpaceForm
+                            form={form}
+                            spaces={spaces}
+                            projectUuid={projectUuid}
+                            isLoading={isLoading}
+                            spaceManagement={spaceManagement}
+                            selectedSpaceName={
+                                spaces.find(
+                                    (space) =>
+                                        space.uuid === form.values.spaceUuid,
+                                )?.name
+                            }
+                        />
+                    )}
                 </form>
-            </Modal>
+            </MantineModal>
         </MantineProvider>
     );
 };
diff --git a/packages/frontend/src/features/sqlRunner/components/SaveSqlChartModal.tsx b/packages/frontend/src/features/sqlRunner/components/SaveSqlChartModal.tsx
index a90ff0ed45a33..1a1da04764dc9 100644
--- a/packages/frontend/src/features/sqlRunner/components/SaveSqlChartModal.tsx
+++ b/packages/frontend/src/features/sqlRunner/components/SaveSqlChartModal.tsx
@@ -1,4 +1,4 @@
-import { ChartKind } from '@lightdash/common';
+import { ChartKind, FeatureFlags } from '@lightdash/common';
 import {
     Box,
     Button,
@@ -18,6 +18,7 @@ import { selectCompleteConfigByKind } from '../../../components/DataViz/store/se
 import MantineIcon from '../../../components/common/MantineIcon';
 import SaveToSpaceForm from '../../../components/common/modal/ChartCreateModal/SaveToSpaceForm';
 import { saveToSpaceSchema } from '../../../components/common/modal/ChartCreateModal/types';
+import { useFeatureFlagEnabled } from '../../../hooks/useFeatureFlagEnabled';
 import { useModalSteps } from '../../../hooks/useModalSteps';
 import { useSpaceManagement } from '../../../hooks/useSpaceManagement';
 import { useSpaceSummaries } from '../../../hooks/useSpaces';
@@ -44,6 +45,9 @@ type FormValues = z.infer<typeof saveChartFormSchema>;
 type Props = Pick<ModalProps, 'opened' | 'onClose'>;
 
 const SaveChartForm: FC<Pick<Props, 'onClose'>> = ({ onClose }) => {
+    const isNestedSpacesEnabled = useFeatureFlagEnabled(
+        FeatureFlags.NestedSpaces,
+    );
     const dispatch = useAppDispatch();
     const projectUuid = useAppSelector((state) => state.sqlRunner.projectUuid);
     const hasUnrunChanges = useAppSelector(
@@ -180,9 +184,10 @@ const SaveChartForm: FC<Pick<Props, 'onClose'>> = ({ onClose }) => {
 
     const shouldShowNewSpaceButton = useMemo(
         () =>
+            isNestedSpacesEnabled &&
             modalSteps.currentStep === ModalStep.SelectDestination &&
             !isCreatingNewSpace,
-        [modalSteps.currentStep, isCreatingNewSpace],
+        [isNestedSpacesEnabled, modalSteps.currentStep, isCreatingNewSpace],
     );
 
     const isFormReadyToSave = useMemo(
diff --git a/packages/frontend/src/features/sqlRunner/components/UpdateSqlChartModal.tsx b/packages/frontend/src/features/sqlRunner/components/UpdateSqlChartModal.tsx
index 60a164e05bc02..02fcef7d9ade9 100644
--- a/packages/frontend/src/features/sqlRunner/components/UpdateSqlChartModal.tsx
+++ b/packages/frontend/src/features/sqlRunner/components/UpdateSqlChartModal.tsx
@@ -1,3 +1,4 @@
+import { FeatureFlags } from '@lightdash/common';
 import {
     Button,
     Group,
@@ -15,6 +16,7 @@ import { z } from 'zod';
 import MantineIcon from '../../../components/common/MantineIcon';
 import SaveToSpaceForm from '../../../components/common/modal/ChartCreateModal/SaveToSpaceForm';
 import { saveToSpaceSchema } from '../../../components/common/modal/ChartCreateModal/types';
+import { useFeatureFlagEnabled } from '../../../hooks/useFeatureFlagEnabled';
 import { useModalSteps } from '../../../hooks/useModalSteps';
 import { useSpaceManagement } from '../../../hooks/useSpaceManagement';
 import { useSpaceSummaries } from '../../../hooks/useSpaces';
@@ -52,6 +54,9 @@ export const UpdateSqlChartModal = ({
     onClose,
     onSuccess,
 }: Props) => {
+    const isNestedSpacesEnabled = useFeatureFlagEnabled(
+        FeatureFlags.NestedSpaces,
+    );
     const {
         data,
         isLoading: isChartLoading,
@@ -138,9 +143,10 @@ export const UpdateSqlChartModal = ({
 
     const shouldShowNewSpaceButton = useMemo(
         () =>
+            isNestedSpacesEnabled &&
             modalSteps.currentStep === ModalStep.SelectDestination &&
             !isCreatingNewSpace,
-        [modalSteps.currentStep, isCreatingNewSpace],
+        [isNestedSpacesEnabled, modalSteps.currentStep, isCreatingNewSpace],
     );
 
     const isFormReadyToSave = useMemo(
diff --git a/packages/frontend/src/hooks/useSpaceManagement.ts b/packages/frontend/src/hooks/useSpaceManagement.ts
index dd31b243d7466..59a640a5f26ed 100644
--- a/packages/frontend/src/hooks/useSpaceManagement.ts
+++ b/packages/frontend/src/hooks/useSpaceManagement.ts
@@ -24,7 +24,7 @@ export const useSpaceManagement = ({
 
     const handleCreateNewSpace = useCallback(
         async ({ isPrivate }: { isPrivate?: boolean } = {}) => {
-            if (newSpaceName.length === 0) return null;
+            if (newSpaceName.length === 0) return;
 
             const result = await createSpaceMutation.mutateAsync({
                 name: newSpaceName,


Reviews:
user: login: IrakliJani
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T08:11:39Z
user: login: IrakliJani
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T08:13:15Z
user: login: IrakliJani
body: looks good! looking at render now
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T08:13:48Z
user: login: joaoviana
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T08:15:35Z
user: login: joaoviana
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T08:15:47Z

Review comments:
diff_hunk: @@ -24,7 +24,7 @@ export const useSpaceManagement = ({
 
     const handleCreateNewSpace = useCallback(
         async ({ isPrivate }: { isPrivate?: boolean } = {}) => {
-            if (newSpaceName.length === 0) return null;
+            if (newSpaceName.length === 0) return undefined;
path: packages/frontend/src/hooks/useSpaceManagement.ts
user: login: IrakliJani
body: ```suggestion
            if (newSpaceName.length === 0) return;
```
created_at: 2025-04-24T08:11:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -136,11 +137,46 @@ const DashboardCreateModal: FC<DashboardCreateModalProps> = ({
                 tabs: [], // add default tab
             });
             onConfirm?.(dashboard);
+
             form.reset();
         },
-        [createDashboard, createSpace, onConfirm, form],
+        [createDashboard, onConfirm, form, spaceManagement],
+    );
+
+    const handleNextStep = () => {
+        modalSteps.goToStep(ModalStep.SelectDestination);
+    };
+
+    const handleBack = () => {
+        modalSteps.goToStep(ModalStep.InitialInfo);
+    };
+
+    const shouldShowNewSpaceButton = useMemo(
+        () =>
+            isNestedSpacesEnabled &&
+            modalSteps.currentStep === ModalStep.SelectDestination &&
+            !isCreatingNewSpace,
+        [isNestedSpacesEnabled, modalSteps.currentStep, isCreatingNewSpace],
     );
 
+    const isFormReadyToSave = useMemo(
+        () =>
+            modalSteps.currentStep === ModalStep.SelectDestination &&
+            form.values.dashboardName &&
+            (form.values.newSpaceName || form.values.spaceUuid),
+        [
+            modalSteps.currentStep,
+            form.values.dashboardName,
+            form.values.newSpaceName,
+            form.values.spaceUuid,
+        ],
+    );
path: packages/frontend/src/components/common/modal/DashboardCreateModal.tsx
user: login: IrakliJani
body: wonder if we should use zod for this kind of validations. mantine form has a great zod support.
(we can chat about that after the launch)
created_at: 2025-04-24T08:13:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,7 +24,7 @@ export const useSpaceManagement = ({
 
     const handleCreateNewSpace = useCallback(
         async ({ isPrivate }: { isPrivate?: boolean } = {}) => {
-            if (newSpaceName.length === 0) return null;
+            if (newSpaceName.length === 0) return undefined;
path: packages/frontend/src/hooks/useSpaceManagement.ts
user: login: joaoviana
body: done, ty!
created_at: 2025-04-24T08:15:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -136,11 +137,46 @@ const DashboardCreateModal: FC<DashboardCreateModalProps> = ({
                 tabs: [], // add default tab
             });
             onConfirm?.(dashboard);
+
             form.reset();
         },
-        [createDashboard, createSpace, onConfirm, form],
+        [createDashboard, onConfirm, form, spaceManagement],
+    );
+
+    const handleNextStep = () => {
+        modalSteps.goToStep(ModalStep.SelectDestination);
+    };
+
+    const handleBack = () => {
+        modalSteps.goToStep(ModalStep.InitialInfo);
+    };
+
+    const shouldShowNewSpaceButton = useMemo(
+        () =>
+            isNestedSpacesEnabled &&
+            modalSteps.currentStep === ModalStep.SelectDestination &&
+            !isCreatingNewSpace,
+        [isNestedSpacesEnabled, modalSteps.currentStep, isCreatingNewSpace],
     );
 
+    const isFormReadyToSave = useMemo(
+        () =>
+            modalSteps.currentStep === ModalStep.SelectDestination &&
+            form.values.dashboardName &&
+            (form.values.newSpaceName || form.values.spaceUuid),
+        [
+            modalSteps.currentStep,
+            form.values.dashboardName,
+            form.values.newSpaceName,
+            form.values.spaceUuid,
+        ],
+    );
path: packages/frontend/src/components/common/modal/DashboardCreateModal.tsx
user: login: joaoviana
body: ahhh like a form.isValid()?
created_at: 2025-04-24T08:15:47Z
author_association: CONTRIBUTOR

PR comments:
user: login: joaoviana
created_at: 2025-04-23T13:38:37Z
body: > [!WARNING]
> <b>This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14507?utm_source=stack-comment-downstack-mergeability-warning" >on Graphite</a>.</b>
> <a href="https://graphite.dev/docs/merge-pull-requests">Learn more</a>

* **#14517** <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14517?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#14507** <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14507?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14507?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#14497** <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14497?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>: 3 other dependent PRs ([#14498](https://github.com/lightdash/lightdash/pull/14498) <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14498?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>, [#14504](https://github.com/lightdash/lightdash/pull/14504) <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14504?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>, [#14515](https://github.com/lightdash/lightdash/pull/14515) <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14515?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>)
* **#14492** <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14492?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#14489** <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14489?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>: 1 other dependent PR ([#14490](https://github.com/lightdash/lightdash/pull/14490) <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14490?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>)
* **#14488** <a href="https://app.graphite.dev/github/pr/lightdash/lightdash/14488?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* `main`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: CONTRIBUTOR
user: login: IrakliJani
created_at: 2025-04-24T09:07:42Z
body: @joaoviana  got this error when testing saving a chart (when I was on a tree component) is this resolved in the upstack PR? I am starting to think this is an issue with the tree component.

 ![CleanShot 2025-04-24 at 13.06.36@2x.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/cOL6QbQzyaUdOs5psinD/362fd507-55b5-45ae-8dd7-fda10c2985b3.png)


author_association: CONTRIBUTOR
user: login: joaoviana
created_at: 2025-04-24T09:37:15Z
body: > @joaoviana got this error when testing saving a chart (when I was on a tree component) is this resolved in the upstack PR? I am starting to think this is an issue with the tree component.
> 
> ![CleanShot 2025-04-24 at 13.06.36@2x.png](https://camo.githubusercontent.com/69fb92fc341582b41724cc2abd82570ce9b93e519ae9fa6018c65a9247ae9689/68747470733a2f2f67726170686974652d757365722d75706c6f616465642d6173736574732d70726f642e73332e616d617a6f6e6177732e636f6d2f634f4c365162517a796155644f73357073696e442f33363266643530372d353562352d343561652d386464372d6664613130633239383562332e706e67)

oh 🤔 so like - save chart -> interact with tree -> then boom?
author_association: CONTRIBUTOR
user: login: IrakliJani
created_at: 2025-04-24T09:38:05Z
body: yes :(
author_association: CONTRIBUTOR
user: login: joaoviana
created_at: 2025-04-24T11:26:54Z
body: > yes :(

confirmed this happens when selecting a selected node. Culprit is in the `useEffect` + `onChange` call, which triggers a state update
author_association: CONTRIBUTOR