diff --git a/apps/design-system/package.json b/apps/design-system/package.json index e6acd2763ef0b..197bb8d13bc14 100644 --- a/apps/design-system/package.json +++ b/apps/design-system/package.json @@ -6,7 +6,7 @@ "scripts": { "preinstall": "npx only-allow pnpm", "dev": "next dev --turbopack --port 3003", - "build": "pnpm run content:build && pnpm run build:registry && next build", + "build": "pnpm run content:build && pnpm run build:registry && next build --turbopack", "build:registry": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache", "start": "next start", "lint": "next lint", diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel.tsx index 2d4aea160f4a1..36b0407e88d6b 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel.tsx @@ -6,7 +6,6 @@ import * as z from 'zod' import { useParams } from 'common' import { useCreateDestinationPipelineMutation } from 'data/replication/create-destination-pipeline-mutation' -import { useCreateTenantSourceMutation } from 'data/replication/create-tenant-source-mutation' import { useReplicationDestinationByIdQuery } from 'data/replication/destination-by-id-query' import { useReplicationPipelineByIdQuery } from 'data/replication/pipeline-by-id-query' import { useReplicationPublicationsQuery } from 'data/replication/publications-query' @@ -21,9 +20,6 @@ import { AccordionContent_Shadcn_, AccordionItem_Shadcn_, AccordionTrigger_Shadcn_, - Alert_Shadcn_, - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, Button, Form_Shadcn_, FormControl_Shadcn_, @@ -43,7 +39,6 @@ import { SheetSection, SheetTitle, TextArea_Shadcn_, - WarningIcon, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import NewPublicationPanel from './NewPublicationPanel' @@ -60,7 +55,6 @@ const FormSchema = z.object({ datasetId: z.string().min(1, 'Dataset id is required'), serviceAccountKey: z.string().min(1, 'Service account key is required'), publicationName: z.string().min(1, 'Publication is required'), - maxSize: z.number().min(1, 'Max Size must be greater than 0').int().optional(), maxFillMs: z.number().min(1, 'Max Fill milliseconds should be greater than 0').int().optional(), maxStalenessMins: z.number().nonnegative().optional(), }) @@ -90,9 +84,6 @@ export const DestinationPanel = ({ const editMode = !!existingDestination const [publicationPanelVisible, setPublicationPanelVisible] = useState(false) - const { mutateAsync: createTenantSource, isLoading: creatingTenantSource } = - useCreateTenantSourceMutation() - const { mutateAsync: createDestinationPipeline, isLoading: creatingDestinationPipeline } = useCreateDestinationPipelineMutation({ onSuccess: () => form.reset(defaultValues), @@ -129,7 +120,6 @@ export const DestinationPanel = ({ // For now, the password will always be set as empty for security reasons. serviceAccountKey: destinationData?.config?.big_query?.service_account_key ?? '', publicationName: pipelineData?.config.publication_name ?? '', - maxSize: pipelineData?.config?.batch?.max_size, maxFillMs: pipelineData?.config?.batch?.max_fill_ms, maxStalenessMins: destinationData?.config?.big_query?.max_staleness_mins, }), @@ -162,9 +152,8 @@ export const DestinationPanel = ({ } const batchConfig: any = {} - if (!!data.maxSize) batchConfig.maxSize = data.maxSize if (!!data.maxFillMs) batchConfig.maxFillMs = data.maxFillMs - const hasBothBatchFields = Object.keys(batchConfig).length === 2 + const hasBatchFields = Object.keys(batchConfig).length > 0 await updateDestinationPipeline({ destinationId: existingDestination.destinationId, @@ -174,7 +163,7 @@ export const DestinationPanel = ({ destinationConfig: { bigQuery: bigQueryConfig }, pipelineConfig: { publicationName: data.publicationName, - ...(hasBothBatchFields ? { batch: batchConfig } : {}), + ...(hasBatchFields ? { batch: batchConfig } : {}), }, sourceId, }) @@ -209,9 +198,8 @@ export const DestinationPanel = ({ } const batchConfig: any = {} - if (!!data.maxSize) batchConfig.maxSize = data.maxSize if (!!data.maxFillMs) batchConfig.maxFillMs = data.maxFillMs - const hasBothBatchFields = Object.keys(batchConfig).length === 2 + const hasBatchFields = Object.keys(batchConfig).length > 0 const { pipeline_id: pipelineId } = await createDestinationPipeline({ projectRef, @@ -220,7 +208,7 @@ export const DestinationPanel = ({ sourceId, pipelineConfig: { publicationName: data.publicationName, - ...(hasBothBatchFields ? { batch: batchConfig } : {}), + ...(hasBatchFields ? { batch: batchConfig } : {}), }, }) // Set request status only right before starting, then fire and close @@ -235,11 +223,6 @@ export const DestinationPanel = ({ } } - const onEnableReplication = async () => { - if (!projectRef) return console.error('Project ref is required') - await createTenantSource({ projectRef }) - } - useEffect(() => { if (editMode && destinationData && pipelineData) { form.reset(defaultValues) @@ -253,7 +236,7 @@ export const DestinationPanel = ({ } }, [visible, defaultValues, form]) - return sourceId ? ( + return ( <> @@ -399,31 +382,6 @@ export const DestinationPanel = ({ Advanced Settings - ( - - - { - const val = e.target.value - field.onChange(val === '' ? undefined : Number(val)) - }} - placeholder="Leave empty for default" - /> - - - )} - /> + setPublicationPanelVisible(false)} /> - ) : ( - - -
- - Create a new destination - - - - - - {/* Pricing to be decided yet */} - Enabling replication will cost additional $xx.xx - - - -
- -
-
-
-
- - - -
-
-
) } diff --git a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx index c1c133f3f0111..604b908c1d762 100644 --- a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx +++ b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx @@ -1,19 +1,21 @@ +import { useQueryClient } from '@tanstack/react-query' import { Plus, Search } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { useParams } from 'common' import Table from 'components/to-be-cleaned/Table' -import AlertError from 'components/ui/AlertError' +import { AlertError } from 'components/ui/AlertError' +import { DocsButton } from 'components/ui/DocsButton' import { useReplicationDestinationsQuery } from 'data/replication/destinations-query' +import { replicationKeys } from 'data/replication/keys' +import { fetchReplicationPipelineVersion } from 'data/replication/pipeline-version-query' import { useReplicationPipelinesQuery } from 'data/replication/pipelines-query' import { useReplicationSourcesQuery } from 'data/replication/sources-query' -import { fetchReplicationPipelineVersion } from 'data/replication/pipeline-version-query' -import { replicationKeys } from 'data/replication/keys' -import { useQueryClient } from '@tanstack/react-query' import { Button, cn, Input_Shadcn_ } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' import { DestinationPanel } from './DestinationPanel' import { DestinationRow } from './DestinationRow' +import { EnableReplicationModal } from './EnableReplicationModal' import { PIPELINE_ERROR_MESSAGES } from './Pipeline.utils' export const Destinations = () => { @@ -26,11 +28,13 @@ export const Destinations = () => { error: sourcesError, isLoading: isSourcesLoading, isError: isSourcesError, + isSuccess: isSourcesSuccess, } = useReplicationSourcesQuery({ projectRef, }) const sourceId = sourcesData?.sources.find((s) => s.name === projectRef)?.id + const replicationNotEnabled = isSourcesSuccess && !sourceId const { data: destinationsData, @@ -52,7 +56,7 @@ export const Destinations = () => { projectRef, }) - const anyDestinations = isDestinationsSuccess && destinationsData.destinations.length > 0 + const hasDestinations = isDestinationsSuccess && destinationsData.destinations.length > 0 const filteredDestinations = filterString.length === 0 @@ -103,9 +107,11 @@ export const Destinations = () => { /> - + {!!sourceId && ( + + )} @@ -119,7 +125,21 @@ export const Destinations = () => { /> )} - {anyDestinations ? ( + {replicationNotEnabled ? ( +
+
+

Run analysis on your data via integrations with Replication

+

+ Enable replication on your project to send data to your first destination +

+
+
+ + {/* [Joshen] Placeholder for when we have documentation */} + +
+
+ ) : hasDestinations ? ( Name, @@ -147,7 +167,7 @@ export const Destinations = () => { /> ) })} - >
+ /> ) : ( !isSourcesLoading && !isDestinationsLoading && @@ -180,7 +200,7 @@ export const Destinations = () => { {!isSourcesLoading && !isDestinationsLoading && filteredDestinations.length === 0 && - anyDestinations && ( + hasDestinations && (

No destinations match "{filterString}"

diff --git a/apps/studio/components/interfaces/Database/Replication/EnableReplicationModal.tsx b/apps/studio/components/interfaces/Database/Replication/EnableReplicationModal.tsx new file mode 100644 index 0000000000000..c84b826ab4ba6 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/EnableReplicationModal.tsx @@ -0,0 +1,78 @@ +import { useState } from 'react' +import { toast } from 'sonner' + +import { useParams } from 'common' +import { useCreateTenantSourceMutation } from 'data/replication/create-tenant-source-mutation' +import { + Button, + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogSection, + DialogSectionSeparator, + DialogTitle, + DialogTrigger, +} from 'ui' +import { Admonition } from 'ui-patterns' + +export const EnableReplicationModal = () => { + const { ref: projectRef } = useParams() + const [open, setOpen] = useState(false) + + const { mutateAsync: createTenantSource, isLoading: creatingTenantSource } = + useCreateTenantSourceMutation({ + onSuccess: () => { + toast.success('Replication has been successfully enabled!') + setOpen(false) + }, + onError: (error) => { + toast.error(`Failed to enable replication: ${error.message}`) + }, + }) + + const onEnableReplication = async () => { + if (!projectRef) return console.error('Project ref is required') + await createTenantSource({ projectRef }) + } + + return ( + + + + + + + Confirm to enable Replication + + + + +

+ This feature is in active development and may change as we gather feedback. + Availability and behavior can evolve while in Alpha. +

+

+ Pricing has not been finalized yet. You can enable replication now; we’ll announce + pricing later and notify you before any charges apply. +

+
+
+ + + + +
+
+ ) +} diff --git a/apps/studio/components/interfaces/Home/ProjectList/EmptyStates.tsx b/apps/studio/components/interfaces/Home/ProjectList/EmptyStates.tsx index 10d003463461e..d09575245629d 100644 --- a/apps/studio/components/interfaces/Home/ProjectList/EmptyStates.tsx +++ b/apps/studio/components/interfaces/Home/ProjectList/EmptyStates.tsx @@ -52,6 +52,26 @@ export const NoFilterResults = ({ ) } +export const LoadingTableRow = () => ( + + + + + + + + + + + + + + + + + +) + export const LoadingTableView = () => { return ( @@ -67,23 +87,7 @@ export const LoadingTableView = () => { {[...Array(3)].map((_, i) => ( - - - - - - - - - - - - - - - - - + ))} diff --git a/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 5ae3a2958f279..650cb768cbd7a 100644 --- a/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -1,19 +1,19 @@ import { Github } from 'lucide-react' +import InlineSVG from 'react-inlinesvg' import CardButton from 'components/ui/CardButton' import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper' import type { IntegrationProjectConnection } from 'data/integrations/integrations.types' import { ProjectIndexPageLink } from 'data/prefetchers/project.$ref' -import type { ProjectInfo } from 'data/projects/projects-query' +import { OrgProject } from 'data/projects/projects-infinite-query' import type { ResourceWarning } from 'data/usage/resource-warnings-query' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from 'lib/constants' -import InlineSVG from 'react-inlinesvg' import { inferProjectStatus } from './ProjectCard.utils' import { ProjectCardStatus } from './ProjectCardStatus' export interface ProjectCardProps { - project: ProjectInfo + project: OrgProject rewriteHref?: string githubIntegration?: IntegrationProjectConnection vercelIntegration?: IntegrationProjectConnection @@ -28,20 +28,20 @@ export const ProjectCard = ({ resourceWarnings, }: ProjectCardProps) => { const { name, ref: projectRef } = project - const desc = `${project.cloud_provider} | ${project.region}` + const infraInformation = project.databases.find((x) => x.identifier === project.ref) + const desc = `${infraInformation?.cloud_provider} | ${project.region}` const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ 'project_homepage:show_instance_size', ]) - const isBranchingEnabled = project.preview_branch_refs?.length > 0 const isGithubIntegrated = githubIntegration !== undefined const isVercelIntegrated = vercelIntegration !== undefined const githubRepository = githubIntegration?.metadata.name ?? undefined - const projectStatus = inferProjectStatus(project) + const projectStatus = inferProjectStatus(project.status) return ( -
  • +
  • { +export const inferProjectStatus = (projectStatus: string) => { let status = undefined - switch (project.status) { + switch (projectStatus) { case PROJECT_STATUS.ACTIVE_HEALTHY: status = 'isHealthy' break diff --git a/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx index 4ce7a0a194ced..bb204454298c4 100644 --- a/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx +++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx @@ -1,47 +1,85 @@ +import { UIEvent, useMemo } from 'react' + +import { useDebounce } from '@uidotdev/usehooks' +import { LOCAL_STORAGE_KEYS, useParams } from 'common' import AlertError from 'components/ui/AlertError' import NoSearchResults from 'components/ui/NoSearchResults' import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only' import { usePermissionsQuery } from 'data/permissions/permissions-query' -import { useProjectsQuery } from 'data/projects/projects-query' +import { useOrgProjectsInfiniteQuery } from 'data/projects/projects-infinite-query' import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' +import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' -import { makeRandomString } from 'lib/helpers' +import { isAtBottom } from 'lib/helpers' +import { parseAsArrayOf, parseAsString, useQueryState } from 'nuqs' import type { Organization } from 'types' -import { Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' -import { LoadingCardView, LoadingTableView, NoFilterResults, NoProjectsState } from './EmptyStates' +import { + Card, + cn, + LoadingLine, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from 'ui' +import { + LoadingCardView, + LoadingTableRow, + LoadingTableView, + NoFilterResults, + NoProjectsState, +} from './EmptyStates' import { ProjectCard } from './ProjectCard' import { ProjectTableRow } from './ProjectTableRow' +import { ShimmeringCard } from './ShimmeringCard' export interface ProjectListProps { organization?: Organization rewriteHref?: (projectRef: string) => string - search?: string - filterStatus?: string[] - resetFilterStatus?: () => void - viewMode?: 'grid' | 'table' } -export const ProjectList = ({ - search = '', - organization: organization_, - rewriteHref, - filterStatus, - resetFilterStatus, - viewMode = 'grid', -}: ProjectListProps) => { +export const ProjectList = ({ organization: organization_, rewriteHref }: ProjectListProps) => { + const { slug: urlSlug } = useParams() const { data: selectedOrganization } = useSelectedOrganizationQuery() + + const [search] = useQueryState('search', parseAsString.withDefault('')) + const debouncedSearch = useDebounce(search, 500) + + const [filterStatus, setFilterStatus] = useQueryState( + 'status', + parseAsArrayOf(parseAsString, ',').withDefault([]) + ) + const [viewMode] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.PROJECTS_VIEW, 'grid') + const organization = organization_ ?? selectedOrganization + const slug = organization?.slug ?? urlSlug const { data, + error: projectsError, isLoading: isLoadingProjects, isSuccess: isSuccessProjects, isError: isErrorProjects, - error: projectsError, - } = useProjectsQuery() - const allProjects = data?.projects ?? [] + isFetching, + isFetchingNextPage, + hasNextPage, + fetchNextPage, + } = useOrgProjectsInfiniteQuery( + { + slug, + search: search.length === 0 ? search : debouncedSearch, + statuses: filterStatus, + }, + { + keepPreviousData: true, + } + ) + const orgProjects = + useMemo(() => data?.pages.flatMap((page) => page.projects), [data?.pages]) || [] const { isLoading: _isLoadingPermissions, @@ -54,42 +92,18 @@ export const ProjectList = ({ const { data: integrations } = useOrgIntegrationsQuery({ orgSlug: organization?.slug }) const { data: connections } = useGitHubConnectionsQuery({ organizationId: organization?.id }) - const orgProjects = allProjects.filter((x) => x.organization_slug === organization?.slug) const isLoadingPermissions = IS_PLATFORM ? _isLoadingPermissions : false - const hasFilterStatusApplied = filterStatus !== undefined && filterStatus.length !== 2 + const isEmpty = + debouncedSearch.length === 0 && + filterStatus.length === 0 && + (!orgProjects || orgProjects.length === 0) + const sortedProjects = [...(orgProjects || [])].sort((a, b) => a.name.localeCompare(b.name)) + const noResultsFromSearch = - search.length > 0 && - isSuccessProjects && - orgProjects.filter((project) => { - return ( - project.name.toLowerCase().includes(search.toLowerCase()) || - project.ref.includes(search.toLowerCase()) - ) - }).length === 0 + debouncedSearch.length > 0 && isSuccessProjects && orgProjects.length === 0 const noResultsFromStatusFilter = - hasFilterStatusApplied && - isSuccessProjects && - orgProjects.filter((project) => filterStatus.includes(project.status)).length === 0 - - const isEmpty = !orgProjects || orgProjects.length === 0 - const sortedProjects = [...(orgProjects || [])].sort((a, b) => a.name.localeCompare(b.name)) - const filteredProjects = - search.length > 0 - ? sortedProjects.filter((project) => { - return ( - project.name.toLowerCase().includes(search.toLowerCase()) || - project.ref.includes(search.toLowerCase()) - ) - }) - : sortedProjects - - const filteredProjectsByStatus = - filterStatus !== undefined - ? filterStatus.length === 2 - ? filteredProjects - : filteredProjects.filter((project) => filterStatus.includes(project.status)) - : filteredProjects + filterStatus.length > 0 && isSuccessProjects && orgProjects.length === 0 const githubConnections = connections?.map((connection) => ({ id: String(connection.id), @@ -111,6 +125,11 @@ export const ProjectList = ({ ?.filter((integration) => integration.integration.name === 'Vercel') .flatMap((integration) => integration.connections) + const handleScroll = (event: UIEvent) => { + if (isLoadingProjects || isFetchingNextPage || !isAtBottom(event)) return + fetchNextPage() + } + if (isErrorPermissions) { return ( + - + {/* [Joshen] Ideally we can figure out sticky table headers here */} + Project Status @@ -149,6 +169,11 @@ export const ProjectList = ({ Region Created + + + + + {noResultsFromStatusFilter ? ( @@ -156,7 +181,7 @@ export const ProjectList = ({ setFilterStatus([])} className="border-0" /> @@ -168,22 +193,26 @@ export const ProjectList = ({ ) : ( - filteredProjectsByStatus?.map((project) => ( - resourceWarning.project === project.ref - )} - githubIntegration={githubConnections?.find( - (connection) => connection.supabase_project_ref === project.ref - )} - vercelIntegration={vercelConnections?.find( - (connection) => connection.supabase_project_ref === project.ref - )} - /> - )) + <> + {sortedProjects?.map((project) => ( + resourceWarning.project === project.ref + )} + githubIntegration={githubConnections?.find( + (connection) => connection.supabase_project_ref === project.ref + )} + vercelIntegration={vercelConnections?.find( + (connection) => connection.supabase_project_ref === project.ref + )} + /> + ))} + {hasNextPage && } + )}
    @@ -194,14 +223,24 @@ export const ProjectList = ({ return ( <> {noResultsFromStatusFilter ? ( - + setFilterStatus([])} + /> ) : noResultsFromSearch ? ( ) : ( -
      - {filteredProjectsByStatus?.map((project) => ( +
        + {sortedProjects?.map((project) => ( ))} + {hasNextPage && [...Array(2)].map((_, i) => )}
      )} diff --git a/apps/studio/components/interfaces/Home/ProjectList/ProjectTableRow.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectTableRow.tsx index 812fc067d81e0..30563af30bb53 100644 --- a/apps/studio/components/interfaces/Home/ProjectList/ProjectTableRow.tsx +++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectTableRow.tsx @@ -4,16 +4,18 @@ import InlineSVG from 'react-inlinesvg' import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper' import type { IntegrationProjectConnection } from 'data/integrations/integrations.types' -import type { ProjectInfo } from 'data/projects/projects-query' +import { OrgProject } from 'data/projects/projects-infinite-query' import type { ResourceWarning } from 'data/usage/resource-warnings-query' import { BASE_PATH } from 'lib/constants' +import { Organization } from 'types' import { TableCell, TableRow } from 'ui' import { TimestampInfo } from 'ui-patterns' import { inferProjectStatus } from './ProjectCard.utils' import { ProjectCardStatus } from './ProjectCardStatus' export interface ProjectTableRowProps { - project: ProjectInfo + project: OrgProject + organization?: Organization rewriteHref?: string githubIntegration?: IntegrationProjectConnection vercelIntegration?: IntegrationProjectConnection @@ -22,6 +24,7 @@ export interface ProjectTableRowProps { export const ProjectTableRow = ({ project, + organization, rewriteHref, githubIntegration, vercelIntegration, @@ -29,14 +32,15 @@ export const ProjectTableRow = ({ }: ProjectTableRowProps) => { const router = useRouter() const { name, ref: projectRef } = project - const projectStatus = inferProjectStatus(project) + const projectStatus = inferProjectStatus(project.status) const url = rewriteHref ?? `/project/${project.ref}` - const isBranchingEnabled = project.preview_branch_refs?.length > 0 const isGithubIntegrated = githubIntegration !== undefined const isVercelIntegrated = vercelIntegration !== undefined const githubRepository = githubIntegration?.metadata.name ?? undefined + const infraInformation = project.databases.find((x) => x.identifier === project.ref) + return ( {name}

      ID: {projectRef}

      - {(isGithubIntegrated || isVercelIntegrated || isBranchingEnabled) && ( + {(isGithubIntegrated || isVercelIntegrated) && (
      {isVercelIntegrated && (
      @@ -91,7 +95,14 @@ export const ProjectTableRow = ({
      {project.status !== 'INACTIVE' ? ( - + ) : ( - )} @@ -99,7 +110,7 @@ export const ProjectTableRow = ({ - {project.cloud_provider} | {project.region || 'N/A'} + {infraInformation?.cloud_provider} | {project.region || 'N/A'} diff --git a/apps/studio/components/interfaces/HomePageActions.tsx b/apps/studio/components/interfaces/HomePageActions.tsx index 7ca095608665a..64058004de517 100644 --- a/apps/studio/components/interfaces/HomePageActions.tsx +++ b/apps/studio/components/interfaces/HomePageActions.tsx @@ -1,9 +1,13 @@ -import { Filter, Grid, List, Plus, Search } from 'lucide-react' +import { Filter, Grid, List, Loader2, Plus, Search, X } from 'lucide-react' import Link from 'next/link' -import { useParams } from 'common' +import { useDebounce } from '@uidotdev/usehooks' +import { LOCAL_STORAGE_KEYS, useParams } from 'common' +import { useOrgProjectsInfiniteQuery } from 'data/projects/projects-infinite-query' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' +import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { PROJECT_STATUS } from 'lib/constants' +import { parseAsArrayOf, parseAsString, useQueryState } from 'nuqs' import { Button, Checkbox_Shadcn_, @@ -17,29 +21,37 @@ import { import { Input } from 'ui-patterns/DataInputs/Input' interface HomePageActionsProps { - search: string - filterStatus: string[] + slug?: string hideNewProject?: boolean - viewMode?: 'grid' | 'table' showViewToggle?: boolean - setSearch: (value: string) => void - setFilterStatus: (value: string[]) => void - setViewMode?: (value: 'grid' | 'table') => void } export const HomePageActions = ({ - search, - filterStatus, + slug: _slug, hideNewProject = false, - viewMode, showViewToggle = false, - setSearch, - setFilterStatus, - setViewMode, }: HomePageActionsProps) => { - const { slug } = useParams() + const { slug: urlSlug } = useParams() const projectCreationEnabled = useIsFeatureEnabled('projects:create') + const slug = _slug ?? urlSlug + const [search, setSearch] = useQueryState('search', parseAsString.withDefault('')) + const debouncedSearch = useDebounce(search, 500) + const [filterStatus, setFilterStatus] = useQueryState( + 'status', + parseAsArrayOf(parseAsString, ',').withDefault([]) + ) + const [viewMode, setViewMode] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.PROJECTS_VIEW, 'grid') + + const { isFetching: isFetchingProjects } = useOrgProjectsInfiniteQuery( + { + slug, + search: search.length === 0 ? search : debouncedSearch, + statuses: filterStatus, + }, + { keepPreviousData: true } + ) + return (
      @@ -47,15 +59,26 @@ export const HomePageActions = ({ placeholder="Search for a project" icon={} size="tiny" - className="w-64 pl-8 [&>div>div>div>input]:!pl-7 [&>div>div>div>div]:!pl-2" + className="w-32 md:w-64 pl-8 [&>div>div>div>input]:!pl-7 [&>div>div>div>div]:!pl-2" value={search} onChange={(event) => setSearch(event.target.value)} + actions={[ + search && ( +
      + + {isFetchingProjects && }
      diff --git a/apps/studio/components/layouts/DefaultLayout.tsx b/apps/studio/components/layouts/DefaultLayout.tsx index 9361bd4dce92f..99a094feedc11 100644 --- a/apps/studio/components/layouts/DefaultLayout.tsx +++ b/apps/studio/components/layouts/DefaultLayout.tsx @@ -1,13 +1,12 @@ import { useRouter } from 'next/router' import { PropsWithChildren } from 'react' -import { LOCAL_STORAGE_KEYS } from 'common' -import { useParams } from 'common' +import { LOCAL_STORAGE_KEYS, useParams } from 'common' import { AppBannerWrapper } from 'components/interfaces/App' import { AppBannerContextProvider } from 'components/interfaces/App/AppBannerWrapperContext' import { Sidebar } from 'components/interfaces/Sidebar' -import { useCheckLatestDeploy } from 'hooks/use-check-latest-deploy' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' +import { useCheckLatestDeploy } from 'hooks/use-check-latest-deploy' import { useAppStateSnapshot } from 'state/app-state' import { SidebarProvider } from 'ui' import { LayoutHeader } from './ProjectLayout/LayoutHeader' diff --git a/apps/studio/components/layouts/PageLayout/PageLayout.tsx b/apps/studio/components/layouts/PageLayout/PageLayout.tsx index e9144545a7ec4..033a2da2e4cc6 100644 --- a/apps/studio/components/layouts/PageLayout/PageLayout.tsx +++ b/apps/studio/components/layouts/PageLayout/PageLayout.tsx @@ -75,7 +75,7 @@ export const PageLayout = ({ const router = useRouter() return ( -
      +
      {/* Header section */} diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx index 269790306d708..ad60d7afc75b8 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx @@ -394,7 +394,7 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { void + onNewChat: () => void onCloseAssistant: () => void showMetadataWarning: boolean updatedOptInSinceMCP: boolean @@ -19,7 +19,7 @@ interface AIAssistantHeaderProps { export const AIAssistantHeader = ({ isChatLoading, - onClearMessages, + onNewChat, onCloseAssistant, showMetadataWarning, updatedOptInSinceMCP, @@ -54,22 +54,22 @@ export const AIAssistantHeader = ({ } - onClick={() => setIsOptInModalOpen(true)} + icon={} + onClick={onNewChat} className="h-7 w-7 p-0" disabled={isChatLoading} - tooltip={{ - content: { side: 'bottom', text: 'Permission settings' }, - }} + tooltip={{ content: { side: 'bottom', text: 'New chat' } }} /> } - onClick={onClearMessages} + icon={} + onClick={() => setIsOptInModalOpen(true)} className="h-7 w-7 p-0" disabled={isChatLoading} - tooltip={{ content: { side: 'bottom', text: 'Clear messages' } }} + tooltip={{ + content: { side: 'bottom', text: 'Permission settings' }, + }} /> ['all-projects'] as const, + infiniteListByOrg: ( + slug: string | undefined, + params: { + limit: number + sort?: 'name_asc' | 'name_desc' | 'created_asc' | 'created_desc' + search?: string + statuses?: string[] + } + ) => ['all-projects', slug, params] as const, status: (projectRef: string | undefined) => ['project', projectRef, 'status'] as const, types: (projectRef: string | undefined) => ['project', projectRef, 'types'] as const, detail: (projectRef: string | undefined) => ['project', projectRef, 'detail'] as const, diff --git a/apps/studio/data/projects/projects-infinite-query.ts b/apps/studio/data/projects/projects-infinite-query.ts new file mode 100644 index 0000000000000..356cf8df0da31 --- /dev/null +++ b/apps/studio/data/projects/projects-infinite-query.ts @@ -0,0 +1,87 @@ +import { useInfiniteQuery, UseInfiniteQueryOptions } from '@tanstack/react-query' + +import { components } from 'api-types' +import { get, handleError } from 'data/fetchers' +import { useProfile } from 'lib/profile' +import { ResponseError } from 'types' +import { projectKeys } from './keys' + +// [Joshen] Try to keep this value a multiple of 6 (common denominator of 2 and 3) to fit the cards view +// So that the last row will always be a full row of cards while there's a next page +// API max rows is 100, I'm just choosing 96 here as the highest value thats a multiple of 6 +const DEFAULT_LIMIT = 96 + +interface GetOrgProjectsInfiniteVariables { + slug?: string + limit?: number + sort?: 'name_asc' | 'name_desc' | 'created_asc' | 'created_desc' + search?: string + page?: number + statuses?: string[] +} + +export type OrgProject = components['schemas']['OrganizationProjectsResponse']['projects'][number] + +async function getOrganizationProjects( + { + slug, + limit = DEFAULT_LIMIT, + page = 0, + sort = 'name_asc', + search: _search = '', + statuses: _statuses = [], + }: GetOrgProjectsInfiniteVariables, + signal?: AbortSignal, + headers?: Record +) { + if (!slug) throw new Error('Slug is required') + + const offset = page * limit + const search = _search.length === 0 ? undefined : _search + const statuses = _statuses.length === 0 ? undefined : _statuses.join(',') + + const { data, error } = await get('/platform/organizations/{slug}/projects', { + params: { path: { slug }, query: { limit, offset, sort, search, statuses } }, + signal, + headers, + }) + + if (error) handleError(error) + return data +} + +export type OrgProjectsInfiniteData = Awaited> +export type OrgProjectsInfiniteError = ResponseError + +export const useOrgProjectsInfiniteQuery = ( + { + slug, + limit = DEFAULT_LIMIT, + sort = 'name_asc', + search, + statuses = [], + }: GetOrgProjectsInfiniteVariables, + { + enabled = true, + ...options + }: UseInfiniteQueryOptions = {} +) => { + const { profile } = useProfile() + return useInfiniteQuery( + projectKeys.infiniteListByOrg(slug, { limit, sort, search, statuses }), + ({ signal, pageParam }) => + getOrganizationProjects({ slug, limit, page: pageParam, sort, search, statuses }, signal), + { + enabled: enabled && profile !== undefined && typeof slug !== 'undefined', + getNextPageParam(lastPage, pages) { + const page = pages.length + const currentTotalCount = page * limit + const totalCount = lastPage.pagination.count + + if (currentTotalCount >= totalCount) return undefined + return page + }, + ...options, + } + ) +} diff --git a/apps/studio/data/replication/create-destination-pipeline-mutation.ts b/apps/studio/data/replication/create-destination-pipeline-mutation.ts index ce9e285b09258..2189dd8cd0691 100644 --- a/apps/studio/data/replication/create-destination-pipeline-mutation.ts +++ b/apps/studio/data/replication/create-destination-pipeline-mutation.ts @@ -22,7 +22,6 @@ export type CreateDestinationPipelineParams = { pipelineConfig: { publicationName: string batch?: { - maxSize: number maxFillMs: number } } @@ -60,7 +59,6 @@ async function createDestinationPipeline( ...(batch ? { batch: { - max_size: batch.maxSize, max_fill_ms: batch.maxFillMs, }, } diff --git a/apps/studio/data/replication/update-destination-pipeline-mutation.ts b/apps/studio/data/replication/update-destination-pipeline-mutation.ts index c96719e1df390..2bd6830a8e5a1 100644 --- a/apps/studio/data/replication/update-destination-pipeline-mutation.ts +++ b/apps/studio/data/replication/update-destination-pipeline-mutation.ts @@ -24,7 +24,6 @@ export type UpdateDestinationPipelineParams = { pipelineConfig: { publicationName: string batch?: { - maxSize: number maxFillMs: number } } @@ -64,7 +63,6 @@ async function updateDestinationPipeline( publication_name: publicationName, ...(batch && { batch: { - max_size: batch.maxSize, max_fill_ms: batch.maxFillMs, }, }), diff --git a/apps/studio/lib/ai/tool-filter.test.ts b/apps/studio/lib/ai/tool-filter.test.ts index 3dae04865f221..c419c119e47d1 100644 --- a/apps/studio/lib/ai/tool-filter.test.ts +++ b/apps/studio/lib/ai/tool-filter.test.ts @@ -35,6 +35,7 @@ describe('tool allowance by opt-in level', () => { list_policies: { execute: vitest.fn().mockResolvedValue({ status: 'success' }) }, // Log tools get_advisors: { execute: vitest.fn().mockResolvedValue({ status: 'success' }) }, + get_logs: { execute: vitest.fn().mockResolvedValue({ status: 'success' }) }, } as unknown as ToolSet const filtered = filterToolsByOptInLevel(mockTools, optInLevel as any) @@ -61,6 +62,7 @@ describe('tool allowance by opt-in level', () => { expect(tools).not.toContain('list_extensions') expect(tools).not.toContain('list_edge_functions') expect(tools).not.toContain('list_branches') + expect(tools).not.toContain('get_logs') expect(tools).not.toContain('execute_sql') }) @@ -76,6 +78,7 @@ describe('tool allowance by opt-in level', () => { expect(tools).toContain('list_policies') expect(tools).toContain('search_docs') expect(tools).not.toContain('get_advisors') + expect(tools).not.toContain('get_logs') expect(tools).not.toContain('execute_sql') }) @@ -91,6 +94,7 @@ describe('tool allowance by opt-in level', () => { expect(tools).toContain('list_policies') expect(tools).toContain('search_docs') expect(tools).toContain('get_advisors') + expect(tools).toContain('get_logs') expect(tools).not.toContain('execute_sql') }) @@ -106,6 +110,7 @@ describe('tool allowance by opt-in level', () => { expect(tools).toContain('list_policies') expect(tools).toContain('search_docs') expect(tools).toContain('get_advisors') + expect(tools).toContain('get_logs') expect(tools).not.toContain('execute_sql') }) }) @@ -125,6 +130,7 @@ describe('filterToolsByOptInLevel', () => { search_docs: { execute: vitest.fn().mockResolvedValue({ status: 'success' }) }, // Log tools get_advisors: { execute: vitest.fn().mockResolvedValue({ status: 'success' }) }, + get_logs: { execute: vitest.fn().mockResolvedValue({ status: 'success' }) }, // Unknown tool - should be filtered out entirely some_other_tool: { execute: vitest.fn().mockResolvedValue({ status: 'success' }) }, } as unknown as ToolSet @@ -180,6 +186,7 @@ describe('filterToolsByOptInLevel', () => { 'list_branches', 'list_policies', 'get_advisors', + 'get_logs', ]) }) @@ -193,13 +200,14 @@ describe('filterToolsByOptInLevel', () => { 'list_branches', 'list_policies', 'get_advisors', + 'get_logs', ]) }) it('should stub log tools for schema opt-in level', async () => { const tools = filterToolsByOptInLevel(mockTools, 'schema') - await expectStubsFor(tools, ['get_advisors']) + await expectStubsFor(tools, ['get_advisors', 'get_logs']) }) // No execute_sql tool, so nothing additional to stub for schema_and_log opt-in level diff --git a/apps/studio/lib/ai/tool-filter.ts b/apps/studio/lib/ai/tool-filter.ts index 2a32b8638b3f3..33d5f07c7fa0d 100644 --- a/apps/studio/lib/ai/tool-filter.ts +++ b/apps/studio/lib/ai/tool-filter.ts @@ -25,6 +25,7 @@ export const toolSetValidationSchema = z.record( 'list_branches', 'search_docs', 'get_advisors', + 'get_logs', // Local tools 'display_query', @@ -105,6 +106,7 @@ export const TOOL_CATEGORY_MAP: Record = { // Log tools - MCP and local get_advisors: TOOL_CATEGORIES.LOG, + get_logs: TOOL_CATEGORIES.LOG, } /** diff --git a/apps/studio/lib/ai/tools/fallback-tools.ts b/apps/studio/lib/ai/tools/fallback-tools.ts index 4b6783d1a6b9b..cb59c733c91ba 100644 --- a/apps/studio/lib/ai/tools/fallback-tools.ts +++ b/apps/studio/lib/ai/tools/fallback-tools.ts @@ -370,8 +370,10 @@ export const getFallbackTools = ({ ) : [] + const dataArray = Array.isArray(data) ? data : [] + // Filter functions by requested schemas - const filteredFunctions = data.filter((func) => schemas.includes(func.schema)) + const filteredFunctions = dataArray.filter((func) => schemas.includes(func.schema)) const formattedFunctions = filteredFunctions .map( diff --git a/apps/studio/lib/helpers.ts b/apps/studio/lib/helpers.ts index 2fab08b83d579..505b4712eeac2 100644 --- a/apps/studio/lib/helpers.ts +++ b/apps/studio/lib/helpers.ts @@ -3,7 +3,7 @@ export { default as uuidv4 } from './uuid' import { UIEvent } from 'react' import type { TablesData } from '../data/tables/tables-query' -export const isAtBottom = ({ currentTarget }: UIEvent): boolean => { +export const isAtBottom = ({ currentTarget }: UIEvent): boolean => { return currentTarget.scrollTop + 10 >= currentTarget.scrollHeight - currentTarget.clientHeight } diff --git a/apps/studio/pages/org/[slug]/index.tsx b/apps/studio/pages/org/[slug]/index.tsx index 4ea440053677c..77159c82864ab 100644 --- a/apps/studio/pages/org/[slug]/index.tsx +++ b/apps/studio/pages/org/[slug]/index.tsx @@ -1,5 +1,3 @@ -import { useState } from 'react' - import { useIsMFAEnabled } from 'common' import { ProjectList } from 'components/interfaces/Home/ProjectList/ProjectList' import { HomePageActions } from 'components/interfaces/HomePageActions' @@ -10,7 +8,6 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import { InlineLink } from 'components/ui/InlineLink' import { useAutoProjectsPrefetch } from 'data/projects/projects-query' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' -import { PROJECT_STATUS } from 'lib/constants' import type { NextPageWithLayout } from 'types' import { Admonition } from 'ui-patterns' @@ -19,18 +16,11 @@ const ProjectsPage: NextPageWithLayout = () => { const isUserMFAEnabled = useIsMFAEnabled() const disableAccessMfa = org?.organization_requires_mfa && !isUserMFAEnabled - const [search, setSearch] = useState('') - const [filterStatus, setFilterStatus] = useState([ - PROJECT_STATUS.ACTIVE_HEALTHY, - PROJECT_STATUS.INACTIVE, - ]) - const [viewMode, setViewMode] = useState<'grid' | 'table'>('grid') - useAutoProjectsPrefetch() return ( - - + + {disableAccessMfa ? (

      @@ -40,23 +30,11 @@ const ProjectsPage: NextPageWithLayout = () => {

      ) : ( -
      - - - setFilterStatus(['ACTIVE_HEALTHY', 'INACTIVE'])} - viewMode={viewMode} - /> + // [Joshen] Very odd, but the h-px here is required for ProjectList to have a max + // height based on the remaining space that it can grow to +
      + +
      )} diff --git a/apps/studio/pages/project/_/[[...routeSlug]].tsx b/apps/studio/pages/project/_/[[...routeSlug]].tsx index 9da13856dd818..b4c1e96940a32 100644 --- a/apps/studio/pages/project/_/[[...routeSlug]].tsx +++ b/apps/studio/pages/project/_/[[...routeSlug]].tsx @@ -1,18 +1,28 @@ -import { partition } from 'lodash' -import { AlertTriangleIcon, Boxes } from 'lucide-react' +import { AlertTriangleIcon } from 'lucide-react' import { NextPage } from 'next' import Link from 'next/link' import { useRouter } from 'next/router' -import { Fragment, useMemo, useState } from 'react' +import { useEffect, useState } from 'react' import { IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'common' import { ProjectList } from 'components/interfaces/Home/ProjectList/ProjectList' import { HomePageActions } from 'components/interfaces/HomePageActions' +import { PageLayout } from 'components/layouts/PageLayout/PageLayout' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { withAuth } from 'hooks/misc/withAuth' -import { BASE_PATH, PROJECT_STATUS } from 'lib/constants' -import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Badge } from 'ui' +import { BASE_PATH } from 'lib/constants' +import { + Alert_Shadcn_, + AlertDescription_Shadcn_, + AlertTitle_Shadcn_, + Select_Shadcn_, + SelectContent_Shadcn_, + SelectItem_Shadcn_, + SelectTrigger_Shadcn_, + SelectValue_Shadcn_, +} from 'ui' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' const Header = () => { @@ -32,6 +42,26 @@ const Header = () => { ) } +const OrganizationLoadingState = () => { + return ( + <> + + + + + ) +} + +const OrganizationErrorState = () => { + return ( + + + Failed to load your Supabase organizations + Try refreshing the page + + ) +} + // [Joshen] I'd say we don't do route validation here, this page will act more // like a proxy to the project specific pages, and we let those pages handle // any route validation logic instead @@ -40,24 +70,22 @@ const GenericProjectPage: NextPage = () => { const router = useRouter() const { routeSlug, ...queryParams } = router.query - const [search, setSearch] = useState('') - const [filterStatus, setFilterStatus] = useState([ - PROJECT_STATUS.ACTIVE_HEALTHY, - PROJECT_STATUS.INACTIVE, - ]) - const [lastVisitedOrgSlug] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION, '' ) + const [selectedSlug, setSlug] = useState(lastVisitedOrgSlug) + const { data: organizations = [], + isSuccess: isSuccessOrganizations, isLoading: isLoadingOrganizations, isError: isErrorOrganizations, } = useOrganizationsQuery({ enabled: IS_PLATFORM, }) + const selectedOrganization = organizations.find((x) => x.slug === selectedSlug) const query = Object.keys(queryParams).length ? `?${new URLSearchParams(queryParams as Record)}` @@ -76,89 +104,55 @@ const GenericProjectPage: NextPage = () => { } } - const [[lastVisitedOrganization], otherOrganizations] = useMemo( - () => partition(organizations, (org) => org.slug === lastVisitedOrgSlug), - [lastVisitedOrgSlug, organizations] - ) + useEffect(() => { + if (!!lastVisitedOrgSlug) { + setSlug(lastVisitedOrgSlug) + } else if (isSuccessOrganizations) { + setSlug(organizations[0].slug) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [lastVisitedOrgSlug, isSuccessOrganizations]) return ( - <> +
      -
      -

      Select a project to continue

      -
      -
      - + + + +
      + + +
      +

      Organization:

      + +
      +
      + + {organizations.map((org) => ( + + {org.name} + + ))} + +
      + +
      +
      + {isLoadingOrganizations ? ( ) : isErrorOrganizations ? ( - ) : ( - <> - {!!lastVisitedOrganization && ( - <> -

      - - {lastVisitedOrganization.name} - Recently visited -

      - - - )} - {otherOrganizations.map((organization) => ( - -

      - - {organization.name} -

      - -
      - ))} - - )} -
      -
      -
      - - ) -} - -function OrganizationLoadingState() { - return ( - <> - - - - - ) -} - -function OrganizationErrorState() { - return ( - - - Failed to load your Supabase organizations - Try refreshing the page - + ) : !!selectedOrganization ? ( + + ) : null} + + + +
      ) } diff --git a/apps/studio/styles/main.scss b/apps/studio/styles/main.scss index 90c0bdfc2f959..f7df5cab778ea 100644 --- a/apps/studio/styles/main.scss +++ b/apps/studio/styles/main.scss @@ -1,6 +1,4 @@ -@tailwind base; @tailwind components; -@tailwind utilities; @import './../../../packages/ui/build/css/source/global.css'; @import './../../../packages/ui/build/css/themes/dark.css'; @@ -46,21 +44,6 @@ --sidebar-ring: 217.2 91.2% 59.8%; } -@layer utilities { - .btn-primary { - @apply inline-block rounded border border-green-500 bg-green-500 py-1 px-3 text-sm; - color: #fff !important; - font-weight: 600; - line-height: 20px; - text-align: center; - } - - .btn-primary-hover { - @apply bg-green-600; - cursor: pointer; - } -} - html, body, #__next, @@ -161,14 +144,6 @@ input.is-invalid { @apply placeholder:text-red-600; } -input[type='submit'] { - @apply btn-primary; -} - -input[type='submit']:hover { - @apply btn-primary-hover; -} - input::placeholder { @apply text-foreground-lighter; } diff --git a/apps/studio/styles/typography.scss b/apps/studio/styles/typography.scss index b284618ace54e..55a5ca29acef7 100644 --- a/apps/studio/styles/typography.scss +++ b/apps/studio/styles/typography.scss @@ -1,5 +1,4 @@ @tailwind base; -@tailwind components; @tailwind utilities; @layer base { diff --git a/apps/ui-library/package.json b/apps/ui-library/package.json index 00a3f742a9793..acf53e60d3baa 100644 --- a/apps/ui-library/package.json +++ b/apps/ui-library/package.json @@ -6,7 +6,7 @@ "scripts": { "preinstall": "npx only-allow pnpm", "dev": "next dev --port 3004", - "build": "pnpm run content:build && pnpm run build:registry && pnpm run build:llms && next build", + "build": "pnpm run content:build && pnpm run build:registry && pnpm run build:llms && next build --turbopack", "build:registry": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --cache --write registry.json && rimraf -G public/r && shadcn build && tsx scripts/clean-registry.ts", "build:llms": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-llms-txt.ts", "start": "next start", diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index 7204239e4ad15..525b4f2561311 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -5142,12 +5142,7 @@ export interface components { * @description Maximum fill time in milliseconds * @example 200 */ - max_fill_ms: number - /** - * @description Maximum batch size - * @example 5000 - */ - max_size: number + max_fill_ms?: number } /** * @description Publication name @@ -5170,12 +5165,7 @@ export interface components { * @description Maximum fill time in milliseconds * @example 200 */ - max_fill_ms: number - /** - * @description Maximum batch size - * @example 5000 - */ - max_size: number + max_fill_ms?: number } /** * @description Publication name @@ -8015,12 +8005,7 @@ export interface components { * @description Maximum fill time in milliseconds * @example 200 */ - max_fill_ms: number - /** - * @description Maximum batch size - * @example 5000 - */ - max_size: number + max_fill_ms?: number } /** * @description Publication name @@ -8075,12 +8060,7 @@ export interface components { * @description Maximum fill time in milliseconds * @example 200 */ - max_fill_ms: number - /** - * @description Maximum batch size - * @example 5000 - */ - max_size: number + max_fill_ms?: number } /** * @description Publication name @@ -9429,12 +9409,7 @@ export interface components { * @description Maximum fill time in milliseconds * @example 200 */ - max_fill_ms: number - /** - * @description Maximum batch size - * @example 5000 - */ - max_size: number + max_fill_ms?: number } /** * @description Publication name @@ -9457,12 +9432,7 @@ export interface components { * @description Maximum fill time in milliseconds * @example 200 */ - max_fill_ms: number - /** - * @description Maximum batch size - * @example 5000 - */ - max_size: number + max_fill_ms?: number } /** * @description Publication name diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts index db05c8dd204b5..3572600e27315 100644 --- a/packages/common/constants/local-storage.ts +++ b/packages/common/constants/local-storage.ts @@ -6,6 +6,7 @@ export const LOCAL_STORAGE_KEYS = { `supabase-ai-assistant-state-${projectRef}`, SIDEBAR_BEHAVIOR: 'supabase-sidebar-behavior', EDITOR_PANEL_STATE: 'supabase-editor-panel-state', + PROJECTS_VIEW: 'projects-view', UI_PREVIEW_API_SIDE_PANEL: 'supabase-ui-api-side-panel', UI_PREVIEW_CLS: 'supabase-ui-cls', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d7c926d80b79..6e29c270a35ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,8 @@ settings: catalogs: default: '@supabase/auth-js': - specifier: 2.71.1-rc.1 - version: 2.71.1-rc.1 + specifier: 2.72.0-rc.11 + version: 2.72.0-rc.11 '@supabase/realtime-js': specifier: ^2.11.3 version: 2.11.3 @@ -41,7 +41,7 @@ catalogs: version: 6.3.5 overrides: - '@supabase/supabase-js>@supabase/auth-js': 2.71.1-rc.1 + '@supabase/supabase-js>@supabase/auth-js': 2.72.0-rc.11 '@tanstack/directive-functions-plugin>vite': ^6.2.7 '@tanstack/react-start-plugin>vite': ^6.2.7 esbuild: ^0.25.2 @@ -794,7 +794,7 @@ importers: version: 7.5.0 '@supabase/auth-js': specifier: 'catalog:' - version: 2.71.1-rc.1 + version: 2.72.0-rc.11 '@supabase/mcp-server-supabase': specifier: ^0.4.4 version: 0.4.4(supports-color@8.1.1) @@ -1857,7 +1857,7 @@ importers: dependencies: '@supabase/auth-js': specifier: 'catalog:' - version: 2.71.1-rc.1 + version: 2.72.0-rc.11 '@supabase/supabase-js': specifier: 'catalog:' version: 2.49.3 @@ -8041,8 +8041,8 @@ packages: resolution: {integrity: sha512-Cq3KKe+G1o7PSBMbmrgpT2JgBeyH2THHr3RdIX2MqF7AnBuspIMgtZ3ktcCgP7kZsTMvnmWymr7zZCT1zeWbMw==} engines: {node: '>=12.16'} - '@supabase/auth-js@2.71.1-rc.1': - resolution: {integrity: sha512-tgSFLO19e8Ig7eQON6c02DoyfV3OLEG709UqinbFExvRKB4z1yHBwwaMbkZtoKcu395AFVtekcHtoM9TEmixoQ==} + '@supabase/auth-js@2.72.0-rc.11': + resolution: {integrity: sha512-7C0xC6ZXbIB8GAoO6saWNMRhKox9OeYvMmpfPJyxFiECg9z78SMhMotiyI3U1Ws47iGiaDonEUnuxDuHAG04nQ==} '@supabase/functions-js@2.4.4': resolution: {integrity: sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==} @@ -26091,7 +26091,7 @@ snapshots: '@stripe/stripe-js@7.5.0': {} - '@supabase/auth-js@2.71.1-rc.1': + '@supabase/auth-js@2.72.0-rc.11': dependencies: '@supabase/node-fetch': 2.6.15 @@ -26248,7 +26248,7 @@ snapshots: '@supabase/supabase-js@2.49.3': dependencies: - '@supabase/auth-js': 2.71.1-rc.1 + '@supabase/auth-js': 2.72.0-rc.11 '@supabase/functions-js': 2.4.4 '@supabase/node-fetch': 2.6.15 '@supabase/postgrest-js': 1.19.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 086ef363e3135..cf0cf483d42e5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,7 +5,7 @@ packages: catalog: '@types/node': ^22.0.0 - '@supabase/auth-js': 2.71.1-rc.1 + '@supabase/auth-js': 2.72.0-rc.11 '@supabase/supabase-js': ^2.47.14 '@supabase/realtime-js': ^2.11.3 next: ^15.5.2