From 6a777e1faee8930cf7901ed6708945bb14e5037c Mon Sep 17 00:00:00 2001 From: Chris Stockton <180184+cstockton@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:28:47 -0700 Subject: [PATCH 1/6] fix: small typo in identites -> identities (#38292) Co-authored-by: Chris Stockton --- apps/docs/spec/supabase_dart_v2.yml | 2 +- apps/docs/spec/supabase_js_v2.yml | 2 +- apps/docs/spec/supabase_py_v2.yml | 2 +- apps/docs/spec/supabase_swift_v2.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/spec/supabase_dart_v2.yml b/apps/docs/spec/supabase_dart_v2.yml index 2a1dd1b2d4e1b..6219bea93b790 100644 --- a/apps/docs/spec/supabase_dart_v2.yml +++ b/apps/docs/spec/supabase_dart_v2.yml @@ -1341,7 +1341,7 @@ functions: isSpotlight: true code: | ```dart - // retrieve all identites linked to a user + // retrieve all identities linked to a user final identities = await supabase.auth.getUserIdentities(); // find the google identity diff --git a/apps/docs/spec/supabase_js_v2.yml b/apps/docs/spec/supabase_js_v2.yml index dcaf090330929..2e6c387f8d8a9 100644 --- a/apps/docs/spec/supabase_js_v2.yml +++ b/apps/docs/spec/supabase_js_v2.yml @@ -1625,7 +1625,7 @@ functions: isSpotlight: true code: | ```js - // retrieve all identites linked to a user + // retrieve all identities linked to a user const identities = await supabase.auth.getUserIdentities() // find the google identity diff --git a/apps/docs/spec/supabase_py_v2.yml b/apps/docs/spec/supabase_py_v2.yml index 661cf1e884e8d..31f21f040112f 100644 --- a/apps/docs/spec/supabase_py_v2.yml +++ b/apps/docs/spec/supabase_py_v2.yml @@ -1550,7 +1550,7 @@ functions: isSpotlight: true code: | ```python - # retrieve all identites linked to a user + # retrieve all identities linked to a user response = supabase.auth.get_user_identities() # find the google identity diff --git a/apps/docs/spec/supabase_swift_v2.yml b/apps/docs/spec/supabase_swift_v2.yml index 37e8587aabd57..9970a629f1209 100644 --- a/apps/docs/spec/supabase_swift_v2.yml +++ b/apps/docs/spec/supabase_swift_v2.yml @@ -944,7 +944,7 @@ functions: isSpotlight: true code: | ```swift - // retrieve all identites linked to a user + // retrieve all identities linked to a user let identities = try await supabase.auth.userIdentities() // find the google identity From 29ab42957a075323ad59d8ebdf88166fec7bb85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Thu, 4 Sep 2025 00:46:48 +0200 Subject: [PATCH 2/6] ref(docs): Respect x-internal openapi spec attribute (#38255) --- apps/docs/spec/common-api-sections.json | 54 ------------------- .../spec/sections/generateMgmtApiSections.cts | 6 +++ 2 files changed, 6 insertions(+), 54 deletions(-) diff --git a/apps/docs/spec/common-api-sections.json b/apps/docs/spec/common-api-sections.json index 733b6ffb8149b..0e81bfb4e9400 100644 --- a/apps/docs/spec/common-api-sections.json +++ b/apps/docs/spec/common-api-sections.json @@ -196,12 +196,6 @@ "slug": "v1-apply-a-migration", "type": "operation" }, - { - "id": "v1-create-restore-point", - "title": "Create restore point", - "slug": "v1-create-restore-point", - "type": "operation" - }, { "id": "v1-disable-readonly-mode-temporarily", "title": "Disable readonly mode temporarily", @@ -256,12 +250,6 @@ "slug": "v1-get-readonly-mode-status", "type": "operation" }, - { - "id": "v1-get-restore-point", - "title": "Get restore point", - "slug": "v1-get-restore-point", - "type": "operation" - }, { "id": "v1-get-ssl-enforcement-config", "title": "Get ssl enforcement config", @@ -310,12 +298,6 @@ "slug": "v1-setup-a-read-replica", "type": "operation" }, - { - "id": "v1-undo", - "title": "Undo", - "slug": "v1-undo", - "type": "operation" - }, { "id": "v1-update-pooler-config", "title": "Update pooler config", @@ -538,12 +520,6 @@ "slug": "v1-exchange-oauth-token", "type": "operation" }, - { - "id": "v1-oauth-authorize-project-claim", - "title": "Oauth authorize project claim", - "slug": "v1-oauth-authorize-project-claim", - "type": "operation" - }, { "id": "v1-revoke-token", "title": "Revoke token", @@ -556,12 +532,6 @@ "type": "category", "title": "Organizations", "items": [ - { - "id": "v1-claim-project-for-organization", - "title": "Claim project for organization", - "slug": "v1-claim-project-for-organization", - "type": "operation" - }, { "id": "v1-create-an-organization", "title": "Create an organization", @@ -574,12 +544,6 @@ "slug": "v1-get-an-organization", "type": "operation" }, - { - "id": "v1-get-organization-project-claim", - "title": "Get organization project claim", - "slug": "v1-get-organization-project-claim", - "type": "operation" - }, { "id": "v1-list-all-organizations", "title": "List all organizations", @@ -610,12 +574,6 @@ "slug": "v1-create-a-project", "type": "operation" }, - { - "id": "v1-create-project-claim-token", - "title": "Create project claim token", - "slug": "v1-create-project-claim-token", - "type": "operation" - }, { "id": "v1-delete-a-project", "title": "Delete a project", @@ -628,12 +586,6 @@ "slug": "v1-delete-network-bans", "type": "operation" }, - { - "id": "v1-delete-project-claim-token", - "title": "Delete project claim token", - "slug": "v1-delete-project-claim-token", - "type": "operation" - }, { "id": "v1-get-network-restrictions", "title": "Get network restrictions", @@ -658,12 +610,6 @@ "slug": "v1-get-project", "type": "operation" }, - { - "id": "v1-get-project-claim-token", - "title": "Get project claim token", - "slug": "v1-get-project-claim-token", - "type": "operation" - }, { "id": "v1-get-services-health", "title": "Get services health", diff --git a/apps/docs/spec/sections/generateMgmtApiSections.cts b/apps/docs/spec/sections/generateMgmtApiSections.cts index 580acb24866ee..c235b7eccf2ef 100644 --- a/apps/docs/spec/sections/generateMgmtApiSections.cts +++ b/apps/docs/spec/sections/generateMgmtApiSections.cts @@ -41,6 +41,12 @@ function extractSectionsFromOpenApi(filePath, outputPath) { for (const route in openApiJson.paths) { const methods = openApiJson.paths[route] for (const method in methods) { + // We are using `x-internal` to hide endpoints from the docs, + // but still have them included in the spec so they generate types and can be used. + if (methods[method]['x-internal']) { + continue + } + const tag = methods[method].tags?.[0] const operationId = methods[method].operationId // If operationId is not in the form of a slug ignore it. From 0f712bc9e28b724aa3cb29f350299b2ddfe4250a Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Thu, 4 Sep 2025 10:58:30 +1000 Subject: [PATCH 3/6] Home New: Top (#38336) * new home top * misc clean ups * branching truncate * revert to button * fixes * Add comments + small style nudges * Add comment * nit * Use Timestampinfo * final final final v4 * I swear last nit i found * FIX --------- Co-authored-by: Joshen Lim --- .../components/interfaces/Home/Home.tsx | 297 ++++++++++++++++ .../interfaces/HomeNew/ActivityStats.tsx | 131 +++++++ .../components/interfaces/HomeNew/Home.tsx | 115 ++++++ .../interfaces/HomeNew/ServiceStatus.tsx | 331 ++++++++++++++++++ .../interfaces/HomeNew/SortableSection.tsx | 36 ++ .../interfaces/HomeNew/TopSection.tsx | 94 +++++ .../InstanceConfiguration.tsx | 200 ++++++----- .../Infrastructure/InfrastructureInfo.tsx | 2 +- apps/studio/pages/project/[ref]/index.tsx | 306 +--------------- .../ui-patterns/src/TimestampInfo/index.tsx | 10 +- .../SimpleCodeBlock/SimpleCodeBlock.tsx | 13 +- 11 files changed, 1135 insertions(+), 400 deletions(-) create mode 100644 apps/studio/components/interfaces/Home/Home.tsx create mode 100644 apps/studio/components/interfaces/HomeNew/ActivityStats.tsx create mode 100644 apps/studio/components/interfaces/HomeNew/Home.tsx create mode 100644 apps/studio/components/interfaces/HomeNew/ServiceStatus.tsx create mode 100644 apps/studio/components/interfaces/HomeNew/SortableSection.tsx create mode 100644 apps/studio/components/interfaces/HomeNew/TopSection.tsx diff --git a/apps/studio/components/interfaces/Home/Home.tsx b/apps/studio/components/interfaces/Home/Home.tsx new file mode 100644 index 0000000000000..014d8749a84a5 --- /dev/null +++ b/apps/studio/components/interfaces/Home/Home.tsx @@ -0,0 +1,297 @@ +import dayjs from 'dayjs' +import Link from 'next/link' +import { useEffect, useMemo, useRef } from 'react' + +import { useParams } from 'common' +import { ClientLibrary } from 'components/interfaces/Home' +import { AdvisorWidget } from 'components/interfaces/Home/AdvisorWidget' +import { ExampleProject } from 'components/interfaces/Home/ExampleProject' +import { CLIENT_LIBRARIES, EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants' +import { NewProjectPanel } from 'components/interfaces/Home/NewProjectPanel/NewProjectPanel' +import { ProjectUsageSection } from 'components/interfaces/Home/ProjectUsageSection' +import { ServiceStatus } from 'components/interfaces/Home/ServiceStatus' +import { ProjectPausedState } from 'components/layouts/ProjectLayout/PausedState/ProjectPausedState' +import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper' +import { InlineLink } from 'components/ui/InlineLink' +import { ProjectUpgradeFailedBanner } from 'components/ui/ProjectUpgradeFailedBanner' +import { useBranchesQuery } from 'data/branches/branches-query' +import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query' +import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' +import { useTablesQuery } from 'data/tables/tables-query' +import { useCustomContent } from 'hooks/custom-content/useCustomContent' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' +import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' +import { + useIsOrioleDb, + useProjectByRefQuery, + useSelectedProjectQuery, +} from 'hooks/misc/useSelectedProject' +import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' +import { useAppStateSnapshot } from 'state/app-state' +import { + Badge, + cn, + Tabs_Shadcn_, + TabsContent_Shadcn_, + TabsList_Shadcn_, + TabsTrigger_Shadcn_, + Tooltip, + TooltipContent, + TooltipTrigger, +} from 'ui' +import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' + +export const Home = () => { + const { data: project } = useSelectedProjectQuery() + const { data: organization } = useSelectedOrganizationQuery() + const { data: parentProject } = useProjectByRefQuery(project?.parent_project_ref) + const isOrioleDb = useIsOrioleDb() + const snap = useAppStateSnapshot() + const { ref, enableBranching } = useParams() + + const { projectHomepageExampleProjects } = useCustomContent(['project_homepage:example_projects']) + + const { + projectHomepageShowAllClientLibraries: showAllClientLibraries, + projectHomepageShowInstanceSize: showInstanceSize, + projectHomepageShowExamples: showExamples, + } = useIsFeatureEnabled([ + 'project_homepage:show_all_client_libraries', + 'project_homepage:show_instance_size', + 'project_homepage:show_examples', + ]) + + const clientLibraries = useMemo(() => { + if (showAllClientLibraries) { + return CLIENT_LIBRARIES + } + return CLIENT_LIBRARIES.filter((library) => library.language === 'JavaScript') + }, [showAllClientLibraries]) + + const hasShownEnableBranchingModalRef = useRef(false) + const isPaused = project?.status === PROJECT_STATUS.INACTIVE + const isNewProject = dayjs(project?.inserted_at).isAfter(dayjs().subtract(2, 'day')) + + useEffect(() => { + if (enableBranching && !hasShownEnableBranchingModalRef.current) { + hasShownEnableBranchingModalRef.current = true + snap.setShowCreateBranchModal(true) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [enableBranching]) + + const { data: tablesData, isLoading: isLoadingTables } = useTablesQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + schema: 'public', + }) + const { data: functionsData, isLoading: isLoadingFunctions } = useEdgeFunctionsQuery({ + projectRef: project?.ref, + }) + const { data: replicasData, isLoading: isLoadingReplicas } = useReadReplicasQuery({ + projectRef: project?.ref, + }) + + const { data: branches } = useBranchesQuery({ + projectRef: project?.parent_project_ref ?? project?.ref, + }) + + const mainBranch = branches?.find((branch) => branch.is_default) + const currentBranch = branches?.find((branch) => branch.project_ref === project?.ref) + const isMainBranch = currentBranch?.name === mainBranch?.name + let projectName = 'Welcome to your project' + + if (currentBranch && !isMainBranch) { + projectName = currentBranch?.name + } else if (project?.name) { + projectName = project?.name + } + + const tablesCount = Math.max(0, tablesData?.length ?? 0) + const functionsCount = Math.max(0, functionsData?.length ?? 0) + // [Joshen] JFYI minus 1 as the replicas endpoint returns the primary DB minimally + const replicasCount = Math.max(0, (replicasData?.length ?? 1) - 1) + + return ( +
+
+
+
+
+
+ {!isMainBranch && ( + + {parentProject?.name} + + )} +

{projectName}

+
+
+ {isOrioleDb && ( + + + OrioleDB + + + This project is using Postgres with OrioleDB which is currently in preview and + not suitable for production workloads. View our{' '} + + documentation + {' '} + for all limitations. + + + )} + {showInstanceSize && ( + + )} +
+
+
+ {project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && ( +
+
+ + Tables + + + {isLoadingTables ? ( + + ) : ( +

{tablesCount}

+ )} +
+ + {IS_PLATFORM && ( +
+ + Functions + + {isLoadingFunctions ? ( + + ) : ( +

{functionsCount}

+ )} +
+ )} + + {IS_PLATFORM && ( +
+ + Replicas + + {isLoadingReplicas ? ( + + ) : ( +

{replicasCount}

+ )} +
+ )} +
+ )} + {IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && ( +
+ +
+ )} +
+
+ + {isPaused && } +
+
+ + {!isPaused && ( + <> +
+
+ {IS_PLATFORM && project?.status !== PROJECT_STATUS.INACTIVE && ( + <>{isNewProject ? : } + )} + {!isNewProject && project?.status !== PROJECT_STATUS.INACTIVE && } +
+
+ +
+
+ {project?.status !== PROJECT_STATUS.INACTIVE && ( + <> +
+

Client libraries

+
+ {clientLibraries.map((library) => ( + + ))} +
+
+ {showExamples && ( +
+

Example projects

+ {!!projectHomepageExampleProjects ? ( +
+ {projectHomepageExampleProjects + .sort((a, b) => a.title.localeCompare(b.title)) + .map((project) => ( + + ))} +
+ ) : ( +
+ + + App Frameworks + + Mobile Frameworks + + + +
+ {EXAMPLE_PROJECTS.filter((project) => project.type === 'app') + .sort((a, b) => a.title.localeCompare(b.title)) + .map((project) => ( + + ))} +
+
+ +
+ {EXAMPLE_PROJECTS.filter((project) => project.type === 'mobile') + .sort((a, b) => a.title.localeCompare(b.title)) + .map((project) => ( + + ))} +
+
+
+
+ )} +
+ )} + + )} +
+
+ + )} +
+ ) +} diff --git a/apps/studio/components/interfaces/HomeNew/ActivityStats.tsx b/apps/studio/components/interfaces/HomeNew/ActivityStats.tsx new file mode 100644 index 0000000000000..22464a7a5fbc3 --- /dev/null +++ b/apps/studio/components/interfaces/HomeNew/ActivityStats.tsx @@ -0,0 +1,131 @@ +import dayjs from 'dayjs' +import { GitBranch } from 'lucide-react' +import Link from 'next/link' +import { useMemo } from 'react' + +import { useParams } from 'common' +import { useBranchesQuery } from 'data/branches/branches-query' +import { useBackupsQuery } from 'data/database/backups-query' +import { useMigrationsQuery } from 'data/database/migrations-query' +import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' +import { cn, Skeleton } from 'ui' +import { TimestampInfo } from 'ui-patterns' +import { ServiceStatus } from './ServiceStatus' + +export const ActivityStats = () => { + const { ref } = useParams() + const { data: project } = useSelectedProjectQuery() + + const { data: branchesData, isLoading: isLoadingBranches } = useBranchesQuery({ + projectRef: project?.parent_project_ref ?? project?.ref, + }) + const isDefaultProject = project?.parent_project_ref === undefined + const currentBranch = useMemo( + () => (branchesData ?? []).find((b) => b.project_ref === ref), + [branchesData, ref] + ) + const latestNonDefaultBranch = useMemo(() => { + const list = (branchesData ?? []).filter((b) => !b.is_default) + if (list.length === 0) return undefined + return list + .slice() + .sort( + (a, b) => + new Date(b.created_at ?? b.updated_at).valueOf() - + new Date(a.created_at ?? a.updated_at).valueOf() + )[0] + }, [branchesData]) + + const { data: migrationsData, isLoading: isLoadingMigrations } = useMigrationsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + const latestMigration = useMemo(() => (migrationsData ?? [])[0], [migrationsData]) + + const { data: backupsData, isLoading: isLoadingBackups } = useBackupsQuery({ + projectRef: project?.ref, + }) + const latestBackup = useMemo(() => { + const list = backupsData?.backups ?? [] + if (list.length === 0) return undefined + return list + .slice() + .sort((a, b) => new Date(b.inserted_at).valueOf() - new Date(a.inserted_at).valueOf())[0] + }, [backupsData]) + + return ( +
+
+
+

Status

+ +
+ + +

Last migration

+ +
+ {isLoadingMigrations ? ( + + ) : latestMigration ? ( + + ) : ( +

No migrations

+ )} +
+ + + +

Last backup

+ +
+ {isLoadingBackups ? ( + + ) : backupsData?.pitr_enabled ? ( +

PITR enabled

+ ) : latestBackup ? ( + + ) : ( +

No backups

+ )} +
+ + + +

+ {isDefaultProject ? 'Recent branch' : 'Branch Created'} +

+ +
+ {isLoadingBranches ? ( + + ) : isDefaultProject ? ( +
+ +

+ {latestNonDefaultBranch?.name ?? 'No branches'} +

+
+ ) : currentBranch?.created_at ? ( + + ) : ( +

Unknown

+ )} +
+ +
+
+ ) +} diff --git a/apps/studio/components/interfaces/HomeNew/Home.tsx b/apps/studio/components/interfaces/HomeNew/Home.tsx new file mode 100644 index 0000000000000..5c2390d1c473f --- /dev/null +++ b/apps/studio/components/interfaces/HomeNew/Home.tsx @@ -0,0 +1,115 @@ +import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core' +import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' +import { useEffect, useRef } from 'react' + +import { useParams } from 'common' +import { SortableSection } from 'components/interfaces/HomeNew/SortableSection' +import { TopSection } from 'components/interfaces/HomeNew/TopSection' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' +import { useBranchesQuery } from 'data/branches/branches-query' +import { useLocalStorage } from 'hooks/misc/useLocalStorage' +import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' +import { + useIsOrioleDb, + useProjectByRefQuery, + useSelectedProjectQuery, +} from 'hooks/misc/useSelectedProject' +import { PROJECT_STATUS } from 'lib/constants' +import { useAppStateSnapshot } from 'state/app-state' + +export const HomeV2 = () => { + const { ref, enableBranching } = useParams() + const isOrioleDb = useIsOrioleDb() + const snap = useAppStateSnapshot() + const { data: project } = useSelectedProjectQuery() + const { data: organization } = useSelectedOrganizationQuery() + const { data: parentProject } = useProjectByRefQuery(project?.parent_project_ref) + + const hasShownEnableBranchingModalRef = useRef(false) + const isPaused = project?.status === PROJECT_STATUS.INACTIVE + + const { data: branches } = useBranchesQuery({ + projectRef: project?.parent_project_ref ?? project?.ref, + }) + + const mainBranch = branches?.find((branch) => branch.is_default) + const currentBranch = branches?.find((branch) => branch.project_ref === project?.ref) + const isMainBranch = currentBranch?.name === mainBranch?.name + + const projectName = + currentBranch && !isMainBranch + ? currentBranch.name + : project?.name + ? project.name + : 'Welcome to your project' + + const [sectionOrder, setSectionOrder] = useLocalStorage( + `home-section-order-${project?.ref || 'default'}`, + ['getting-started', 'usage', 'advisor', 'custom-report'] + ) + + const [gettingStartedState] = useLocalStorage<'empty' | 'code' | 'no-code' | 'hidden'>( + `home-getting-started-${project?.ref || 'default'}`, + 'empty' + ) + + const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } })) + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event + if (!over || active.id === over.id) return + setSectionOrder((items) => { + const oldIndex = items.indexOf(String(active.id)) + const newIndex = items.indexOf(String(over.id)) + if (oldIndex === -1 || newIndex === -1) return items + return arrayMove(items, oldIndex, newIndex) + }) + } + + useEffect(() => { + if (enableBranching && !hasShownEnableBranchingModalRef.current) { + hasShownEnableBranchingModalRef.current = true + snap.setShowCreateBranchModal(true) + } + }, [enableBranching, snap]) + + return ( +
+ + + + + + + {!isPaused && ( + + + + id !== 'getting-started' || gettingStartedState !== 'hidden' + )} + strategy={verticalListSortingStrategy} + > + {sectionOrder.map((id) => ( + + {id} + + ))} + + + + + )} +
+ ) +} diff --git a/apps/studio/components/interfaces/HomeNew/ServiceStatus.tsx b/apps/studio/components/interfaces/HomeNew/ServiceStatus.tsx new file mode 100644 index 0000000000000..768c0de480a36 --- /dev/null +++ b/apps/studio/components/interfaces/HomeNew/ServiceStatus.tsx @@ -0,0 +1,331 @@ +import { AlertTriangle, CheckCircle2, ChevronDown, ChevronRight, Loader2 } from 'lucide-react' +import Link from 'next/link' +import { useState } from 'react' + +import { PopoverSeparator } from '@ui/components/shadcn/ui/popover' +import { useParams } from 'common' +import { useBranchesQuery } from 'data/branches/branches-query' +import { useEdgeFunctionServiceStatusQuery } from 'data/service-status/edge-functions-status-query' +import { + ProjectServiceStatus, + useProjectServiceStatusQuery, +} from 'data/service-status/service-status-query' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' +import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' +import { + Button, + InfoIcon, + PopoverContent_Shadcn_, + PopoverTrigger_Shadcn_, + Popover_Shadcn_, + cn, +} from 'ui' + +/** + * [Joshen] JFYI before we go live with this, we need to revisit the migrations section + * as I don't think it should live in the ServiceStatus component since its not indicative + * of a project's "service". ServiceStatus's intention is to be an ongoing health/status check. + * + * For context, migrations are meant to be indicative for only when creating branches or projects + * with an initial SQL, so "healthy" migrations just means that migrations have all been successfully + * ran. So it might be a matter of decoupling "ready" state vs "health checks" + * [Edit] Now that migrations are only showing up if the project is a branch, i think its okay for now + * + * [Joshen] Another issue that requires investigation before we go live with the changes: + * We've removed the isProjectNew check in this component which we had that logic cause new + * projects would show unhealthy as the services are still starting up - but it causes a + * perceived negative impression as new projects were showing unhealthy, hence the 5 minute + * threshold check (we’d show “Coming up” instead of “unhealthy” if the project is within 5 + * minutes of when it was created). Might be related to decoupling "ready" state vs "health checks" + */ + +const StatusMessage = ({ + status, + isLoading, + isHealthy, +}: { + isLoading: boolean + isHealthy: boolean + status?: ProjectServiceStatus +}) => { + if (isHealthy) return 'Healthy' + if (isLoading) return 'Checking status' + if (status === 'UNHEALTHY') return 'Unhealthy' + if (status === 'COMING_UP') return 'Coming up...' + if (status === 'ACTIVE_HEALTHY') return 'Healthy' + if (status) return status + return 'Unable to connect' +} + +const iconProps = { + size: 18, + strokeWidth: 1.5, +} +const LoaderIcon = () => +const AlertIcon = () => +const CheckIcon = () => + +const StatusIcon = ({ + isLoading, + isHealthy, + projectStatus, +}: { + isLoading: boolean + isHealthy: boolean + projectStatus?: ProjectServiceStatus +}) => { + if (isHealthy) return + if (isLoading) return + if (projectStatus === 'UNHEALTHY') return + if (projectStatus === 'COMING_UP') return + if (projectStatus === 'ACTIVE_HEALTHY') return + return +} + +export const ServiceStatus = () => { + const { ref } = useParams() + const { data: project } = useSelectedProjectQuery() + const [open, setOpen] = useState(false) + + const { + projectAuthAll: authEnabled, + projectEdgeFunctionAll: edgeFunctionsEnabled, + realtimeAll: realtimeEnabled, + projectStorageAll: storageEnabled, + } = useIsFeatureEnabled([ + 'project_auth:all', + 'project_edge_function:all', + 'realtime:all', + 'project_storage:all', + ]) + + const isBranch = project?.parentRef !== project?.ref + + // Get branches data when on a branch + const { data: branches, isLoading: isBranchesLoading } = useBranchesQuery( + { projectRef: isBranch ? project?.parentRef : undefined }, + { + enabled: isBranch, + } + ) + + const currentBranch = isBranch + ? branches?.find((branch) => branch.project_ref === ref) + : undefined + + // [Joshen] Need pooler service check eventually + const { data: status, isLoading } = useProjectServiceStatusQuery( + { projectRef: ref }, + { refetchInterval: (data) => (data?.some((service) => !service.healthy) ? 5000 : false) } + ) + const { data: edgeFunctionsStatus } = useEdgeFunctionServiceStatusQuery( + { projectRef: ref }, + { refetchInterval: (data) => (!data?.healthy ? 5000 : false) } + ) + + const authStatus = status?.find((service) => service.name === 'auth') + const restStatus = status?.find((service) => service.name === 'rest') + const realtimeStatus = status?.find((service) => service.name === 'realtime') + const storageStatus = status?.find((service) => service.name === 'storage') + const dbStatus = status?.find((service) => service.name === 'db') + + const isMigrationLoading = + project?.status === 'COMING_UP' || + (isBranch && + (isBranchesLoading || + currentBranch?.status === 'CREATING_PROJECT' || + currentBranch?.status === 'RUNNING_MIGRATIONS')) + + // [Joshen] Need individual troubleshooting docs for each service eventually for users to self serve + const services: { + name: string + error?: string + docsUrl?: string + isLoading: boolean + isHealthy: boolean + status: ProjectServiceStatus + logsUrl: string + }[] = [ + { + name: 'Database', + error: undefined, + docsUrl: undefined, + isLoading: isLoading, + isHealthy: !!dbStatus?.healthy, + status: dbStatus?.status ?? 'UNHEALTHY', + logsUrl: '/logs/postgres-logs', + }, + { + name: 'PostgREST', + error: restStatus?.error, + docsUrl: undefined, + isLoading, + isHealthy: !!restStatus?.healthy, + status: restStatus?.status ?? 'UNHEALTHY', + logsUrl: '/logs/postgrest-logs', + }, + ...(authEnabled + ? [ + { + name: 'Auth', + error: authStatus?.error, + docsUrl: undefined, + isLoading, + isHealthy: !!authStatus?.healthy, + status: authStatus?.status ?? 'UNHEALTHY', + logsUrl: '/logs/auth-logs', + }, + ] + : []), + ...(realtimeEnabled + ? [ + { + name: 'Realtime', + error: realtimeStatus?.error, + docsUrl: undefined, + isLoading, + isHealthy: !!realtimeStatus?.healthy, + status: realtimeStatus?.status ?? 'UNHEALTHY', + logsUrl: '/logs/realtime-logs', + }, + ] + : []), + ...(storageEnabled + ? [ + { + name: 'Storage', + error: storageStatus?.error, + docsUrl: undefined, + isLoading, + isHealthy: !!storageStatus?.healthy, + status: storageStatus?.status ?? 'UNHEALTHY', + logsUrl: '/logs/storage-logs', + }, + ] + : []), + ...(edgeFunctionsEnabled + ? [ + { + name: 'Edge Functions', + error: undefined, + docsUrl: 'https://supabase.com/docs/guides/functions/troubleshooting', + isLoading, + isHealthy: !!edgeFunctionsStatus?.healthy, + status: edgeFunctionsStatus?.healthy + ? 'ACTIVE_HEALTHY' + : isLoading + ? 'COMING_UP' + : ('UNHEALTHY' as ProjectServiceStatus), + logsUrl: '/logs/edge-functions-logs', + }, + ] + : []), + ...(isBranch + ? [ + { + name: 'Migrations', + error: undefined, + docsUrl: undefined, + isLoading: isBranchesLoading, + isHealthy: isBranch + ? currentBranch?.status === 'FUNCTIONS_DEPLOYED' + : !isMigrationLoading, + status: (isBranch + ? currentBranch?.status === 'FUNCTIONS_DEPLOYED' + ? 'ACTIVE_HEALTHY' + : currentBranch?.status === 'FUNCTIONS_FAILED' || + currentBranch?.status === 'MIGRATIONS_FAILED' + ? 'UNHEALTHY' + : 'COMING_UP' + : isMigrationLoading + ? 'COMING_UP' + : 'ACTIVE_HEALTHY') as ProjectServiceStatus, + logsUrl: isBranch ? '/branches' : '/logs/database-logs', + }, + ] + : []), + ] + + const isLoadingChecks = services.some((service) => service.isLoading) + const allServicesOperational = services.every((service) => service.isHealthy) + + const anyUnhealthy = services.some( + (service) => !service.isHealthy && service.status !== 'COMING_UP' + ) + const anyComingUp = services.some((service) => service.status === 'COMING_UP') + const overallStatusLabel = isLoadingChecks + ? 'Checking...' + : anyUnhealthy + ? 'Unhealthy' + : anyComingUp || isMigrationLoading + ? 'Coming up...' + : 'Healthy' + + return ( + + + + + + {services.map((service) => ( + +
+ +
+

{service.name}

+

+ +

+
+
+
+ View logs + +
+ + ))} + {allServicesOperational ? null : ( + <> + +
+
+ +
+ Recently restored projects can take up to 5 minutes to become fully operational. +
+ + )} +
+
+ ) +} diff --git a/apps/studio/components/interfaces/HomeNew/SortableSection.tsx b/apps/studio/components/interfaces/HomeNew/SortableSection.tsx new file mode 100644 index 0000000000000..78704e042da48 --- /dev/null +++ b/apps/studio/components/interfaces/HomeNew/SortableSection.tsx @@ -0,0 +1,36 @@ +import { useSortable } from '@dnd-kit/sortable' +import { GripVertical } from 'lucide-react' +import type { CSSProperties, ReactNode } from 'react' +import { cn } from 'ui' + +type SortableSectionProps = { + id: string + children: ReactNode +} + +export const SortableSection = ({ id, children }: SortableSectionProps) => { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id, + }) + + const style: CSSProperties = { + transform: transform + ? `translate3d(${Math.round(transform.x)}px, ${Math.round(transform.y)}px, 0)` + : undefined, + transition, + } + + return ( +
+ +
{children}
+
+ ) +} diff --git a/apps/studio/components/interfaces/HomeNew/TopSection.tsx b/apps/studio/components/interfaces/HomeNew/TopSection.tsx new file mode 100644 index 0000000000000..56c026709ebb2 --- /dev/null +++ b/apps/studio/components/interfaces/HomeNew/TopSection.tsx @@ -0,0 +1,94 @@ +import Link from 'next/link' + +import { ActivityStats } from 'components/interfaces/HomeNew/ActivityStats' +import { ProjectPausedState } from 'components/layouts/ProjectLayout/PausedState/ProjectPausedState' +import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper' +import { InlineLink } from 'components/ui/InlineLink' +import { ProjectUpgradeFailedBanner } from 'components/ui/ProjectUpgradeFailedBanner' +import { ReactFlowProvider } from 'reactflow' +import { Badge, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui' +import { InstanceConfiguration } from '../Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration' + +interface TopSectionProps { + projectName: string + isMainBranch?: boolean + parentProject?: { ref?: string; name?: string } | null + isOrioleDb?: boolean + project: any + organization: any + projectRef?: string + isPaused: boolean +} + +export const TopSection = ({ + projectName, + isMainBranch, + parentProject, + isOrioleDb, + project, + organization, + isPaused, +}: TopSectionProps) => { + return ( +
+
+
+
+
+ {!isMainBranch && ( + + {parentProject?.name} + + )} +

{projectName}

+
+
+ {isOrioleDb && ( + + + OrioleDB + + + This project is using Postgres with OrioleDB which is currently in preview and + not suitable for production workloads. View our{' '} + + documentation + {' '} + for all limitations. + + + )} + +
+
+
+ +
+
+
+
+ + + +
+
+
+ + {isPaused && } +
+ ) +} diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx index ea3cee853fcb0..f74301cc8c6ab 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx @@ -1,12 +1,13 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useParams } from 'common' import { partition } from 'lodash' import { ChevronDown, Globe2, Loader2, Network } from 'lucide-react' import { useTheme } from 'next-themes' +import Link from 'next/link' import { useEffect, useMemo, useRef, useState } from 'react' import ReactFlow, { Background, Edge, ReactFlowProvider, useReactFlow } from 'reactflow' import 'reactflow/dist/style.css' +import { useParams } from 'common' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query' @@ -18,7 +19,6 @@ import { import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { timeout } from 'lib/helpers' -import Link from 'next/link' import { type AWS_REGIONS_KEYS } from 'shared-data' import { Button, @@ -40,7 +40,11 @@ import MapView from './MapView' import { RestartReplicaConfirmationModal } from './RestartReplicaConfirmationModal' import { useShowNewReplicaPanel } from './use-show-new-replica' -const InstanceConfigurationUI = () => { +interface InstanceConfigurationUIProps { + diagramOnly?: boolean +} + +const InstanceConfigurationUI = ({ diagramOnly = false }: InstanceConfigurationUIProps) => { const reactFlow = useReactFlow() const isOrioleDb = useIsOrioleDb() const { resolvedTheme } = useTheme() @@ -210,9 +214,9 @@ const InstanceConfigurationUI = () => { }, [isSuccessReplicas, isSuccessLoadBalancers, nodes, edges, view]) return ( -
+
@@ -224,70 +228,72 @@ const InstanceConfigurationUI = () => { {isError && } {isSuccessReplicas && !isLoadingProject && ( <> -
-
- 0 ? 'rounded-r-none' : '')} - onClick={() => setShowNewReplicaPanel(true)} - tooltip={{ - content: { - side: 'bottom', - text: !canManageReplicas - ? 'You need additional permissions to deploy replicas' - : isOrioleDb - ? 'Read replicas are not supported with OrioleDB' - : undefined, - }, - }} - > - Deploy a new replica - - {replicas.length > 0 && ( - - -
- {project?.cloud_provider === 'AWS' && ( + {!diagramOnly && ( +
-
- )} -
+ {project?.cloud_provider === 'AWS' && ( +
+
+ )} +
+ )} {view === 'flow' ? ( { )}
- setRefetchInterval(5000)} - onClose={() => { - setNewReplicaRegion(undefined) - setShowNewReplicaPanel(false) - }} - /> + {!diagramOnly && ( + <> + setRefetchInterval(5000)} + onClose={() => { + setNewReplicaRegion(undefined) + setShowNewReplicaPanel(false) + }} + /> - setRefetchInterval(5000)} - onCancel={() => setSelectedReplicaToDrop(undefined)} - /> + setRefetchInterval(5000)} + onCancel={() => setSelectedReplicaToDrop(undefined)} + /> - setRefetchInterval(5000)} - onCancel={() => setShowDeleteAllModal(false)} - /> + setRefetchInterval(5000)} + onCancel={() => setShowDeleteAllModal(false)} + /> - setRefetchInterval(5000)} - onCancel={() => setSelectedReplicaToRestart(undefined)} - /> + setRefetchInterval(5000)} + onCancel={() => setSelectedReplicaToRestart(undefined)} + /> + + )}
) } -const InstanceConfiguration = () => { +interface InstanceConfigurationProps { + diagramOnly?: boolean +} + +export const InstanceConfiguration = ({ diagramOnly = false }: InstanceConfigurationProps) => { return ( - + ) } - -export default InstanceConfiguration diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureInfo.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureInfo.tsx index c2e375ade9bf2..88c9a63eba5dc 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureInfo.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureInfo.tsx @@ -24,7 +24,7 @@ import { TooltipTrigger, } from 'ui' import { ProjectUpgradeAlert } from '../General/Infrastructure/ProjectUpgradeAlert' -import InstanceConfiguration from './InfrastructureConfiguration/InstanceConfiguration' +import { InstanceConfiguration } from './InfrastructureConfiguration/InstanceConfiguration' import { ObjectsToBeDroppedWarning, ReadReplicasWarning, diff --git a/apps/studio/pages/project/[ref]/index.tsx b/apps/studio/pages/project/[ref]/index.tsx index 567ecaa180002..b786f450fe37b 100644 --- a/apps/studio/pages/project/[ref]/index.tsx +++ b/apps/studio/pages/project/[ref]/index.tsx @@ -1,308 +1,22 @@ -import dayjs from 'dayjs' -import Link from 'next/link' -import { useEffect, useMemo, useRef } from 'react' - -import { useParams } from 'common' -import { ClientLibrary } from 'components/interfaces/Home' -import { AdvisorWidget } from 'components/interfaces/Home/AdvisorWidget' -import { ExampleProject } from 'components/interfaces/Home/ExampleProject' -import { CLIENT_LIBRARIES, EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants' -import { NewProjectPanel } from 'components/interfaces/Home/NewProjectPanel/NewProjectPanel' -import { ProjectUsageSection } from 'components/interfaces/Home/ProjectUsageSection' -import { ServiceStatus } from 'components/interfaces/Home/ServiceStatus' +import { useFlag } from 'common' +import { Home } from 'components/interfaces/Home/Home' +import { HomeV2 } from 'components/interfaces/HomeNew/Home' import DefaultLayout from 'components/layouts/DefaultLayout' -import { ProjectPausedState } from 'components/layouts/ProjectLayout/PausedState/ProjectPausedState' import { ProjectLayoutWithAuth } from 'components/layouts/ProjectLayout/ProjectLayout' -import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper' -import { InlineLink } from 'components/ui/InlineLink' -import { ProjectUpgradeFailedBanner } from 'components/ui/ProjectUpgradeFailedBanner' -import { useBranchesQuery } from 'data/branches/branches-query' -import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query' -import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useTablesQuery } from 'data/tables/tables-query' -import { useCustomContent } from 'hooks/custom-content/useCustomContent' -import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' -import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' -import { - useIsOrioleDb, - useProjectByRefQuery, - useSelectedProjectQuery, -} from 'hooks/misc/useSelectedProject' -import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' -import { useAppStateSnapshot } from 'state/app-state' import type { NextPageWithLayout } from 'types' -import { - Badge, - cn, - Tabs_Shadcn_, - TabsContent_Shadcn_, - TabsList_Shadcn_, - TabsTrigger_Shadcn_, - Tooltip, - TooltipContent, - TooltipTrigger, -} from 'ui' -import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' - -const Home: NextPageWithLayout = () => { - const { data: project } = useSelectedProjectQuery() - const { data: organization } = useSelectedOrganizationQuery() - const { data: parentProject } = useProjectByRefQuery(project?.parent_project_ref) - const isOrioleDb = useIsOrioleDb() - const snap = useAppStateSnapshot() - const { ref, enableBranching } = useParams() - - const { projectHomepageExampleProjects } = useCustomContent(['project_homepage:example_projects']) - - const { - projectHomepageShowAllClientLibraries: showAllClientLibraries, - projectHomepageShowInstanceSize: showInstanceSize, - projectHomepageShowExamples: showExamples, - } = useIsFeatureEnabled([ - 'project_homepage:show_all_client_libraries', - 'project_homepage:show_instance_size', - 'project_homepage:show_examples', - ]) - - const clientLibraries = useMemo(() => { - if (showAllClientLibraries) { - return CLIENT_LIBRARIES - } - return CLIENT_LIBRARIES.filter((library) => library.language === 'JavaScript') - }, [showAllClientLibraries]) - - const hasShownEnableBranchingModalRef = useRef(false) - const isPaused = project?.status === PROJECT_STATUS.INACTIVE - const isNewProject = dayjs(project?.inserted_at).isAfter(dayjs().subtract(2, 'day')) - - useEffect(() => { - if (enableBranching && !hasShownEnableBranchingModalRef.current) { - hasShownEnableBranchingModalRef.current = true - snap.setShowCreateBranchModal(true) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [enableBranching]) - - const { data: tablesData, isLoading: isLoadingTables } = useTablesQuery({ - projectRef: project?.ref, - connectionString: project?.connectionString, - schema: 'public', - }) - const { data: functionsData, isLoading: isLoadingFunctions } = useEdgeFunctionsQuery({ - projectRef: project?.ref, - }) - const { data: replicasData, isLoading: isLoadingReplicas } = useReadReplicasQuery({ - projectRef: project?.ref, - }) - - const { data: branches } = useBranchesQuery({ - projectRef: project?.parent_project_ref ?? project?.ref, - }) - const mainBranch = branches?.find((branch) => branch.is_default) - const currentBranch = branches?.find((branch) => branch.project_ref === project?.ref) - const isMainBranch = currentBranch?.name === mainBranch?.name - let projectName = 'Welcome to your project' - - if (currentBranch && !isMainBranch) { - projectName = currentBranch?.name - } else if (project?.name) { - projectName = project?.name +const HomePage: NextPageWithLayout = () => { + const isHomeNew = useFlag('homeNew') + if (isHomeNew) { + return } - - const tablesCount = Math.max(0, tablesData?.length ?? 0) - const functionsCount = Math.max(0, functionsData?.length ?? 0) - // [Joshen] JFYI minus 1 as the replicas endpoint returns the primary DB minimally - const replicasCount = Math.max(0, (replicasData?.length ?? 1) - 1) - - return ( -
-
-
-
-
-
- {!isMainBranch && ( - - {parentProject?.name} - - )} -

{projectName}

-
-
- {isOrioleDb && ( - - - OrioleDB - - - This project is using Postgres with OrioleDB which is currently in preview and - not suitable for production workloads. View our{' '} - - documentation - {' '} - for all limitations. - - - )} - {showInstanceSize && ( - - )} -
-
-
- {project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && ( -
-
- - Tables - - - {isLoadingTables ? ( - - ) : ( -

{tablesCount}

- )} -
- - {IS_PLATFORM && ( -
- - Functions - - {isLoadingFunctions ? ( - - ) : ( -

{functionsCount}

- )} -
- )} - - {IS_PLATFORM && ( -
- - Replicas - - {isLoadingReplicas ? ( - - ) : ( -

{replicasCount}

- )} -
- )} -
- )} - {IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && ( -
- -
- )} -
-
- - {isPaused && } -
-
- - {!isPaused && ( - <> -
-
- {IS_PLATFORM && project?.status !== PROJECT_STATUS.INACTIVE && ( - <>{isNewProject ? : } - )} - {!isNewProject && project?.status !== PROJECT_STATUS.INACTIVE && } -
-
- -
-
- {project?.status !== PROJECT_STATUS.INACTIVE && ( - <> -
-

Client libraries

-
- {clientLibraries.map((library) => ( - - ))} -
-
- {showExamples && ( -
-

Example projects

- {!!projectHomepageExampleProjects ? ( -
- {projectHomepageExampleProjects - .sort((a, b) => a.title.localeCompare(b.title)) - .map((project) => ( - - ))} -
- ) : ( -
- - - App Frameworks - - Mobile Frameworks - - - -
- {EXAMPLE_PROJECTS.filter((project) => project.type === 'app') - .sort((a, b) => a.title.localeCompare(b.title)) - .map((project) => ( - - ))} -
-
- -
- {EXAMPLE_PROJECTS.filter((project) => project.type === 'mobile') - .sort((a, b) => a.title.localeCompare(b.title)) - .map((project) => ( - - ))} -
-
-
-
- )} -
- )} - - )} -
-
- - )} -
- ) + return } -Home.getLayout = (page) => ( +HomePage.getLayout = (page) => ( {page} ) -export default Home +export default HomePage diff --git a/packages/ui-patterns/src/TimestampInfo/index.tsx b/packages/ui-patterns/src/TimestampInfo/index.tsx index f259abad456ee..7fa5d6e3b2f73 100644 --- a/packages/ui-patterns/src/TimestampInfo/index.tsx +++ b/packages/ui-patterns/src/TimestampInfo/index.tsx @@ -53,12 +53,14 @@ export const TimestampInfo = ({ displayAs = 'local', format = 'DD MMM HH:mm:ss', labelFormat = 'DD MMM HH:mm:ss', + label, }: { className?: string utcTimestamp: string | number displayAs?: 'local' | 'utc' format?: string labelFormat?: string + label?: string }) => { const local = timestampLocalFormatter({ utcTimestamp, format }) const utc = timestampUtcFormatter({ utcTimestamp, format }) @@ -139,9 +141,11 @@ export const TimestampInfo = ({ className={`text-xs ${className} border-b border-transparent hover:border-dashed hover:border-foreground-light`} > - {displayAs === 'local' - ? timestampLocalFormatter({ utcTimestamp, format: labelFormat }) - : timestampUtcFormatter({ utcTimestamp, format: labelFormat })} + {label + ? label + : displayAs === 'local' + ? timestampLocalFormatter({ utcTimestamp, format: labelFormat }) + : timestampUtcFormatter({ utcTimestamp, format: labelFormat })} diff --git a/packages/ui/src/components/SimpleCodeBlock/SimpleCodeBlock.tsx b/packages/ui/src/components/SimpleCodeBlock/SimpleCodeBlock.tsx index 6ec54244c1708..20e15661f36dd 100644 --- a/packages/ui/src/components/SimpleCodeBlock/SimpleCodeBlock.tsx +++ b/packages/ui/src/components/SimpleCodeBlock/SimpleCodeBlock.tsx @@ -9,9 +9,9 @@ import { useTheme } from 'next-themes' import { Highlight, Language, Prism, themes } from 'prism-react-renderer' import { PropsWithChildren, useEffect, useRef, useState } from 'react' -import { Button } from './../Button' -import { cn } from './../../lib/utils/cn' import { copyToClipboard } from '../../lib/utils' +import { cn } from './../../lib/utils/cn' +import { Button } from './../Button' import { dart } from './prism' dart(Prism) @@ -65,7 +65,7 @@ export const SimpleCodeBlock = ({
               {tokens.map((line, i) => {
-                const lineProps = getLineProps({ line, key: i })
+                const { key: _key, ...lineProps } = getLineProps({ line, key: i })
 
                 if (highlightLines.includes(i + 1)) {
                   lineProps.className = `${lineProps.className} docusaurus-highlight-code-line`
@@ -73,9 +73,10 @@ export const SimpleCodeBlock = ({
 
                 return (
                   
- {line.map((token, key) => ( - - ))} + {line.map((token, key) => { + const { key: _key, ...tokenProps } = getTokenProps({ token, key }) + return + })}
) })} From b53893a5803904da546f527ef3d61500cdf5e4bc Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:04:30 +1000 Subject: [PATCH 4/6] feat: state of startups 2025 results page (#37782) * wip: fist commit * feat: ascii art * wip: stepped animations * wip: bars bars bars * wip: styles * wip: styles * wip: styles * fix: absolute bar chart values * fix: type errors * chore: reorg components * chore: reorg public assets * wip: simplify filters * wip: simplify filters hook * wip: chart cleanup * fix: empty state on charts * fix: build issues * wip: simplify stat percentage card * wip: misc * fix: floating ToC scroll intersection * fix: brackets * feat: line animations * fix: wipe off animation * fix: smooth out animation * wip: refactor * wip: style improvements * fix: gradient smoothing * fix: smooth expansion and view transitions * wip: design polish * wip: polish * wip: polish * wip: polish * wip: copy * wip: quote avatars * wip: headshots * fix: pair component styles * fix: pair flex * wip: chart data improvement * wip: chart improvements * wip: quote avatars * fix: chart data * wip: remove graph comments * wip: misc * wip: poc chart * fix: demo sql * wip: prep for functions * feat: dedicated functions approach * fix: hardcoded stats * wip: cleanup * wip: cleanup * wip: cleanup * wip: feature flag basics * wip: thank you list * wip: flex with simpler shuffle * wip: misc styles * fix: env var for sentry * fix: duplicate var * fix: mobile style improvements * fix: pattern sharpness * fix: table of contents link * Fix feature flag * Tiny fix * minor copywriting improvements --------- Co-authored-by: Joshen Lim --- .../SurveyResults/DecorativeProgressBar.tsx | 70 ++ .../SurveyResults/SurveyChapter.tsx | 71 ++ .../SurveyResults/SurveyChapterSection.tsx | 108 +++ .../components/SurveyResults/SurveyChart.tsx | 566 ++++++++++++ .../SurveyResults/SurveyPullQuote.tsx | 48 ++ .../SurveyResults/SurveyRankedAnswersPair.tsx | 56 ++ .../SurveyResults/SurveySectionBreak.tsx | 19 + .../SurveyResults/SurveyStatCard.tsx | 117 +++ .../SurveyResults/SurveySummarizedAnswer.tsx | 104 +++ .../SurveyResults/SurveyWordCloud.tsx | 188 ++++ .../charts/AICodingToolsChart.tsx | 49 ++ .../SurveyResults/charts/AIModelsChart.tsx | 48 ++ .../charts/AcceleratorParticipationChart.tsx | 36 + .../charts/BiggestChallengeChart.tsx | 34 + .../SurveyResults/charts/DatabasesChart.tsx | 45 + .../charts/FundingStageChart.tsx | 33 + .../SurveyResults/charts/IndustryChart.tsx | 36 + .../charts/InitialPayingCustomersChart.tsx | 46 + .../SurveyResults/charts/LocationChart.tsx | 24 + .../SurveyResults/charts/NewIdeasChart.tsx | 48 ++ .../charts/RegularSocialMediaUseChart.tsx | 49 ++ .../SurveyResults/charts/RoleChart.tsx | 32 + .../SurveyResults/charts/SalesToolsChart.tsx | 45 + .../charts/WorldOutlookChart.tsx | 24 + .../SurveyResults/surveyResults.css | 34 + .../data/surveys/state-of-startups-2025.tsx | 695 ++++++++++++++- apps/www/pages/state-of-startups.tsx | 812 ++++++++---------- .../state-of-startups/pattern-checker.svg | 11 + .../state-of-startups/pattern-stipple.svg | 4 + .../quote-avatars/jinal-j-120x120.jpg | Bin 0 -> 3333 bytes .../quote-avatars/kevinton-b-120x120.jpg | Bin 0 -> 4039 bytes .../quote-avatars/richard-k-120x120.jpg | Bin 0 -> 2333 bytes .../quote-avatars/robert-w-120x120.jpg | Bin 0 -> 3885 bytes .../quote-avatars/waldemar-k-120x120.jpg | Bin 0 -> 3396 bytes .../{2025 => }/state-of-startups-og.png | Bin turbo.json | 2 + 36 files changed, 2965 insertions(+), 489 deletions(-) create mode 100644 apps/www/components/SurveyResults/DecorativeProgressBar.tsx create mode 100644 apps/www/components/SurveyResults/SurveyChapter.tsx create mode 100644 apps/www/components/SurveyResults/SurveyChapterSection.tsx create mode 100644 apps/www/components/SurveyResults/SurveyChart.tsx create mode 100644 apps/www/components/SurveyResults/SurveyPullQuote.tsx create mode 100644 apps/www/components/SurveyResults/SurveyRankedAnswersPair.tsx create mode 100644 apps/www/components/SurveyResults/SurveySectionBreak.tsx create mode 100644 apps/www/components/SurveyResults/SurveyStatCard.tsx create mode 100644 apps/www/components/SurveyResults/SurveySummarizedAnswer.tsx create mode 100644 apps/www/components/SurveyResults/SurveyWordCloud.tsx create mode 100644 apps/www/components/SurveyResults/charts/AICodingToolsChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/AIModelsChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/AcceleratorParticipationChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/BiggestChallengeChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/DatabasesChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/FundingStageChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/IndustryChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/InitialPayingCustomersChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/LocationChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/NewIdeasChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/RegularSocialMediaUseChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/RoleChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/SalesToolsChart.tsx create mode 100644 apps/www/components/SurveyResults/charts/WorldOutlookChart.tsx create mode 100644 apps/www/components/SurveyResults/surveyResults.css create mode 100644 apps/www/public/images/state-of-startups/pattern-checker.svg create mode 100644 apps/www/public/images/state-of-startups/pattern-stipple.svg create mode 100644 apps/www/public/images/state-of-startups/quote-avatars/jinal-j-120x120.jpg create mode 100644 apps/www/public/images/state-of-startups/quote-avatars/kevinton-b-120x120.jpg create mode 100644 apps/www/public/images/state-of-startups/quote-avatars/richard-k-120x120.jpg create mode 100644 apps/www/public/images/state-of-startups/quote-avatars/robert-w-120x120.jpg create mode 100644 apps/www/public/images/state-of-startups/quote-avatars/waldemar-k-120x120.jpg rename apps/www/public/images/state-of-startups/{2025 => }/state-of-startups-og.png (100%) diff --git a/apps/www/components/SurveyResults/DecorativeProgressBar.tsx b/apps/www/components/SurveyResults/DecorativeProgressBar.tsx new file mode 100644 index 0000000000000..ef691aaa401a8 --- /dev/null +++ b/apps/www/components/SurveyResults/DecorativeProgressBar.tsx @@ -0,0 +1,70 @@ +interface DecorativeProgressBarProps { + /** Whether to reverse the animation direction */ + reverse?: boolean + /** Whether to align to start or end */ + align?: 'start' | 'end' +} + +export function DecorativeProgressBar({ + reverse = false, + align = 'start', +}: DecorativeProgressBarProps) { + // Define the three progress bar configurations + const progressBars = [ + { + height: 'h-4', + bgColor: 'bg-foreground-muted/80', + fgColor: 'bg-brand', + animationDelay: '0s', + }, + { + height: 'h-8', + bgColor: 'bg-foreground-muted/50', + fgColor: 'bg-brand-500', + animationDelay: '0.3s', + }, + { + height: 'h-12', + bgColor: 'bg-foreground-muted/20', + fgColor: 'bg-brand-300', + animationDelay: '0.6s', + }, + ] + + return ( +