From 88f3173920012523649025015bf1ce92b19512d5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 14 Jul 2026 23:55:23 +0800 Subject: [PATCH] Remove home page skeleton loader, improve base loading behaviour (#47903) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context The project's home page has been updated for a while now, but the skeleton loader is still showing the old layout Am opting to remove the skeleton loader entirely and instead improve the loading state of the base UI - adjusts some heights to minimize layout shifts ## Summary by CodeRabbit * **Improvements** * Added shimmer loading for project titles while project details are loading. * Refined project connection dropdown behavior: improved URL display/truncation, adjusted dropdown alignment, and more reliable option selection (disabled items remain unavailable). * Streamlined the project page’s loading and transition experience across different project states. * **Tests** * Updated the connect E2E scenario to interact with the Connect sheet via its accessible label before selecting the ORM option. --- .../ProjectHome/ProjectConnectionPopover.tsx | 143 +++++++++--------- .../interfaces/ProjectHome/TopSection.tsx | 9 +- .../layouts/ProjectLayout/LoadingState.tsx | 50 ------ .../layouts/ProjectLayout/index.tsx | 12 +- e2e/studio/features/connect.spec.ts | 3 +- 5 files changed, 85 insertions(+), 132 deletions(-) delete mode 100644 apps/studio/components/layouts/ProjectLayout/LoadingState.tsx diff --git a/apps/studio/components/interfaces/ProjectHome/ProjectConnectionPopover.tsx b/apps/studio/components/interfaces/ProjectHome/ProjectConnectionPopover.tsx index 599d5e2b87f..bbb20bc3fa0 100644 --- a/apps/studio/components/interfaces/ProjectHome/ProjectConnectionPopover.tsx +++ b/apps/studio/components/interfaces/ProjectHome/ProjectConnectionPopover.tsx @@ -164,84 +164,89 @@ export const ProjectConnectionPopover = ({ projectRef }: ProjectConnectionPopove }, [open]) return ( -
+
{isLoadingApiUrl ? ( - + ) : ( - + {projectUrl ?? 'Project URL unavailable'} )} - - - - - - {menuItems.map((item) => { - const Icon = item.icon - return ( - { - event.preventDefault() - if (item.disabled) return + {!isLoadingApiUrl && ( + + + + + + {menuItems.map((item) => { + const Icon = item.icon - copyToClipboard(item.value) - setCopiedItem(item.label) + return ( + { + event.preventDefault() + if (item.disabled) return + + copyToClipboard(item.value) + setCopiedItem(item.label) + }} + > + +
+
+ {copiedItem !== item.label ? Copy : null} + {item.label} + {copiedItem === item.label ? ( + copied to your clipboard + ) : null} +
+
+ {item.displayValue} +
+
+
+ {copiedItem === item.label ? : } +
+
+ ) + })} + +
+ -
-
-
+ Get Connected + +
+ + + )}
) } diff --git a/apps/studio/components/interfaces/ProjectHome/TopSection.tsx b/apps/studio/components/interfaces/ProjectHome/TopSection.tsx index 368c8d367b1..4002e029d2f 100644 --- a/apps/studio/components/interfaces/ProjectHome/TopSection.tsx +++ b/apps/studio/components/interfaces/ProjectHome/TopSection.tsx @@ -1,6 +1,7 @@ import { ReactFlowProvider } from '@xyflow/react' import Link from 'next/link' import { Badge, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui' +import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { InstanceConfiguration } from '../Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration' import { ActivityStats } from '@/components/interfaces/ProjectHome/ActivityStats' @@ -15,7 +16,7 @@ import { DOCS_URL, IS_PLATFORM, PROJECT_STATUS } from '@/lib/constants' export const TopSection = () => { const isOrioleDb = useIsOrioleDb() - const { data: project } = useSelectedProjectQuery() + const { data: project, isLoading } = useSelectedProjectQuery() const { data: parentProject } = useProjectDetailQuery({ ref: project?.parent_project_ref }) const { data: branches } = useBranchesQuery({ @@ -58,7 +59,11 @@ export const TopSection = () => { )}
-

{projectName}

+ {isLoading ? ( + + ) : ( +

{projectName}

+ )} {isOrioleDb && ( diff --git a/apps/studio/components/layouts/ProjectLayout/LoadingState.tsx b/apps/studio/components/layouts/ProjectLayout/LoadingState.tsx deleted file mode 100644 index 0027ded4b68..00000000000 --- a/apps/studio/components/layouts/ProjectLayout/LoadingState.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useParams } from 'common' -import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' - -import { useProjectDetailQuery } from '@/data/projects/project-detail-query' - -export const LoadingState = () => { - const { ref } = useParams() - const { data: project, isPending: isLoading } = useProjectDetailQuery({ ref }) - - const projectName = ref !== 'default' ? project?.name : 'Welcome to your project' - - return ( -
-
-
- {isLoading ? ( - - ) : ( -

{projectName}

- )} -
-
-
-
- -
-
-
-
- - -
-
-
- ) -} - -export const ProjectUsageLoadingState = () => { - return ( -
- -
- - - - -
-
- ) -} diff --git a/apps/studio/components/layouts/ProjectLayout/index.tsx b/apps/studio/components/layouts/ProjectLayout/index.tsx index c083583c8fb..21e7aad58b3 100644 --- a/apps/studio/components/layouts/ProjectLayout/index.tsx +++ b/apps/studio/components/layouts/ProjectLayout/index.tsx @@ -31,7 +31,6 @@ import ProductMenuBar from '../Navigation/ProductMenuBar' import BuildingState from './BuildingState' import ConnectingState from './ConnectingState' import { getSectionKeyFromPathname, MobileMenuContent } from './LayoutHeader/MobileMenuContent' -import { LoadingState } from './LoadingState' import { ProjectPausedState } from './PausedState/ProjectPausedState' import { PauseFailedState } from './PauseFailedState' import { PausingState } from './PausingState' @@ -355,7 +354,7 @@ export const ProjectLayout = forwardRef
) : ( - + {children} @@ -403,8 +402,6 @@ const MenuBarWrapper = ({ } interface ContentWrapperProps { - isLoading: boolean - isBlocking?: boolean children: ReactNode } @@ -420,7 +417,7 @@ interface ContentWrapperProps { * * [TODO] Next iteration should scrape long polling and just listen to the project's status */ -const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapperProps) => { +const ContentWrapper = ({ children }: ContentWrapperProps) => { const router = useRouter() const { ref } = useParams() const state = useDatabaseSelectorStateSnapshot() @@ -430,7 +427,6 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp const requiresDbConnection = !routesToIgnoreDBConnection.some((x) => router.pathname.includes(x)) const requiresPostgrestConnection = !routesToIgnorePostgrestConnection.includes(router.pathname) - const requiresProjectDetails = !routesToIgnoreProjectDetailsRequest.includes(router.pathname) const isRestarting = selectedProject?.status === PROJECT_STATUS.RESTARTING const isResizing = selectedProject?.status === PROJECT_STATUS.RESIZING @@ -465,10 +461,6 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp if (ref) state.setSelectedDatabaseId(ref) }, [ref]) - if (isBlocking && (isLoading || (requiresProjectDetails && selectedProject === undefined))) { - return router.pathname.endsWith('[ref]') ? : - } - if (isRestarting && !isBackupsPage) { return } diff --git a/e2e/studio/features/connect.spec.ts b/e2e/studio/features/connect.spec.ts index 50d8be1e8c9..4c8192fc9e1 100644 --- a/e2e/studio/features/connect.spec.ts +++ b/e2e/studio/features/connect.spec.ts @@ -168,7 +168,8 @@ test.describe('Connect Sheet deep linking', async () => { await expect(page, 'framework param should be in URL initially').toHaveURL(/framework=nextjs/) - await page.getByRole('button', { name: /ORM/ }).click() + const connectSheet = page.getByLabel('Connect to your project') + await connectSheet.getByRole('button', { name: /ORM/ }).click() await expect(page, 'framework param should be cleared after mode change').not.toHaveURL( /framework=nextjs/