diff --git a/apps/docs/content/guides/telemetry/reports.mdx b/apps/docs/content/guides/telemetry/reports.mdx index e835f98508055..ee26f901827e7 100644 --- a/apps/docs/content/guides/telemetry/reports.mdx +++ b/apps/docs/content/guides/telemetry/reports.mdx @@ -282,13 +282,18 @@ The Storage report provides visibility into how your Supabase Storage is being u The Realtime report tracks WebSocket connections, channel activity, and real-time event patterns in your Supabase project. -| Chart | Description | Key Insights | -| --------------------- | ------------------------------------------------------------- | ------------------------------------------------- | -| Realtime Connections | Active WebSocket connections over time | Concurrent user activity and connection stability | -| Channel Events | Breakdown of broadcast, Postgres changes, and presence events | Real-time feature usage patterns | -| Rate of Channel Joins | Frequency of new channel subscriptions | User engagement with real-time features | -| Total Requests | HTTP requests to Realtime endpoints | API usage alongside WebSocket activity | -| Response Speed | Performance of Realtime API endpoints | Infrastructure optimization opportunities | +| Chart | Description | Key Insights | +| ---------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------- | +| Realtime Connections | Active WebSocket connections over time | Concurrent user activity and connection stability | +| Broadcast Events | Broadcast events over time | Real-time feature usage patterns | +| Presence Events | Presence events over time | Real-time feature usage patterns | +| Postgres Changes Events | Postgres Changes events over time | Real-time feature usage patterns | +| Rate of Channel Joins | Frequency of new channel subscriptions | User engagement with real-time features | +| Message Payload Size | Median size of message payloads sent | Payload size that is being transmitted | +| Broadcast From Database Replication Lag | Median latency between database commit and broadcast when using broadcast from database | Latency to Broadcast from the database | +| Read/Write Private Channel Subscription RLS Execution Time | Median time to authorize private channels | `realtime.messages` RLS policies performance | +| Total Requests | HTTP requests to Realtime endpoints | API usage alongside WebSocket activity | +| Response Speed | Performance of Realtime API endpoints | Infrastructure optimization opportunities | ## Edge Functions diff --git a/apps/studio/components/interfaces/Reports/ReportWidget.tsx b/apps/studio/components/interfaces/Reports/ReportWidget.tsx index 4d3c6857e7bc3..a7a771957d441 100644 --- a/apps/studio/components/interfaces/Reports/ReportWidget.tsx +++ b/apps/studio/components/interfaces/Reports/ReportWidget.tsx @@ -15,6 +15,8 @@ export interface ReportWidgetProps { error?: string | Object | null tooltip?: string | ReactNode className?: string + contentClassName?: string + headerClassName?: string renderer: (props: ReportWidgetRendererProps) => ReactNode append?: (props: ReportWidgetRendererProps) => ReactNode // for overriding props, such as data @@ -38,8 +40,8 @@ const ReportWidget = (props: ReportWidgetProps) => { return ( - -
+ +

{props.title}

{' '} diff --git a/apps/studio/components/interfaces/Reports/Reports.constants.ts b/apps/studio/components/interfaces/Reports/Reports.constants.ts index 7fa34e6f29ddc..3ca695dd32b0f 100644 --- a/apps/studio/components/interfaces/Reports/Reports.constants.ts +++ b/apps/studio/components/interfaces/Reports/Reports.constants.ts @@ -301,6 +301,26 @@ export const PRESET_CONFIG: Record = { timestamp ASC `, }, + requestsByCountry: { + queryType: 'logs', + sql: (filters) => ` + -- reports-api-requests-by-country + select + cf.country as country, + count(t.id) as count + from edge_logs t + cross join unnest(metadata) as m + cross join unnest(m.response) as response + cross join unnest(m.request) as request + cross join unnest(request.headers) as headers + cross join unnest(request.cf) as cf + where + cf.country is not null + ${generateRegexpWhere(filters, false)} + group by + cf.country + `, + }, }, }, [Presets.AUTH]: { diff --git a/apps/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx b/apps/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx index db220dbf91e40..876aa9c91673b 100644 --- a/apps/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx +++ b/apps/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx @@ -1,6 +1,7 @@ import sumBy from 'lodash/sumBy' import { ChevronRight } from 'lucide-react' -import { Fragment, useState } from 'react' +import { Fragment, useRef, useState } from 'react' +import * as z from 'zod' import { useParams } from 'common' import { @@ -25,6 +26,22 @@ import { } from 'ui' import { queryParamsToObject } from '../Reports.utils' import { ReportWidgetProps, ReportWidgetRendererProps } from '../ReportWidget' +import { ComposableMap, Geographies, Geography, Marker, ZoomableGroup } from 'react-simple-maps' +import { COUNTRY_LAT_LON } from 'components/interfaces/ProjectCreation/ProjectCreation.constants' +import { BASE_PATH } from 'lib/constants' +import { geoCentroid } from 'd3-geo' +import { + buildCountsByIso2, + getFillColor, + getFillOpacity, + isMicroCountry, + isKnownCountryCode, + computeMarkerRadius, + MAP_CHART_THEME, + extractIso2FromFeatureProps, + iso2ToCountryName, +} from 'components/interfaces/Reports/utils/geo' +import { useTheme } from 'next-themes' export const NetworkTrafficRenderer = ( props: ReportWidgetProps<{ @@ -385,3 +402,241 @@ const RouteTdContent = (datum: RouteTdContentProps) => ( ) +export const RequestsByCountryMapRenderer = ( + props: ReportWidgetProps<{ + country: string | null + count: number + }> +) => { + const WORLD_TOPO_URL = `${BASE_PATH}/json/worldmap.json` + const containerRef = useRef(null) + const [hoverInfo, setHoverInfo] = useState<{ + x: number + y: number + title: string + subtitle: string + visible: boolean + }>({ x: 0, y: 0, title: '', subtitle: '', visible: false }) + + const countsByIso2 = buildCountsByIso2(props.data) + const max = Object.values(countsByIso2).reduce((m, v) => (v > m ? v : m), 0) + const { resolvedTheme } = useTheme() + const theme = resolvedTheme === 'dark' ? MAP_CHART_THEME.dark : MAP_CHART_THEME.light + + if (!!props.error) { + const AlertErrorSchema = z.object({ message: z.string() }) + const parsed = + typeof props.error === 'string' + ? { success: true, data: { message: props.error } } + : AlertErrorSchema.safeParse(props.error) + const alertError = parsed.success ? parsed.data : null + return + } + + return ( +
+ + + + {({ geographies }) => ( + <> + {geographies.map((geo) => { + const title = + (geo.properties?.name as string) || + (geo.properties?.NAME as string) || + 'Unknown' + const iso2 = extractIso2FromFeatureProps( + (geo.properties || undefined) as Record | undefined + ) + const value = iso2 ? countsByIso2[iso2] || 0 : 0 + const baseOpacity = getFillOpacity(value, max, theme) + const tooltipTitle = title + const tooltipSubtitle = `${value.toLocaleString()} requests` + return ( + { + const rect = containerRef.current?.getBoundingClientRect() + const x = (rect ? e.clientX - rect.left : e.clientX) + 12 + const y = (rect ? e.clientY - rect.top : e.clientY) + 12 + setHoverInfo({ + x, + y, + title: tooltipTitle, + subtitle: tooltipSubtitle, + visible: true, + }) + }} + onMouseEnter={(e) => { + const rect = containerRef.current?.getBoundingClientRect() + const x = (rect ? e.clientX - rect.left : e.clientX) + 12 + const y = (rect ? e.clientY - rect.top : e.clientY) + 12 + setHoverInfo({ + x, + y, + title: tooltipTitle, + subtitle: tooltipSubtitle, + visible: true, + }) + }} + onMouseLeave={() => setHoverInfo((prev) => ({ ...prev, visible: false }))} + style={{ + default: { + fill: getFillColor(value, max, theme), + stroke: theme.boundaryStroke, + strokeWidth: 0.4, + opacity: baseOpacity, + outline: 'none', + cursor: 'default', + }, + hover: { + fill: getFillColor(value, max, theme), + stroke: 'transparent', + strokeWidth: 0, + opacity: Math.max(0, baseOpacity * 0.8), + outline: 'none', + cursor: 'default', + }, + pressed: { + fill: getFillColor(value, max, theme), + stroke: 'transparent', + strokeWidth: 0, + opacity: Math.max(0, baseOpacity * 0.8), + outline: 'none', + cursor: 'default', + }, + }} + aria-label={`${tooltipTitle} — ${tooltipSubtitle}`} + /> + ) + })} + + {geographies.map((geo) => { + const title = + (geo.properties?.name as string) || + (geo.properties?.NAME as string) || + 'Unknown' + if (!isMicroCountry(title)) return null + const iso2 = extractIso2FromFeatureProps( + (geo.properties || undefined) as Record | undefined + ) + const value = iso2 ? countsByIso2[iso2] || 0 : 0 + if (value <= 0) return null + const [lon, lat] = geoCentroid(geo) + const r = computeMarkerRadius(value, max) + const tooltipTitle = title + const tooltipSubtitle = `${value.toLocaleString()} requests` + return ( + { + const rect = containerRef.current?.getBoundingClientRect() + const x = (rect ? e.clientX - rect.left : e.clientX) + 12 + const y = (rect ? e.clientY - rect.top : e.clientY) + 12 + setHoverInfo({ + x, + y, + title: tooltipTitle, + subtitle: tooltipSubtitle, + visible: true, + }) + }} + onMouseEnter={(e) => { + const rect = containerRef.current?.getBoundingClientRect() + const x = (rect ? e.clientX - rect.left : e.clientX) + 12 + const y = (rect ? e.clientY - rect.top : e.clientY) + 12 + setHoverInfo({ + x, + y, + title: tooltipTitle, + subtitle: tooltipSubtitle, + visible: true, + }) + }} + onMouseLeave={() => setHoverInfo((prev) => ({ ...prev, visible: false }))} + > + + + ) + })} + + {(() => { + const present = new Set() + for (const g of geographies) { + const code = extractIso2FromFeatureProps( + (g.properties || undefined) as Record | undefined + ) + if (code) present.add(code) + } + + const markers: JSX.Element[] = [] + for (const iso2 in countsByIso2) { + const count = countsByIso2[iso2] + if (count <= 0) continue + if (present.has(iso2)) continue + if (!isKnownCountryCode(iso2)) continue + const ll = COUNTRY_LAT_LON[iso2] + const r = computeMarkerRadius(count, max) + const tooltipTitle = iso2ToCountryName(iso2) + const tooltipSubtitle = `${count.toLocaleString()} requests` + markers.push( + { + const rect = containerRef.current?.getBoundingClientRect() + const x = (rect ? e.clientX - rect.left : e.clientX) + 12 + const y = (rect ? e.clientY - rect.top : e.clientY) + 12 + setHoverInfo({ + x, + y, + title: tooltipTitle, + subtitle: tooltipSubtitle, + visible: true, + }) + }} + onMouseEnter={(e) => { + const rect = containerRef.current?.getBoundingClientRect() + const x = (rect ? e.clientX - rect.left : e.clientX) + 12 + const y = (rect ? e.clientY - rect.top : e.clientY) + 12 + setHoverInfo({ + x, + y, + title: tooltipTitle, + subtitle: tooltipSubtitle, + visible: true, + }) + }} + onMouseLeave={() => setHoverInfo((prev) => ({ ...prev, visible: false }))} + > + + + ) + } + + return markers + })()} + + )} + + + + {hoverInfo.visible && ( +
+

{hoverInfo.title}

+

{hoverInfo.subtitle}

+
+ )} +
+ ) +} diff --git a/apps/studio/components/interfaces/Reports/utils/geo.ts b/apps/studio/components/interfaces/Reports/utils/geo.ts new file mode 100644 index 0000000000000..253f4416a5bbf --- /dev/null +++ b/apps/studio/components/interfaces/Reports/utils/geo.ts @@ -0,0 +1,132 @@ +import { COUNTRIES } from 'components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingAddress.constants' +import { COUNTRY_LAT_LON } from 'components/interfaces/ProjectCreation/ProjectCreation.constants' + +export type CountryCountRow = { country: string | null; count: number | string } + +export interface MapChartTheme { + zeroFill: string // fill for countries with zero (or when max=0) + brandFill: string // base fill color (same for all, vary by opacity) + opacityScale: [number, number, number, number, number] // low -> high opacities + boundaryStroke: string + boundaryStrokeHover: string + markerFill: string + oceanFill: string +} + +export const MAP_CHART_THEME: { light: MapChartTheme; dark: MapChartTheme } = { + light: { + zeroFill: 'hsl(var(--background-surface-400))', + brandFill: 'hsl(var(--brand-default))', + opacityScale: [0.18, 0.32, 0.5, 0.68, 0.86], + boundaryStroke: 'hsla(var(--brand-300), 0.6)', + boundaryStrokeHover: 'hsl(var(--brand-500))', + markerFill: 'hsl(var(--brand-default))', + oceanFill: 'transparent', + }, + dark: { + zeroFill: 'hsl(var(--background-selection))', + brandFill: 'hsl(var(--brand-default))', + opacityScale: [0.18, 0.32, 0.5, 0.68, 0.86], + boundaryStroke: 'hsla(var(--brand-300), 0.6)', + boundaryStrokeHover: 'hsl(var(--brand-500))', + markerFill: 'hsl(var(--brand-default))', + oceanFill: 'transparent', + }, +} + +export const buildCountsByIso2 = (rows: CountryCountRow[]): Record => { + const counts: Record = {} + for (const row of rows) { + if (!row.country) continue + const code = row.country.toUpperCase() + const numeric = typeof row.count === 'number' ? row.count : Number(row.count) + if (!Number.isFinite(numeric)) continue + counts[code] = (counts[code] || 0) + numeric + } + return counts +} + +export const getFillColor = ( + value: number, + max: number, + theme: MapChartTheme = MAP_CHART_THEME.dark +): string => { + if (max <= 0 || !value) return theme.zeroFill + return theme.brandFill +} + +export const getFillOpacity = ( + value: number, + max: number, + theme: MapChartTheme = MAP_CHART_THEME.dark +): number => { + if (max <= 0 || !value) return 1 + const ratio = value / max + if (ratio > 0.8) return theme.opacityScale[4] + if (ratio > 0.6) return theme.opacityScale[3] + if (ratio > 0.4) return theme.opacityScale[2] + if (ratio > 0.2) return theme.opacityScale[1] + return theme.opacityScale[0] +} + +const MICRO_COUNTRIES = new Set([ + 'Singapore', + 'Monaco', + 'Andorra', + 'Liechtenstein', + 'San Marino', + 'Vatican', + 'Vatican City', + 'Luxembourg', + 'Malta', + 'Bahrain', + 'Brunei', + 'Qatar', + 'Kuwait', + 'Hong Kong', + 'Macau', +]) + +export const isMicroCountry = (name: string): boolean => MICRO_COUNTRIES.has(name) + +export const isKnownCountryCode = (code: string): code is keyof typeof COUNTRY_LAT_LON => { + return Object.prototype.hasOwnProperty.call(COUNTRY_LAT_LON, code) +} + +export const computeMarkerRadius = (value: number, max: number): number => { + if (max <= 0) return 2 + return Math.max(1.5, Math.min(4, (value / max) * 4)) +} + +// Best-effort extraction of ISO2 code from feature properties, with name fallback +export const extractIso2FromFeatureProps = ( + props?: Record +): string | undefined => { + if (!props) return undefined + const candidates = [ + 'ISO_A2_EH', + 'ISO_A2', + 'iso_a2', + 'ADMIN_ISO_A2', + 'WB_A2', + 'ADM0_A3_IS', + 'ADM0_A3', + 'ISO_N3', + 'id', + ] + for (const key of candidates) { + const v = props[key] as unknown + if (typeof v === 'string' && v.length === 2) return v.toUpperCase() + } + const name = + (props['name'] as string | undefined) || (props['NAME'] as string | undefined) || undefined + if (!name) return undefined + const entry = COUNTRIES.find((c) => c.name === name) + return entry?.code +} + +export const iso2ToCountryName = (iso2: string): string => { + const code = iso2.toUpperCase() + const entry = COUNTRIES.find((c) => c.code === code) + return entry?.name ?? code +} diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts b/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts index 0f48acb42ffed..e31e6ab29b103 100644 --- a/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts +++ b/apps/studio/components/interfaces/Settings/Logs/Logs.constants.ts @@ -54,7 +54,7 @@ where h.x_real_ip is not null for: ['api'], }, { - label: 'Requests by Country', + label: 'Requests by Geography', description: 'List all ISO 3166-1 alpha-2 country codes that used the Supabase API', mode: 'custom', searchString: `select diff --git a/apps/studio/components/interfaces/Storage/FilesBuckets/BucketsTable.LoadMoreRow.tsx b/apps/studio/components/interfaces/Storage/FilesBuckets/BucketsTable.LoadMoreRow.tsx new file mode 100644 index 0000000000000..1e4902713d969 --- /dev/null +++ b/apps/studio/components/interfaces/Storage/FilesBuckets/BucketsTable.LoadMoreRow.tsx @@ -0,0 +1,46 @@ +import { useIntersectionObserver } from '@uidotdev/usehooks' +import { VirtualizedTableCell, VirtualizedTableRow } from 'components/ui/VirtualizedTable' +import { useEffect, type ReactNode } from 'react' +import { TableCell, TableRow } from 'ui' +import { ShimmeringLoader } from 'ui-patterns' +import type { BucketsTablePaginationProps } from './BucketsTable.types' + +type LoadMoreRowProps = { + mode: 'standard' | 'virtualized' + colSpan: number +} & BucketsTablePaginationProps + +export const LoadMoreRow = ({ + mode, + colSpan, + + hasMore = false, + isLoadingMore = false, + onLoadMore, +}: LoadMoreRowProps): ReactNode => { + const [sentinelRef, entry] = useIntersectionObserver({ + threshold: 0, + rootMargin: '200px 0px 200px 0px', + }) + + useEffect(() => { + if (entry?.isIntersecting && hasMore && !isLoadingMore) { + onLoadMore?.() + } + }, [entry?.isIntersecting, hasMore, isLoadingMore, onLoadMore]) + + if (!hasMore && !isLoadingMore) return null + + const RowComponent = mode === 'standard' ? TableRow : VirtualizedTableRow + const CellComponent = mode === 'standard' ? TableCell : VirtualizedTableCell + + return ( + + {Array.from({ length: colSpan }, (_, idx) => ( + + {idx !== 0 && idx !== colSpan - 1 && } + + ))} + + ) +} diff --git a/apps/studio/components/interfaces/Storage/FilesBuckets/BucketsTable.tsx b/apps/studio/components/interfaces/Storage/FilesBuckets/BucketsTable.tsx index 5dd406a8c85fb..b6e09d9302808 100644 --- a/apps/studio/components/interfaces/Storage/FilesBuckets/BucketsTable.tsx +++ b/apps/studio/components/interfaces/Storage/FilesBuckets/BucketsTable.tsx @@ -1,13 +1,18 @@ +import { useRef } from 'react' + import { VirtualizedTable, VirtualizedTableBody } from 'components/ui/VirtualizedTable' import { Bucket } from 'data/storage/buckets-query' import { Table, TableBody } from 'ui' import { BucketTableEmptyState, BucketTableHeader, BucketTableRow } from './BucketTable' +import { LoadMoreRow } from './BucketsTable.LoadMoreRow' +import type { BucketsTablePaginationProps } from './BucketsTable.types' type BucketsTableProps = { buckets: Bucket[] projectRef: string filterString: string formattedGlobalUploadLimit: string + pagination: BucketsTablePaginationProps } export const BucketsTable = (props: BucketsTableProps) => { @@ -24,12 +29,16 @@ const BucketsTableUnvirtualized = ({ projectRef, filterString, formattedGlobalUploadLimit, + pagination: { hasMore = false, isLoadingMore = false, onLoadMore }, }: BucketsTableProps) => { const showSearchEmptyState = buckets.length === 0 && filterString.length > 0 return ( 0} /> @@ -46,6 +55,13 @@ const BucketsTableUnvirtualized = ({ /> )) )} +
) @@ -56,11 +72,18 @@ const BucketsTableVirtualized = ({ projectRef, filterString, formattedGlobalUploadLimit, + pagination: { hasMore = false, isLoadingMore = false, onLoadMore }, }: BucketsTableProps) => { const showSearchEmptyState = buckets.length === 0 && filterString.length > 0 + const scrollContainerRef = useRef(null) return ( - 59} getItemKey={(bucket) => bucket.id}> + 59} + getItemKey={(bucket) => bucket.id} + scrollContainerRef={scrollContainerRef} + > 0} /> paddingColSpan={5} @@ -69,6 +92,15 @@ const BucketsTableVirtualized = ({ ) : undefined } + trailingContent={ + + } > {(bucket) => ( void +} diff --git a/apps/studio/components/interfaces/Storage/FilesBuckets/index.tsx b/apps/studio/components/interfaces/Storage/FilesBuckets/index.tsx index e6b5ed6b8ffcb..4638c87b62b77 100644 --- a/apps/studio/components/interfaces/Storage/FilesBuckets/index.tsx +++ b/apps/studio/components/interfaces/Storage/FilesBuckets/index.tsx @@ -1,12 +1,13 @@ +import { useDebounce } from '@uidotdev/usehooks' import { ArrowDownNarrowWide, Search } from 'lucide-react' -import { useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useParams } from 'common' import AlertError from 'components/ui/AlertError' import { InlineLink } from 'components/ui/InlineLink' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query' -import { useBucketsQuery } from 'data/storage/buckets-query' +import { usePaginatedBucketsQuery } from 'data/storage/buckets-query' import { IS_PLATFORM } from 'lib/constants' import { formatBytes } from 'lib/helpers' import { parseAsBoolean, useQueryState } from 'nuqs' @@ -33,7 +34,13 @@ import { BucketsTable } from './BucketsTable' export const FilesBuckets = () => { const { ref } = useParams() const snap = useStorageExplorerStateSnapshot() + const [filterString, setFilterString] = useState('') + const debouncedFilterString = useDebounce(filterString, 250) + const normalizedSearch = debouncedFilterString.trim() + + const sortColumn = snap.sortBucket === STORAGE_BUCKET_SORT.ALPHABETICAL ? 'name' : 'created_at' + const sortOrder = snap.sortBucket === STORAGE_BUCKET_SORT.ALPHABETICAL ? 'asc' : 'desc' const [visible, setVisible] = useQueryState( 'new', @@ -42,36 +49,34 @@ export const FilesBuckets = () => { const { data } = useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM }) const { - data: buckets = [], + data: bucketsData, error: bucketsError, isError: isErrorBuckets, isLoading: isLoadingBuckets, isSuccess: isSuccessBuckets, - } = useBucketsQuery({ projectRef: ref }) + isFetching: isFetchingBuckets, + fetchNextPage, + hasNextPage, + } = usePaginatedBucketsQuery({ + projectRef: ref, + search: normalizedSearch.length > 0 ? normalizedSearch : undefined, + sortColumn, + sortOrder, + }) + const buckets = useMemo(() => bucketsData?.pages.flatMap((page) => page) ?? [], [bucketsData]) + const fileBuckets = buckets.filter((bucket) => !('type' in bucket) || bucket.type === 'STANDARD') + const hasNoBuckets = fileBuckets.length === 0 && normalizedSearch.length === 0 const formattedGlobalUploadLimit = formatBytes(data?.fileSizeLimit ?? 0) - const filesBuckets = buckets - .filter((bucket) => !('type' in bucket) || bucket.type === 'STANDARD') - .filter((bucket) => - filterString.length === 0 - ? true - : bucket.id.toLowerCase().includes(filterString.toLowerCase()) - ) - const hasNoBuckets = - buckets.filter((bucket) => !('type' in bucket) || bucket.type === 'STANDARD').length === 0 const hasNoApiKeys = isErrorBuckets && bucketsError.message.includes('Project has no active API keys') - const sortedFilesBuckets = useMemo( - () => - snap.sortBucket === 'alphabetical' - ? filesBuckets.sort((a, b) => - a.id.toLowerCase().trim().localeCompare(b.id.toLowerCase().trim()) - ) - : filesBuckets.sort((a, b) => (new Date(b.created_at) > new Date(a.created_at) ? 1 : -1)), - [filesBuckets, snap.sortBucket] - ) + const handleLoadMoreBuckets = useCallback(() => { + if (hasNextPage && !isFetchingBuckets) { + fetchNextPage() + } + }, [hasNextPage, isFetchingBuckets, fetchNextPage]) return ( <> @@ -138,10 +143,15 @@ export const FilesBuckets = () => { diff --git a/apps/studio/components/ui/VirtualizedTable.tsx b/apps/studio/components/ui/VirtualizedTable.tsx index 966c4faf124a3..25407b371148f 100644 --- a/apps/studio/components/ui/VirtualizedTable.tsx +++ b/apps/studio/components/ui/VirtualizedTable.tsx @@ -29,6 +29,7 @@ type TableComponentProps = React.ComponentProps interface VirtualizedTableProps extends TableComponentProps { scrollContainerProps?: HTMLAttributes + scrollContainerRef: React.Ref data: TItem[] children: ReactNode overscan?: number @@ -57,6 +58,7 @@ const useVirtualizedTableContext = () => { export const VirtualizedTable = ({ scrollContainerProps, + scrollContainerRef: externalScrollContainerRef, containerProps, data, children, @@ -66,6 +68,7 @@ export const VirtualizedTable = ({ ...tableProps }: VirtualizedTableProps) => { const scrollContainerRef = useRef(null) + const scrollContainerMergedRef = mergeRefs(scrollContainerRef, externalScrollContainerRef) const rowKeyGetter = useCallback( (item: TItem, index: number) => { @@ -121,7 +124,7 @@ export const VirtualizedTable = ({ return (
diff --git a/apps/studio/data/edge-functions/edge-function-body-query.ts b/apps/studio/data/edge-functions/edge-function-body-query.ts index c18cd93731576..715d89bf9cecd 100644 --- a/apps/studio/data/edge-functions/edge-function-body-query.ts +++ b/apps/studio/data/edge-functions/edge-function-body-query.ts @@ -33,7 +33,10 @@ export async function getEdgeFunctionBody( if (!data || !boundary) return { files: [] } - for await (let part of parseMultipartStream(data, { boundary })) { + for await (let part of parseMultipartStream(data, { + boundary, + maxFileSize: 20 * 1024 * 1024, + })) { if (part.isFile) { files.push({ name: part.filename, diff --git a/apps/studio/data/reports/api-report-query.ts b/apps/studio/data/reports/api-report-query.ts index b005a1aaea64b..71142452755b6 100644 --- a/apps/studio/data/reports/api-report-query.ts +++ b/apps/studio/data/reports/api-report-query.ts @@ -26,6 +26,7 @@ export const useApiReport = () => { const responseSpeed = queryHooks.responseSpeed() const topSlowRoutes = queryHooks.topSlowRoutes() const networkTraffic = queryHooks.networkTraffic() + const requestsByCountry = queryHooks.requestsByCountry() const activeHooks = [ totalRequests, topRoutes, @@ -34,6 +35,7 @@ export const useApiReport = () => { responseSpeed, topSlowRoutes, networkTraffic, + requestsByCountry, ] const addFilter = (filter: ReportFilterItem) => { @@ -94,6 +96,11 @@ export const useApiReport = () => { if (networkTraffic.changeQuery) { networkTraffic.changeQuery(PRESET_CONFIG.api.queries.networkTraffic.sql(formattedFilters)) } + if (requestsByCountry.changeQuery) { + requestsByCountry.changeQuery( + PRESET_CONFIG.api.queries.requestsByCountry.sql(formattedFilters) + ) + } }, [JSON.stringify(formattedFilters)]) const handleRefresh = async () => { @@ -117,6 +124,7 @@ export const useApiReport = () => { topErrorRoutes: topErrorRoutes.logData, topSlowRoutes: topSlowRoutes.logData, networkTraffic: networkTraffic.logData, + requestsByCountry: requestsByCountry.logData, }, params: { totalRequests: totalRequests.params, @@ -126,6 +134,7 @@ export const useApiReport = () => { topErrorRoutes: topErrorRoutes.params, topSlowRoutes: topSlowRoutes.params, networkTraffic: networkTraffic.params, + requestsByCountry: requestsByCountry.params, }, error: { totalRequest: totalRequests.error, @@ -135,6 +144,7 @@ export const useApiReport = () => { topErrorRoute: topErrorRoutes.error, topSlowRoutes: topSlowRoutes.error, networkTraffic: networkTraffic.error, + requestsByCountry: requestsByCountry.error, }, mergeParams: handleSetParams, filters, diff --git a/apps/studio/data/storage/buckets-query.ts b/apps/studio/data/storage/buckets-query.ts index df112a57be727..2aa128ac43457 100644 --- a/apps/studio/data/storage/buckets-query.ts +++ b/apps/studio/data/storage/buckets-query.ts @@ -1,11 +1,15 @@ -import { useQuery } from '@tanstack/react-query' +import { useInfiniteQuery, useQuery } from '@tanstack/react-query' import { components } from 'api-types' import { get, handleError } from 'data/fetchers' import { MAX_RETRY_FAILURE_COUNT } from 'data/query-client' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { PROJECT_STATUS } from 'lib/constants' -import { ResponseError, type UseCustomQueryOptions } from 'types' +import { + ResponseError, + type UseCustomInfiniteQueryOptions, + type UseCustomQueryOptions, +} from 'types' import { storageKeys } from './keys' export type BucketsVariables = { projectRef?: string } @@ -18,15 +22,70 @@ export async function getBuckets({ projectRef }: BucketsVariables, signal?: Abor if (!projectRef) throw new Error('projectRef is required') const { data, error } = await get('/platform/storage/{ref}/buckets', { - params: { path: { ref: projectRef } }, + params: { + path: { ref: projectRef }, + }, signal, }) if (error) handleError(error) - return data as Bucket[] + return data +} + +type BucketSortColumn = 'id' | 'name' | 'updated_at' | 'created_at' +type BucketSortOrder = 'asc' | 'desc' + +type BucketListPagination = { + page?: number + limit?: number + sortColumn?: BucketSortColumn + sortOrder?: BucketSortOrder + search?: string +} + +export type BucketsListPaginatedVariables = BucketsVariables & BucketListPagination + +const DEFAULT_PAGE_SIZE = 100 +const DEFAULT_SORT_COLUMN: BucketSortColumn = 'created_at' +const DEFAULT_SORT_ORDER: BucketSortOrder = 'desc' + +const getBucketsPaginated = async ( + { + projectRef, + page = 0, + limit = DEFAULT_PAGE_SIZE, + sortColumn = DEFAULT_SORT_COLUMN, + sortOrder = DEFAULT_SORT_ORDER, + search, + }: BucketsListPaginatedVariables, + signal?: AbortSignal +) => { + if (!projectRef) throw new Error('projectRef is required') + + const offset = page * limit + const trimmedSearch = search?.trim() + const resolvedSearch = !!trimmedSearch && trimmedSearch.length > 0 ? trimmedSearch : undefined + + const { data, error } = await get('/platform/storage/{ref}/buckets', { + params: { + path: { ref: projectRef }, + query: { + limit, + offset, + sortColumn, + sortOrder, + search: resolvedSearch, + }, + }, + signal, + }) + + if (error) handleError(error) + return data } export type BucketsData = Awaited> +export type BucketsWithPaginationData = Awaited> export type BucketsError = ResponseError export const useBucketsQuery = ( @@ -41,21 +100,55 @@ export const useBucketsQuery = ( queryFn: ({ signal }) => getBuckets({ projectRef }, signal), enabled: enabled && typeof projectRef !== 'undefined' && isActive, ...options, - retry: (failureCount, error) => { - if (error instanceof ResponseError) { - if ( - error.message.includes('Missing tenant config') || - error.message.includes('Project has no active API keys') - ) { - return false - } - } - - if (failureCount < MAX_RETRY_FAILURE_COUNT) { - return true - } + retry: shouldRetryBucketsQuery, + }) +} - return false +export const usePaginatedBucketsQuery = ( + { projectRef, ...params }: Omit, + { + enabled = true, + ...options + }: UseCustomInfiniteQueryOptions = {} +) => { + const { data: project } = useSelectedProjectQuery() + const isActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY + + const { keepPreviousData, ...restOptions } = options + const resolvedKeepPreviousData = keepPreviousData ?? true + + return useInfiniteQuery({ + queryKey: storageKeys.bucketsList(projectRef, params), + queryFn: ({ signal, pageParam }) => + getBucketsPaginated({ projectRef, page: pageParam, ...params }, signal), + enabled: enabled && typeof projectRef !== 'undefined' && isActive, + getNextPageParam: (lastPage, pages) => { + const nextPageNumber = pages.length + const limit = params.limit ?? DEFAULT_PAGE_SIZE + + const lastPageCount = lastPage.length + if (lastPageCount < limit) return undefined + return nextPageNumber }, + ...restOptions, + keepPreviousData: resolvedKeepPreviousData, + retry: shouldRetryBucketsQuery, }) } + +const shouldRetryBucketsQuery = (failureCount: number, error: unknown) => { + if (error instanceof ResponseError) { + if ( + error.message.includes('Missing tenant config') || + error.message.includes('Project has no active API keys') + ) { + return false + } + } + + if (failureCount < MAX_RETRY_FAILURE_COUNT) { + return true + } + + return false +} diff --git a/apps/studio/data/storage/keys.ts b/apps/studio/data/storage/keys.ts index fb9bf4a931533..e80babe426af2 100644 --- a/apps/studio/data/storage/keys.ts +++ b/apps/studio/data/storage/keys.ts @@ -1,5 +1,26 @@ export const storageKeys = { buckets: (projectRef: string | undefined) => ['projects', projectRef, 'buckets'] as const, + bucketsList: ( + projectRef: string | undefined, + params: { + limit?: number + search?: string + sortColumn?: string + sortOrder?: string + } = {} + ) => + [ + 'projects', + projectRef, + 'buckets', + 'list', + { + limit: params.limit, + search: params.search, + sortColumn: params.sortColumn, + sortOrder: params.sortOrder, + }, + ] as const, analyticsBuckets: (projectRef: string | undefined) => ['projects', projectRef, 'analytics-buckets'] as const, vectorBuckets: (projectRef: string | undefined) => diff --git a/apps/studio/package.json b/apps/studio/package.json index 2be360d7c4507..28d610b54870f 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -69,6 +69,7 @@ "@tanstack/react-query-devtools": "^4.42.0", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.13.12", + "@types/d3-geo": "^3.1.0", "@uidotdev/usehooks": "^2.4.1", "@vercel/functions": "^2.1.0", "@vitejs/plugin-react": "^4.3.4", @@ -82,6 +83,7 @@ "cron-parser": "^4.9.0", "cronstrue": "^2.50.0", "crypto-js": "^4.2.0", + "d3-geo": "^3.1.1", "dayjs": "^1.11.10", "dnd-core": "^16.0.1", "file-saver": "^2.0.5", @@ -100,7 +102,7 @@ "memoize-one": "^5.0.1", "mime-db": "^1.53.0", "monaco-editor": "0.52.2", - "next": "^16.0.3", + "next": "^16.0.7", "next-themes": "^0.3.0", "nuqs": "2.7.1", "openai": "^4.75.1", diff --git a/apps/studio/pages/api/platform/storage/[ref]/buckets/index.ts b/apps/studio/pages/api/platform/storage/[ref]/buckets/index.ts index 60a36762737a9..1240edb7d6318 100644 --- a/apps/studio/pages/api/platform/storage/[ref]/buckets/index.ts +++ b/apps/studio/pages/api/platform/storage/[ref]/buckets/index.ts @@ -22,7 +22,15 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } const handleGet = async (req: NextApiRequest, res: NextApiResponse) => { - const { data, error } = await supabase.storage.listBuckets() + const { limit, offset, search, sortColumn, sortOrder } = parseStoragePaginationParams(req) + + const { data, error } = await supabase.storage.listBuckets({ + ...(limit ? { limit } : {}), + ...(offset ? { offset } : {}), + ...(search ? { search } : {}), + ...(sortColumn ? { sortColumn } : {}), + ...(sortOrder ? { sortOrder } : {}), + }) if (error) { return res.status(500).json({ error: { message: 'Internal Server Error' } }) } @@ -49,3 +57,35 @@ const handlePost = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(200).json(data) } + +const parseStoragePaginationParams = (req: NextApiRequest) => { + const { + limit: queryLimit, + offset: queryOffset, + search: querySearch, + sortColumn: querySortColumn, + sortOrder: querySortOrder, + } = req.query + + const limit = parseAsInt(queryLimit) + const offset = parseAsInt(queryOffset) + const search = parseAsString(querySearch) + const sortColumn = parseAsStringEnum(querySortColumn, ['id', 'created_at', 'name']) + const sortOrder = parseAsStringEnum(querySortOrder, ['asc', 'desc']) + + return { limit, offset, search, sortColumn, sortOrder } +} + +const parseAsInt = (value: string | string[] | undefined): number | undefined => + value ? (Array.isArray(value) ? parseInt(value[0], 10) : parseInt(value, 10)) : undefined + +const parseAsString = (value: string | string[] | undefined): string | undefined => + value ? (Array.isArray(value) ? value[0] : value) : undefined + +const parseAsStringEnum = ( + value: string | string[] | undefined, + validValues: T[] +): T | undefined => { + const strValue = value ? (Array.isArray(value) ? value[0] : value) : undefined + return strValue && validValues.includes(strValue as T) ? (strValue as T) : undefined +} diff --git a/apps/studio/pages/project/[ref]/observability/api-overview.tsx b/apps/studio/pages/project/[ref]/observability/api-overview.tsx index 389fc02c00d15..4f2841ac7e6cd 100644 --- a/apps/studio/pages/project/[ref]/observability/api-overview.tsx +++ b/apps/studio/pages/project/[ref]/observability/api-overview.tsx @@ -9,6 +9,7 @@ import { NetworkTrafficRenderer, ResponseSpeedChartRenderer, TopApiRoutesRenderer, + RequestsByCountryMapRenderer, TotalRequestsChartRenderer, } from 'components/interfaces/Reports/renderers/ApiRenderers' import { DatePickerValue } from 'components/interfaces/Settings/Logs/Logs.DatePickers' @@ -119,6 +120,17 @@ export const ApiReport: NextPageWithLayout = () => {
} > + { - render() - await screen.findByText('Total Requests') - await screen.findByText('Response Errors') - await screen.findByText('Response Speed') - await screen.findByText('Network Traffic') - await screen.findByText(/Last 60 minutes/) - await screen.findByText(/Add filter/) - await screen.findByText(/All Requests/) -}) diff --git a/apps/studio/tests/features/reports/geo-utils.test.ts b/apps/studio/tests/features/reports/geo-utils.test.ts new file mode 100644 index 0000000000000..90f17df1a5ca6 --- /dev/null +++ b/apps/studio/tests/features/reports/geo-utils.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from 'vitest' + +import { + buildCountsByIso2, + getFillColor, + isMicroCountry, + isKnownCountryCode, + computeMarkerRadius, + MAP_CHART_THEME, +} from 'components/interfaces/Reports/utils/geo' + +describe('geo utils', () => { + test('buildCountsByIso2 aggregates and normalizes input', () => { + const rows = [ + { country: 'us', count: 1 }, + { country: 'US', count: '2' }, + { country: 'sg', count: 3 }, + { country: null, count: 9 }, + { country: 'us', count: 'not-a-number' }, + ] + const counts = buildCountsByIso2(rows as any) + expect(counts).toEqual({ US: 3, SG: 3 }) + }) + + test('getFillColor returns muted color when max=0 or value=0', () => { + const theme = MAP_CHART_THEME.dark + expect(getFillColor(0, 0, theme)).toBe(theme.zeroFill) + expect(getFillColor(0, 10, theme)).toBe(theme.zeroFill) + }) + + test('isMicroCountry identifies small states', () => { + expect(isMicroCountry('Monaco')).toBe(true) + expect(isMicroCountry('Singapore')).toBe(true) + expect(isMicroCountry('Germany')).toBe(false) + }) + + test('isKnownCountryCode guards country codes', () => { + expect(isKnownCountryCode('US')).toBe(true) + expect(isKnownCountryCode('XX')).toBe(false) + }) + + test('computeMarkerRadius scales between bounds', () => { + expect(computeMarkerRadius(0, 0)).toBe(2) + const rLow = computeMarkerRadius(1, 100) + const rHigh = computeMarkerRadius(90, 100) + expect(rLow).toBeGreaterThanOrEqual(1.5) + expect(rHigh).toBeLessThanOrEqual(4) + expect(rHigh).toBeGreaterThan(rLow) + }) +}) diff --git a/apps/ui-library/config/docs.ts b/apps/ui-library/config/docs.ts index 5e005ea99376c..df6701749d0de 100644 --- a/apps/ui-library/config/docs.ts +++ b/apps/ui-library/config/docs.ts @@ -68,7 +68,7 @@ export const componentPages: SidebarNavGroup = { }, { title: 'Social Auth', - supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'], + supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react', 'vue', 'nuxtjs'], href: '/docs/nextjs/social-auth', items: [], new: true, diff --git a/apps/ui-library/content/docs/nuxtjs/password-based-auth.mdx b/apps/ui-library/content/docs/nuxtjs/password-based-auth.mdx index 6323b654a8cca..a49337cf2b763 100644 --- a/apps/ui-library/content/docs/nuxtjs/password-based-auth.mdx +++ b/apps/ui-library/content/docs/nuxtjs/password-based-auth.mdx @@ -70,7 +70,7 @@ NUXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY= ### Setting up routes and redirect URLs 1. Set the site URL in the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings in the Supabase Dashboard. -1. Set up the Next.js route that users will visit to reset or update their password. Go to the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings and add the `forgot-password` route to the list of Redirect URLs. It should look something like: `http://example.com/auth/forgot-password`. +1. Set up the Nuxt.js route that users will visit to reset or update their password. Go to the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings and add the `forgot-password` route to the list of Redirect URLs. It should look something like: `http://example.com/auth/forgot-password`. 1. Update the redirect paths in `login-form.vue` and `update-password-form.vue` components to point to the logged-in routes in your app. Our examples use `/protected`, but you can set this to whatever fits your app. diff --git a/apps/ui-library/content/docs/nuxtjs/social-auth.mdx b/apps/ui-library/content/docs/nuxtjs/social-auth.mdx new file mode 100644 index 0000000000000..c1f12d6d57ec5 --- /dev/null +++ b/apps/ui-library/content/docs/nuxtjs/social-auth.mdx @@ -0,0 +1,61 @@ +--- +title: Social Authentication +description: Social authentication block for Nuxt.js +--- + + + + + The block is using Github provider by default, but can be easily switched by changing a single + parameter. + + +## Installation + + + +## Folder structure + +This block assumes that you have already installed a Supabase client for Nuxt from the previous step. + + + +## Usage + +Once you install the block in your Nuxt.js project, you'll get all the necessary pages and components to set up a social authentication flow. + +### Getting started + +After installing the block, you'll have the following environment variables in your `.env.local` file: + +```env +NUXT_PUBLIC_SUPABASE_URL= +NUXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY= +``` + +- If you're using supabase.com, you can find these values in the [Connect modal](https://supabase.com/dashboard/project/_?showConnect=true&connectTab=frameworks&framework=nuxtjs&using=supabasejs) under App Frameworks or in your project's [API settings](https://supabase.com/dashboard/project/_/settings/api). + +- If you're using a local instance of Supabase, you can find these values by running `supabase start` or `supabase status` (if you already have it running). + +### Setting up third party providers + +We support a wide variety of social providers that you can use to integrate with your application. The full list is available [here](https://supabase.com/docs/guides/auth/social-login). +This block uses the PKCE flow with GitHub as the provider. To switch providers, just update the `provider` field in the `supabase.auth.signInWithOAuth` call. Enable the provider you want to use under [Auth Providers](https://supabase.com/dashboard/project/_/auth/providers) in the Supabase Dashboard and add the necessary credentials. + +### Setting up routes and redirect URLs + +1. Set the site URL in the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings in the Supabase Dashboard. +1. Update the redirect paths in `login-form.vue` to point to your app’s logged-in routes. Our examples use `/protected`, but you can set this to whatever fits your app. +1. Visit `http://your-site-url/auth/login` to see this component in action. + +### Combining social auth with password-based auth + +If you want to combine this block with the password-based auth, you need to: + +- Copy the `handleSocialLogin` function into the password-based `login-form.vue` component and bind it to a "Login with ..." button. +- Copy the `@/server/routes/auth/oauth.ts` in your app under the same route. + +## Further reading + +- [Social login](https://supabase.com/docs/guides/auth/social-login) +- [Authentication error codes](https://supabase.com/docs/guides/auth/debugging/error-codes) diff --git a/apps/ui-library/content/docs/vue/social-auth.mdx b/apps/ui-library/content/docs/vue/social-auth.mdx new file mode 100644 index 0000000000000..729dc532b5a82 --- /dev/null +++ b/apps/ui-library/content/docs/vue/social-auth.mdx @@ -0,0 +1,57 @@ +--- +title: Social Authentication +description: Social authentication block for Vue Single Page Applications +--- + + + + + The block is using GitHub provider by default, but can be easily switched by changing a single + parameter. + + +## Installation + + + +## Folder structure + +This block assumes that you have already installed a Supabase client for Vue from the previous step. + + + +## Usage + +Once you install the block in your Vue project, you'll get all the necessary pages and components to set up a social authentication flow. + +### Getting started + +After installing the block, you'll have the following environment variables in your `.env.local` file: + +```env +VITE_SUPABASE_URL= +VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY= +``` + +- If you're using supabase.com, you can find these values in the [Connect modal](https://supabase.com/dashboard/project/_?showConnect=true&connectTab=frameworks&framework=react&using=vite&with=supabasejs) under App Frameworks or in your project's [API settings](https://supabase.com/dashboard/project/_/settings/api). + +- If you're using a local instance of Supabase, you can find these values by running `supabase start` or `supabase status` (if you already have it running). + +### Setting up third party providers + +We support a wide variety of social providers that you can use to integrate with your application. The full list is available [here](https://supabase.com/docs/guides/auth/social-login). +This block uses the implicit flow with GitHub as the provider. To switch providers, just update the `provider` field in the `supabase.auth.signInWithOAuth` call. Enable the provider you want to use under [Auth Providers](https://supabase.com/dashboard/project/_/auth/providers) in the Supabase Dashboard and add the necessary credentials. + +### Setting up routes and redirect URLs + +1. Set the site URL in the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings in the Supabase Dashboard. +1. Update the redirect paths in `login-form.vue` to point to your app’s logged-in routes. Our examples use `/protected`, but you can set this to whatever fits your app. + +### Combining social auth with password-based auth + +If you want to combine this block with the password-based auth, you need to copy the `handleSocialLogin` function into the password-based `login-form.vue` component and bind it to a button. + +## Further reading + +- [Social login](https://supabase.com/docs/guides/auth/social-login) +- [Authentication error codes](https://supabase.com/docs/guides/auth/debugging/error-codes) diff --git a/apps/ui-library/public/llms.txt b/apps/ui-library/public/llms.txt index aa18fe91ff88b..179a12011d2be 100644 --- a/apps/ui-library/public/llms.txt +++ b/apps/ui-library/public/llms.txt @@ -1,5 +1,5 @@ # Supabase UI Library -Last updated: 2025-11-06T16:46:13.786Z +Last updated: 2025-11-20T11:14:14.146Z ## Overview Library of components for your project. The components integrate with Supabase and are shadcn compatible. @@ -35,6 +35,8 @@ Library of components for your project. The components integrate with Supabase a - Supabase client for Nuxt.js - [Password-based Authentication](https://supabase.com/ui/docs/nuxtjs/password-based-auth) - Password-based authentication block for Nuxt.js +- [Social Authentication](https://supabase.com/ui/docs/nuxtjs/social-auth) + - Social authentication block for Nuxt.js - [Platform Kit](https://supabase.com/ui/docs/platform/platform-kit) - The easiest way to build platforms on top of Supabase - [Supabase Client Libraries](https://supabase.com/ui/docs/react-router/client) @@ -89,3 +91,5 @@ Library of components for your project. The components integrate with Supabase a - Supabase client for Vue Single Page Applications - [Password-based Authentication](https://supabase.com/ui/docs/vue/password-based-auth) - Password-based authentication block for Vue Single Page Applications +- [Social Authentication](https://supabase.com/ui/docs/vue/social-auth) + - Social authentication block for Vue Single Page Applications diff --git a/apps/ui-library/public/r/password-based-auth-vue.json b/apps/ui-library/public/r/password-based-auth-vue.json index 4b5b1d4b10eb6..a3edccc7173dc 100644 --- a/apps/ui-library/public/r/password-based-auth-vue.json +++ b/apps/ui-library/public/r/password-based-auth-vue.json @@ -17,22 +17,26 @@ { "path": "registry/default/password-based-auth/vue/components/login-form.vue", "content": "\n\n\n", - "type": "registry:component" + "type": "registry:component", + "target": "components/login-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/sign-up-form.vue", "content": "\n\n\n", - "type": "registry:component" + "type": "registry:component", + "target": "components/sign-up-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/forgot-password-form.vue", "content": "\n\n\n", - "type": "registry:component" + "type": "registry:component", + "target": "components/forgot-password-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/update-password-form.vue", "content": "\n\n\n", - "type": "registry:component" + "type": "registry:component", + "target": "components/update-password-form.vue" } ] } \ No newline at end of file diff --git a/apps/ui-library/public/r/registry.json b/apps/ui-library/public/r/registry.json index ddef884a812e9..595c4484a22b7 100644 --- a/apps/ui-library/public/r/registry.json +++ b/apps/ui-library/public/r/registry.json @@ -1853,25 +1853,102 @@ "files": [ { "path": "registry/default/password-based-auth/vue/components/login-form.vue", - "type": "registry:component" + "type": "registry:component", + "target": "components/login-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/sign-up-form.vue", - "type": "registry:component" + "type": "registry:component", + "target": "components/sign-up-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/forgot-password-form.vue", - "type": "registry:component" + "type": "registry:component", + "target": "components/forgot-password-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/update-password-form.vue", - "type": "registry:component" + "type": "registry:component", + "target": "components/update-password-form.vue" + } + ], + "dependencies": [ + "@supabase/supabase-js@latest" + ] + }, + { + "name": "social-auth-vue", + "type": "registry:block", + "title": "Social Auth flow for Vue and Supabase", + "description": "Social Auth flow for Vue and Supabase", + "registryDependencies": [ + "button", + "card" + ], + "files": [ + { + "path": "registry/default/social-auth/vue/components/login-form.vue", + "type": "registry:component", + "target": "components/login-form.vue" } ], "dependencies": [ "@supabase/supabase-js@latest" ] }, + { + "name": "social-auth-nuxtjs", + "type": "registry:block", + "title": "Social Auth flow for Nuxt and Supabase", + "description": "Social Auth flow for Nuxt and Supabase", + "registryDependencies": [ + "button", + "card", + "input", + "label" + ], + "files": [ + { + "path": "registry/default/social-auth/nuxtjs/app/components/login-form.vue", + "type": "registry:file", + "target": "app/components/login-form.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/components/logout-button.vue", + "type": "registry:file", + "target": "app/components/logout-button.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/pages/auth/login.vue", + "type": "registry:file", + "target": "app/pages/auth/login.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/pages/auth/error.vue", + "type": "registry:file", + "target": "app/pages/auth/error.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/pages/protected/index.vue", + "type": "registry:file", + "target": "app/pages/protected/index.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/server/middleware/auth.ts", + "type": "registry:file", + "target": "server/middleware/auth.ts" + }, + { + "path": "registry/default/social-auth/nuxtjs/server/routes/auth/oauth.ts", + "type": "registry:file", + "target": "server/routes/auth/oauth.ts" + } + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ] + }, { "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "ai-editor-rules", diff --git a/apps/ui-library/public/r/social-auth-nuxtjs.json b/apps/ui-library/public/r/social-auth-nuxtjs.json new file mode 100644 index 0000000000000..64639fe3b99e7 --- /dev/null +++ b/apps/ui-library/public/r/social-auth-nuxtjs.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "social-auth-nuxtjs", + "type": "registry:block", + "title": "Social Auth flow for Nuxt and Supabase", + "description": "Social Auth flow for Nuxt and Supabase", + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "registryDependencies": [ + "button", + "card", + "input", + "label" + ], + "files": [ + { + "path": "registry/default/social-auth/nuxtjs/app/components/login-form.vue", + "content": "\n\n\n", + "type": "registry:file", + "target": "app/components/login-form.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/components/logout-button.vue", + "content": "\n\n\n", + "type": "registry:file", + "target": "app/components/logout-button.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/pages/auth/login.vue", + "content": "\n\n", + "type": "registry:file", + "target": "app/pages/auth/login.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/pages/auth/error.vue", + "content": "\n\n", + "type": "registry:file", + "target": "app/pages/auth/error.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/pages/protected/index.vue", + "content": "\n\n\n", + "type": "registry:file", + "target": "app/pages/protected/index.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/server/middleware/auth.ts", + "content": "import { defineEventHandler, sendRedirect } from 'h3'\nimport { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client'\n\nexport default defineEventHandler(async (event) => {\n const supabase = createSupabaseServerClient(event)\n\n // Get user claims\n const { data } = await supabase.auth.getClaims()\n const user = data?.claims\n\n const pathname = event.node.req.url || '/'\n\n // Redirect if no user and not already on login/auth route\n if (\n !user &&\n !pathname.startsWith('/login') &&\n !pathname.startsWith('/auth')\n ) {\n return sendRedirect(event, '/auth/login')\n }\n\n // Return event as-is (you could return any object if needed)\n return { user }\n})\n", + "type": "registry:file", + "target": "server/middleware/auth.ts" + }, + { + "path": "registry/default/social-auth/nuxtjs/server/routes/auth/oauth.ts", + "content": "import { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client'\nimport { defineEventHandler, getQuery, sendRedirect, getRequestURL } from \"h3\"\n\nexport default defineEventHandler(async (event) => {\n const url = getRequestURL(event) // URL object of the current request\n const query = getQuery(event)\n\n const code = query.code as string | undefined\n let next = (query.next as string | undefined) ?? \"/\"\n\n if (!next.startsWith(\"/\")) {\n next = \"/\"\n }\n\n if (code) {\n const supabase = createSupabaseServerClient(event)\n const { error } = await supabase.auth.exchangeCodeForSession(code)\n\n if (!error) {\n // Determine origin\n const forwardedHost = event.node.req.headers[\"x-forwarded-host\"] as string | undefined\n const isLocalEnv = process.env.NODE_ENV === \"development\"\n const origin = `${url.protocol}//${url.host}`\n\n if (isLocalEnv) {\n return sendRedirect(event, `${origin}${next}`)\n } else if (forwardedHost) {\n return sendRedirect(event, `https://${forwardedHost}${next}`)\n } else {\n return sendRedirect(event, `${origin}${next}`)\n }\n }\n }\n\n // fallback to error page\n const origin = `${url.protocol}//${url.host}`\n return sendRedirect(event, `${origin}/auth/error`)\n})\n", + "type": "registry:file", + "target": "server/routes/auth/oauth.ts" + } + ] +} \ No newline at end of file diff --git a/apps/ui-library/public/r/social-auth-vue.json b/apps/ui-library/public/r/social-auth-vue.json new file mode 100644 index 0000000000000..870daed4a91ff --- /dev/null +++ b/apps/ui-library/public/r/social-auth-vue.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "social-auth-vue", + "type": "registry:block", + "title": "Social Auth flow for Vue and Supabase", + "description": "Social Auth flow for Vue and Supabase", + "dependencies": [ + "@supabase/supabase-js@latest" + ], + "registryDependencies": [ + "button", + "card" + ], + "files": [ + { + "path": "registry/default/social-auth/vue/components/login-form.vue", + "content": "\n\n\n", + "type": "registry:component", + "target": "components/login-form.vue" + } + ] +} \ No newline at end of file diff --git a/apps/www/_blog/2025-12-03-supabase-power-for-kiro.mdx b/apps/www/_blog/2025-12-03-supabase-power-for-kiro.mdx new file mode 100644 index 0000000000000..7e896eaf9e28c --- /dev/null +++ b/apps/www/_blog/2025-12-03-supabase-power-for-kiro.mdx @@ -0,0 +1,88 @@ +--- +title: 'The new Supabase power for Kiro' +description: 'Build full-stack applications faster with the Kiro IDE using deep knowledge of your Supabase project, best practices for database migrations, edge functions, and security policies.' +author: matt_rossman +image: 2025-12-03-supabase-power-for-kiro/og.png +thumb: 2025-12-03-supabase-power-for-kiro/thumb.png +categories: + - product + - launch-week +date: '2025-12-03' +toc_depth: 2 +--- + +Today we are announcing new Supabase powers for [Amazon's Kiro IDE](https://www.kiro.dev). With these powers, you can build full-stack applications faster by giving Kiro deep knowledge of your Supabase project, best practices for database migrations, edge functions, and security policies. + +## What are Kiro powers? + +[Kiro powers](https://kiro.dev/blog/introducing-powers/) bundles MCP tools and steering files into a single install, giving your agent specialized knowledge without overwhelming it with context. Traditional MCP servers load all their tools into context immediately, overwhelming the agent. Powers load only when relevant. + +When you install a Supabase power in Kiro, the AI assistant gets immediate access to: + +- Your Supabase database schema + +- Best practices for writing edge functions + +- Security guidelines for Row Level Security (RLS) policies + +- SQL syntax optimized for Postgres + +## What you can do with the Supabase powers + +The Supabase powers connect Kiro to your Supabase projects and gives you one-click access to common development tasks. We're releasing two powers—one for developing on hosted projects, and one for developing with the local Supabase stack through Supabase CLI. + +### Manage database schemas + +Kiro can read your database schema, suggest migrations, and help you structure tables following Postgres best practices. The powers uses Supabase's CLI and MCP server to understand your current schema and make informed suggestions about changes. + +![Managing database schemas in Kiro](/images/blog/2025-12-03-supabase-power-for-kiro/1.png) + +### Review edge functions + +Click a button and Kiro will review your edge functions for common issues, performance problems, and best practices. The powers includes specific guidance on how Supabase edge functions work, including the Deno runtime, environment variables, and local testing. + +![Reviewing edge functions in Kiro](/images/blog/2025-12-03-supabase-power-for-kiro/2.png) + +### Check security and performance + +The powers includes automations to check advisors that scan your local project for security issues and performance bottlenecks. Kiro can identify missing RLS policies, inefficient queries, and common security mistakes before you deploy. + +![Security and performance checks in Kiro](/images/blog/2025-12-03-supabase-power-for-kiro/3.png) + +### Write better SQL + +Kiro gets context-aware help for writing SQL that works well with Postgres. The powers include guidance on formatting queries, naming conventions, writing secure database functions, and structuring complex queries with CTEs. Kiro helps ensure your SQL follows Postgres standards and Supabase best practices. + +![Writing SQL with Kiro assistance](/images/blog/2025-12-03-supabase-power-for-kiro/4.png) + +## How it works + +Kiro powers use the Model Context Protocol (MCP) to connect AI assistants to external tools and knowledge. The Supabase power bundles: + +- **MCP server configuration** that connects to your hosted or local Supabase project + +- **Steering files** that tell Kiro how to use Supabase tools correctly + +- **Manual triggers** that give you one-click access to reviews and checks + +- **Dynamic context** that loads only when you're working on database, security, or edge function tasks + +When you ask Kiro to help with a database migration or edge function, it automatically loads the right context without cluttering the conversation with unnecessary information. Kiro also unloads the context when the power is no longer needed. + +## Get started + +To use the Supabase powers: + +1. Install [Kiro](https://kiro.dev/) and add the Supabase power for local or hosted development + +2. Ask Kiro to setup your Supabase project + +3. Start building + +## Built on open standards + +Kiro powers use the same Supabase MCP Server used by other AI coding assistants, ensuring that your app development experience in Kiro remains familiar to you. + +We're committed to delivering a great Supabase development experience across every tool. These Kiro powers gives developers instant access to best practices, security checks, and deep platform knowledge without leaving their editor. + +Try the Supabase powers today and let us know what you build. diff --git a/apps/www/app/api-v2/blog-posts/route.ts b/apps/www/app/api-v2/blog-posts/route.ts new file mode 100644 index 0000000000000..2ac79b060e1c2 --- /dev/null +++ b/apps/www/app/api-v2/blog-posts/route.ts @@ -0,0 +1,87 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getSortedPosts } from 'lib/posts' +import { getAllCMSPosts } from 'lib/get-cms-posts' + +export const revalidate = 30 + +// Cache for combined posts to avoid re-fetching on every request +let cachedPosts: any[] | null = null +let cacheTimestamp = 0 +const CACHE_TTL = 30 * 1000 // 30 seconds + +async function getCombinedPosts() { + const now = Date.now() + + // Return cached posts if still valid + if (cachedPosts && now - cacheTimestamp < CACHE_TTL) { + return cachedPosts + } + + // Get static blog posts + const staticPosts = getSortedPosts({ directory: '_blog' }) + + // Get CMS posts + const cmsPosts = await getAllCMSPosts({ limit: 100 }) + + // Combine and sort by date + const allPosts = [...staticPosts, ...cmsPosts].sort((a: any, b: any) => { + const dateA = new Date(a.date || a.formattedDate).getTime() + const dateB = new Date(b.date || b.formattedDate).getTime() + return dateB - dateA + }) + + // Update cache + cachedPosts = allPosts + cacheTimestamp = now + + return allPosts +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const offset = parseInt(searchParams.get('offset') || '0', 10) + const limit = parseInt(searchParams.get('limit') || '25', 10) + const category = searchParams.get('category') + const search = searchParams.get('q') + + let posts = await getCombinedPosts() + + // Apply category filter + if (category && category !== 'all') { + posts = posts.filter((post: any) => post.categories?.includes(category)) + } + + // Apply search filter + if (search) { + const searchLower = search.toLowerCase() + posts = posts.filter((post: any) => { + return ( + post.tags?.join(' ').replaceAll('-', ' ').includes(searchLower) || + post.title?.toLowerCase().includes(searchLower) || + post.author?.toLowerCase().includes(searchLower) + ) + }) + } + + const total = posts.length + const paginatedPosts = posts.slice(offset, offset + limit) + const hasMore = offset + limit < total + + return NextResponse.json({ + success: true, + posts: paginatedPosts, + total, + offset, + limit, + hasMore, + }) + } catch (error) { + console.error('[blog-posts] Error:', error) + return NextResponse.json({ + success: false, + error: 'Internal server error', + message: error instanceof Error ? error.message : 'Unknown error', + }) + } +} diff --git a/apps/www/app/blog/BlogClient.tsx b/apps/www/app/blog/BlogClient.tsx index e3c9646efe097..636cefd9ed6af 100644 --- a/apps/www/app/blog/BlogClient.tsx +++ b/apps/www/app/blog/BlogClient.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from 'react' +import { useState, useCallback } from 'react' import DefaultLayout from 'components/Layouts/Default' import BlogGridItem from 'components/Blog/BlogGridItem' import BlogListItem from 'components/Blog/BlogListItem' @@ -8,31 +8,170 @@ import BlogFilters from 'components/Blog/BlogFilters' import FeaturedThumb from 'components/Blog/FeaturedThumb' import { cn } from 'ui' import { LOCAL_STORAGE_KEYS, isBrowser } from 'common' +import { useInfiniteScrollWithFetch } from 'hooks/useInfiniteScroll' import type PostTypes from 'types/post' export type BlogView = 'list' | 'grid' -export default function BlogClient(props: { blogs: any[] }) { +const POSTS_PER_PAGE = 25 +const SKELETON_COUNT = 6 + +function BlogListItemSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) +} + +function BlogGridItemSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) +} + +interface BlogClientProps { + initialBlogs: any[] + totalPosts: number +} + +export default function BlogClient({ initialBlogs, totalPosts }: BlogClientProps) { const { BLOG_VIEW } = LOCAL_STORAGE_KEYS const localView = isBrowser ? (localStorage?.getItem(BLOG_VIEW) as BlogView) : undefined - const [blogs, setBlogs] = useState(props.blogs) const [view, setView] = useState(localView ?? 'list') + const [isFiltering, setIsFiltering] = useState(false) + const [filterParams, setFilterParams] = useState<{ category?: string; search?: string }>({}) + const [filteredPosts, setFilteredPosts] = useState(null) + const [filteredTotal, setFilteredTotal] = useState(null) const isList = view === 'list' + // Determine which posts and total to use based on filter state + const currentPosts = filteredPosts ?? initialBlogs + const currentTotal = filteredTotal ?? totalPosts + + const fetchMorePosts = useCallback( + async (offset: number, limit: number) => { + const params = new URLSearchParams({ + offset: offset.toString(), + limit: limit.toString(), + }) + + if (filterParams.category && filterParams.category !== 'all') { + params.set('category', filterParams.category) + } + if (filterParams.search) { + params.set('q', filterParams.search) + } + + const response = await fetch(`/api-v2/blog-posts?${params}`) + const data = await response.json() + + if (!data.success) { + throw new Error(data.error || 'Failed to fetch posts') + } + + return data.posts + }, + [filterParams] + ) + + const { + items: blogs, + setItems: setBlogs, + hasMore, + isLoading, + loadMoreRef, + } = useInfiniteScrollWithFetch({ + initialItems: currentPosts, + totalItems: currentTotal, + pageSize: POSTS_PER_PAGE, + fetchMore: fetchMorePosts, + rootMargin: '1000px', + }) + + // Handle filter changes - fetch filtered results from API + const handleFilterChange = useCallback(async (category?: string, search?: string) => { + // If no filters, reset to initial state + if ((!category || category === 'all') && !search) { + setFilterParams({}) + setFilteredPosts(null) + setFilteredTotal(null) + setIsFiltering(false) + return + } + + setIsFiltering(true) + setFilterParams({ category, search }) + + try { + const params = new URLSearchParams({ + offset: '0', + limit: POSTS_PER_PAGE.toString(), + }) + + if (category && category !== 'all') { + params.set('category', category) + } + if (search) { + params.set('q', search) + } + + const response = await fetch(`/api-v2/blog-posts?${params}`) + const data = await response.json() + + if (data.success) { + setFilteredPosts(data.posts) + setFilteredTotal(data.total) + } + } catch (error) { + console.error('Failed to fetch filtered posts:', error) + } finally { + setIsFiltering(false) + } + }, []) + + const featuredPost = initialBlogs[0] + return ( <>

Supabase blog

- {props.blogs.slice(0, 1).map((blog) => ( - - ))} + {featuredPost && }
- +
    - {blogs?.length ? ( + {isFiltering ? ( + // Show skeletons while filtering + isList ? ( + Array.from({ length: SKELETON_COUNT }).map((_, idx) => ( +
    + +
    + )) + ) : ( + Array.from({ length: SKELETON_COUNT }).map((_, idx) => ( +
    + +
    + )) + ) + ) : blogs?.length ? ( blogs?.map((blog: PostTypes, idx: number) => isList ? (
    No results

    )}
+ + {hasMore && !isFiltering && ( +
diff --git a/apps/www/app/blog/page.tsx b/apps/www/app/blog/page.tsx index 8d70cd17dc9d4..f4faeba928240 100644 --- a/apps/www/app/blog/page.tsx +++ b/apps/www/app/blog/page.tsx @@ -16,6 +16,8 @@ export const metadata: Metadata = { }, } +const INITIAL_POSTS_LIMIT = 25 + export default async function BlogPage() { // Get static blog posts const staticPostsData = getSortedPosts({ directory: '_blog', runner: '** BLOG PAGE **' }) @@ -30,5 +32,9 @@ export default async function BlogPage() { return dateB - dateA }) - return + // Only send initial posts to client, rest will be loaded via API + const initialPosts = allPosts.slice(0, INITIAL_POSTS_LIMIT) + const totalPosts = allPosts.length + + return } diff --git a/apps/www/components/Blog/BlogFilters.tsx b/apps/www/components/Blog/BlogFilters.tsx index 3bcbdbf3afc28..8b3d71f825a15 100644 --- a/apps/www/components/Blog/BlogFilters.tsx +++ b/apps/www/components/Blog/BlogFilters.tsx @@ -3,9 +3,8 @@ import { LOCAL_STORAGE_KEYS, useBreakpoint } from 'common' import { startCase } from 'lib/helpers' import { useSearchParams, useRouter } from 'next/navigation' -import { useEffect, useState } from 'react' +import { useEffect, useState, useCallback } from 'react' import type { BlogView } from 'app/blog/BlogClient' -import type PostTypes from 'types/post' import { AlignJustify, ChevronDown, Grid, Search, X as CloseIcon } from 'lucide-react' import { @@ -19,8 +18,7 @@ import { } from 'ui' interface Props { - allPosts: PostTypes[] - setPosts: (posts: any) => void + onFilterChange: (category?: string, search?: string) => void view: BlogView setView: (view: any) => void } @@ -33,7 +31,7 @@ interface Props { * ✅ search via category and reset q param if present */ -function BlogFilters({ allPosts, setPosts, view, setView }: Props) { +function BlogFilters({ onFilterChange, view, setView }: Props) { const { BLOG_VIEW } = LOCAL_STORAGE_KEYS const isList = view === 'list' const [category, setCategory] = useState('all') @@ -60,41 +58,47 @@ function BlogFilters({ allPosts, setPosts, view, setView }: Props) { 'launch-week', ] - useEffect(() => { - if (!q) { - handlePosts() - } - }, [category]) + // Debounced filter change to avoid too many API calls + const debouncedFilterChange = useCallback( + (() => { + let timeoutId: NodeJS.Timeout + return (cat: string, search: string) => { + clearTimeout(timeoutId) + timeoutId = setTimeout(() => { + onFilterChange(cat, search) + }, 300) + } + })(), + [onFilterChange] + ) + // Handle URL params on mount useEffect(() => { if (q) { - handleSearchByText(q) - } - }, [q]) - - const handleReplaceRouter = () => { - if (!searchTerm && category !== 'all' && router) { - router?.replace(`/blog?category=${category}`, { scroll: false }) + setSearchTerm(q) + onFilterChange(category, q) + } else if (activeCategory && activeCategory !== 'all') { + setCategory(activeCategory) + onFilterChange(activeCategory, '') } - } + }, []) // Only run on mount - const handlePosts = () => { - // construct an array of blog posts - // not including the first blog post - const shiftedBlogs = [...allPosts] - shiftedBlogs.shift() + const handleSearchByText = useCallback( + (text: string) => { + setSearchTerm(text) - handleReplaceRouter() + // Update URL + if (text.length > 0) { + router?.replace(`/blog?q=${text}`, { scroll: false }) + } else { + router?.replace('/blog', { scroll: false }) + } - setPosts( - category === 'all' - ? shiftedBlogs - : allPosts.filter((post: any) => { - const found = post.categories?.includes(category) - return found - }) - ) - } + // Trigger filter change (debounced) + debouncedFilterChange(category, text) + }, + [category, router, debouncedFilterChange] + ) useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { @@ -110,47 +114,31 @@ function BlogFilters({ allPosts, setPosts, view, setView }: Props) { setShowSearchInput(!isMobile) }, [isMobile]) - useEffect(() => { - if (q) { - setSearchTerm(q) - } - if (activeCategory && activeCategory !== 'all') { - setCategory(activeCategory) - } - }, [activeCategory, q]) - - function handleSearchByText(text: string) { - setSearchTerm(text) - searchParams?.has('q') && router?.replace('/blog', { scroll: false }) - router?.replace(`/blog?q=${text}`, { scroll: false }) - if (text.length < 1) router?.replace('/blog', { scroll: false }) - - const matches = allPosts.filter((post: any) => { - const found = - post.tags?.join(' ').replaceAll('-', ' ').includes(text.toLowerCase()) || - post.title?.toLowerCase().includes(text.toLowerCase()) || - post.author?.includes(text.toLowerCase()) - return found - }) + const handleSetCategory = useCallback( + (newCategory: string) => { + setSearchTerm('') + setCategory(newCategory) - setPosts(matches) - } + // Update URL + if (newCategory === 'all') { + router?.replace('/blog', { scroll: false }) + } else { + router?.replace(`/blog?category=${newCategory}`, { scroll: false }) + } - const handleSetCategory = (category: string) => { - searchTerm && handlePosts() - searchTerm && setSearchTerm('') - setCategory(category) - category === 'all' - ? router?.replace('/blog', { scroll: false }) - : router?.replace(`/blog?category=${category}`, { - scroll: false, - }) - } + // Trigger filter change immediately for category changes + onFilterChange(newCategory, '') + }, + [router, onFilterChange] + ) - const handleSearchChange = (event: any) => { - activeCategory && setCategory('all') - handleSearchByText(event.target.value) - } + const handleSearchChange = useCallback( + (event: any) => { + setCategory('all') + handleSearchByText(event.target.value) + }, + [handleSearchByText] + ) const handleViewSelection = () => { setView((prevView: 'list' | 'grid') => { diff --git a/apps/www/components/Blog/BlogGridItem.tsx b/apps/www/components/Blog/BlogGridItem.tsx index 1653fc5afe817..de4cac5dc91ed 100644 --- a/apps/www/components/Blog/BlogGridItem.tsx +++ b/apps/www/components/Blog/BlogGridItem.tsx @@ -39,6 +39,7 @@ const BlogGridItem = ({ post }: Props) => { return (
diff --git a/apps/www/components/Blog/BlogListItem.tsx b/apps/www/components/Blog/BlogListItem.tsx index 165e9519bc269..deec2f87cb440 100644 --- a/apps/www/components/Blog/BlogListItem.tsx +++ b/apps/www/components/Blog/BlogListItem.tsx @@ -48,6 +48,7 @@ const BlogListItem = ({ post }: Props) => { return (
diff --git a/apps/www/hooks/useInfiniteScroll.ts b/apps/www/hooks/useInfiniteScroll.ts new file mode 100644 index 0000000000000..7c58285a14a99 --- /dev/null +++ b/apps/www/hooks/useInfiniteScroll.ts @@ -0,0 +1,92 @@ +'use client' + +import { useEffect, useRef, useCallback, useState } from 'react' + +interface UseInfiniteScrollOptions { + /** Threshold for triggering the callback (0-1) */ + threshold?: number + /** Root margin for the intersection observer */ + rootMargin?: string +} + +interface UseInfiniteScrollWithFetchOptions extends UseInfiniteScrollOptions { + /** Initial items to display */ + initialItems: T[] + /** Total number of items available */ + totalItems: number + /** Number of items to fetch per page */ + pageSize: number + /** Function to fetch more items */ + fetchMore: (offset: number, limit: number) => Promise +} + +export function useInfiniteScrollWithFetch({ + initialItems, + totalItems, + pageSize, + fetchMore, + threshold = 0.1, + rootMargin = '100px', +}: UseInfiniteScrollWithFetchOptions) { + const [items, setItems] = useState(initialItems) + const [currentTotal, setCurrentTotal] = useState(totalItems) + const [isLoading, setIsLoading] = useState(false) + const [hasMore, setHasMore] = useState(initialItems.length < totalItems) + const loadMoreRef = useRef(null) + const fetchingRef = useRef(false) + + // Reset when initial items change (e.g., filtering via server) + useEffect(() => { + setItems(initialItems) + setCurrentTotal(totalItems) + setHasMore(initialItems.length < totalItems) + }, [initialItems, totalItems]) + + const loadMore = useCallback(async () => { + if (fetchingRef.current || !hasMore) return + + fetchingRef.current = true + setIsLoading(true) + + try { + const newItems = await fetchMore(items.length, pageSize) + setItems((prev) => [...prev, ...newItems]) + // If we got fewer items than requested, we've reached the end + setHasMore(newItems.length === pageSize) + } catch (error) { + console.error('Failed to load more items:', error) + } finally { + setIsLoading(false) + fetchingRef.current = false + } + }, [items.length, pageSize, fetchMore, hasMore]) + + useEffect(() => { + const currentRef = loadMoreRef.current + if (!currentRef || !hasMore || isLoading) return + + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && !fetchingRef.current) { + loadMore() + } + }, + { threshold, rootMargin } + ) + + observer.observe(currentRef) + + return () => { + observer.unobserve(currentRef) + } + }, [hasMore, isLoading, loadMore, threshold, rootMargin]) + + return { + items, + setItems, + hasMore, + isLoading, + loadMoreRef, + totalCount: totalItems, + } +} diff --git a/apps/www/lib/authors.json b/apps/www/lib/authors.json index c00445e5fc656..f32eaa31a563e 100644 --- a/apps/www/lib/authors.json +++ b/apps/www/lib/authors.json @@ -761,5 +761,13 @@ "position": "Product Design", "author_url": "https://github.com/dnywh", "author_image_url": "https://github.com/dnywh.png" + }, + { + "author_id": "matt_rossman", + "author": "Matt Rossman", + "username": "mattrossman", + "position": "Engineering", + "author_url": "https://twitter.com/the_ross_man", + "author_image_url": "https://github.com/mattrossman.png" } ] diff --git a/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/1.png b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/1.png new file mode 100644 index 0000000000000..588fb53a80473 Binary files /dev/null and b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/1.png differ diff --git a/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/2.png b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/2.png new file mode 100644 index 0000000000000..983a214e818dc Binary files /dev/null and b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/2.png differ diff --git a/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/3.png b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/3.png new file mode 100644 index 0000000000000..6221c6feedd3c Binary files /dev/null and b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/3.png differ diff --git a/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/4.png b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/4.png new file mode 100644 index 0000000000000..641c36a890833 Binary files /dev/null and b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/4.png differ diff --git a/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/og.png b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/og.png new file mode 100644 index 0000000000000..46dadffe33261 Binary files /dev/null and b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/og.png differ diff --git a/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/thumb.png b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/thumb.png new file mode 100644 index 0000000000000..46dadffe33261 Binary files /dev/null and b/apps/www/public/images/blog/2025-12-03-supabase-power-for-kiro/thumb.png differ diff --git a/apps/www/public/rss.xml b/apps/www/public/rss.xml index fe20b729a5b56..070a20dc3d8a2 100644 --- a/apps/www/public/rss.xml +++ b/apps/www/public/rss.xml @@ -4,9 +4,16 @@ https://supabase.com Latest news from Supabase en - Tue, 02 Dec 2025 00:00:00 -0700 + Wed, 03 Dec 2025 00:00:00 -0700 + https://supabase.com/blog/supabase-power-for-kiro + The new Supabase power for Kiro + https://supabase.com/blog/supabase-power-for-kiro + Build full-stack applications faster with the Kiro IDE using deep knowledge of your Supabase project, best practices for database migrations, edge functions, and security policies. + Wed, 03 Dec 2025 00:00:00 -0700 + + https://supabase.com/blog/introducing-analytics-buckets Introducing Analytics Buckets https://supabase.com/blog/introducing-analytics-buckets @@ -2619,4 +2626,4 @@ - \ No newline at end of file + diff --git a/blocks/vue/registry/default/password-based-auth/vue/registry-item.json b/blocks/vue/registry/default/password-based-auth/vue/registry-item.json index 8966e22af38f2..bc62839ba3bd2 100644 --- a/blocks/vue/registry/default/password-based-auth/vue/registry-item.json +++ b/blocks/vue/registry/default/password-based-auth/vue/registry-item.json @@ -7,19 +7,23 @@ "files": [ { "path": "registry/default/password-based-auth/vue/components/login-form.vue", - "type": "registry:component" + "type": "registry:component", + "target": "components/login-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/sign-up-form.vue", - "type": "registry:component" + "type": "registry:component", + "target": "components/sign-up-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/forgot-password-form.vue", - "type": "registry:component" + "type": "registry:component", + "target": "components/forgot-password-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/update-password-form.vue", - "type": "registry:component" + "type": "registry:component", + "target": "components/update-password-form.vue" } ], "dependencies": ["@supabase/supabase-js@latest"] diff --git a/blocks/vue/registry/default/social-auth/nuxtjs/app/components/login-form.vue b/blocks/vue/registry/default/social-auth/nuxtjs/app/components/login-form.vue new file mode 100644 index 0000000000000..ef5ebeb466cc2 --- /dev/null +++ b/blocks/vue/registry/default/social-auth/nuxtjs/app/components/login-form.vue @@ -0,0 +1,61 @@ + + + diff --git a/blocks/vue/registry/default/social-auth/nuxtjs/app/components/logout-button.vue b/blocks/vue/registry/default/social-auth/nuxtjs/app/components/logout-button.vue new file mode 100644 index 0000000000000..066aa0067548a --- /dev/null +++ b/blocks/vue/registry/default/social-auth/nuxtjs/app/components/logout-button.vue @@ -0,0 +1,17 @@ + + + diff --git a/blocks/vue/registry/default/social-auth/nuxtjs/app/pages/auth/error.vue b/blocks/vue/registry/default/social-auth/nuxtjs/app/pages/auth/error.vue new file mode 100644 index 0000000000000..966dea5d7c4dc --- /dev/null +++ b/blocks/vue/registry/default/social-auth/nuxtjs/app/pages/auth/error.vue @@ -0,0 +1,30 @@ + + + \ No newline at end of file diff --git a/blocks/vue/registry/default/social-auth/nuxtjs/app/pages/auth/login.vue b/blocks/vue/registry/default/social-auth/nuxtjs/app/pages/auth/login.vue new file mode 100644 index 0000000000000..76bdef58a23c0 --- /dev/null +++ b/blocks/vue/registry/default/social-auth/nuxtjs/app/pages/auth/login.vue @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/blocks/vue/registry/default/social-auth/nuxtjs/app/pages/protected/index.vue b/blocks/vue/registry/default/social-auth/nuxtjs/app/pages/protected/index.vue new file mode 100644 index 0000000000000..8722f8a6872e7 --- /dev/null +++ b/blocks/vue/registry/default/social-auth/nuxtjs/app/pages/protected/index.vue @@ -0,0 +1,36 @@ + + + diff --git a/blocks/vue/registry/default/social-auth/nuxtjs/registry-item.json b/blocks/vue/registry/default/social-auth/nuxtjs/registry-item.json new file mode 100644 index 0000000000000..b252d0c7f6adf --- /dev/null +++ b/blocks/vue/registry/default/social-auth/nuxtjs/registry-item.json @@ -0,0 +1,45 @@ +{ + "name": "social-auth-nuxtjs", + "type": "registry:block", + "title": "Social Auth flow for Nuxt and Supabase", + "description": "Social Auth flow for Nuxt and Supabase", + "registryDependencies": ["button", "card", "input", "label"], + "files": [ + { + "path": "registry/default/social-auth/nuxtjs/app/components/login-form.vue", + "type": "registry:file", + "target": "app/components/login-form.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/components/logout-button.vue", + "type": "registry:file", + "target": "app/components/logout-button.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/pages/auth/login.vue", + "type": "registry:file", + "target": "app/pages/auth/login.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/pages/auth/error.vue", + "type": "registry:file", + "target": "app/pages/auth/error.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/app/pages/protected/index.vue", + "type": "registry:file", + "target": "app/pages/protected/index.vue" + }, + { + "path": "registry/default/social-auth/nuxtjs/server/middleware/auth.ts", + "type": "registry:file", + "target": "server/middleware/auth.ts" + }, + { + "path": "registry/default/social-auth/nuxtjs/server/routes/auth/oauth.ts", + "type": "registry:file", + "target": "server/routes/auth/oauth.ts" + } + ], + "dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"] +} diff --git a/blocks/vue/registry/default/social-auth/nuxtjs/server/middleware/auth.ts b/blocks/vue/registry/default/social-auth/nuxtjs/server/middleware/auth.ts new file mode 100644 index 0000000000000..e87ca43ea0ea7 --- /dev/null +++ b/blocks/vue/registry/default/social-auth/nuxtjs/server/middleware/auth.ts @@ -0,0 +1,24 @@ +import { defineEventHandler, sendRedirect } from 'h3' +import { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client' + +export default defineEventHandler(async (event) => { + const supabase = createSupabaseServerClient(event) + + // Get user claims + const { data } = await supabase.auth.getClaims() + const user = data?.claims + + const pathname = event.node.req.url || '/' + + // Redirect if no user and not already on login/auth route + if ( + !user && + !pathname.startsWith('/login') && + !pathname.startsWith('/auth') + ) { + return sendRedirect(event, '/auth/login') + } + + // Return event as-is (you could return any object if needed) + return { user } +}) diff --git a/blocks/vue/registry/default/social-auth/nuxtjs/server/routes/auth/oauth.ts b/blocks/vue/registry/default/social-auth/nuxtjs/server/routes/auth/oauth.ts new file mode 100644 index 0000000000000..16994eb83d572 --- /dev/null +++ b/blocks/vue/registry/default/social-auth/nuxtjs/server/routes/auth/oauth.ts @@ -0,0 +1,38 @@ +import { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client' +import { defineEventHandler, getQuery, sendRedirect, getRequestURL } from "h3" + +export default defineEventHandler(async (event) => { + const url = getRequestURL(event) // URL object of the current request + const query = getQuery(event) + + const code = query.code as string | undefined + let next = (query.next as string | undefined) ?? "/" + + if (!next.startsWith("/")) { + next = "/" + } + + if (code) { + const supabase = createSupabaseServerClient(event) + const { error } = await supabase.auth.exchangeCodeForSession(code) + + if (!error) { + // Determine origin + const forwardedHost = event.node.req.headers["x-forwarded-host"] as string | undefined + const isLocalEnv = process.env.NODE_ENV === "development" + const origin = `${url.protocol}//${url.host}` + + if (isLocalEnv) { + return sendRedirect(event, `${origin}${next}`) + } else if (forwardedHost) { + return sendRedirect(event, `https://${forwardedHost}${next}`) + } else { + return sendRedirect(event, `${origin}${next}`) + } + } + } + + // fallback to error page + const origin = `${url.protocol}//${url.host}` + return sendRedirect(event, `${origin}/auth/error`) +}) diff --git a/blocks/vue/registry/default/social-auth/vue/components/login-form.vue b/blocks/vue/registry/default/social-auth/vue/components/login-form.vue new file mode 100644 index 0000000000000..6744a764eb0a7 --- /dev/null +++ b/blocks/vue/registry/default/social-auth/vue/components/login-form.vue @@ -0,0 +1,57 @@ + + + diff --git a/blocks/vue/registry/default/social-auth/vue/registry-item.json b/blocks/vue/registry/default/social-auth/vue/registry-item.json new file mode 100644 index 0000000000000..2838756ba6b43 --- /dev/null +++ b/blocks/vue/registry/default/social-auth/vue/registry-item.json @@ -0,0 +1,15 @@ +{ + "name": "social-auth-vue", + "type": "registry:block", + "title": "Social Auth flow for Vue and Supabase", + "description": "Social Auth flow for Vue and Supabase", + "registryDependencies": ["button", "card"], + "files": [ + { + "path": "registry/default/social-auth/vue/components/login-form.vue", + "type": "registry:component", + "target": "components/login-form.vue" + } + ], + "dependencies": ["@supabase/supabase-js@latest"] +} diff --git a/blocks/vue/registry/index.ts b/blocks/vue/registry/index.ts index b432fb397b998..0ce964f3b342c 100644 --- a/blocks/vue/registry/index.ts +++ b/blocks/vue/registry/index.ts @@ -1,6 +1,7 @@ import { clients } from './clients' import { passwordBasedAuth } from './password-based-auth' +import { socialAuth } from './social-auth' -const blocks = [...clients, ...passwordBasedAuth] +const blocks = [...clients, ...passwordBasedAuth, ...socialAuth] export { blocks } diff --git a/blocks/vue/registry/social-auth.ts b/blocks/vue/registry/social-auth.ts new file mode 100644 index 0000000000000..8d7a0cabff6d1 --- /dev/null +++ b/blocks/vue/registry/social-auth.ts @@ -0,0 +1,5 @@ +import { type Registry } from 'shadcn/schema' +import vue from './default/social-auth/vue/registry-item.json' with { type: 'json' } +import nuxt from './default/social-auth/nuxtjs/registry-item.json' with { type: 'json' } + +export const socialAuth = [vue, nuxt] as Registry['items'] diff --git a/e2e/studio/utils/storage-helpers.ts b/e2e/studio/utils/storage-helpers.ts index 21e25f06aaa7a..3abaa83f0c6e0 100644 --- a/e2e/studio/utils/storage-helpers.ts +++ b/e2e/studio/utils/storage-helpers.ts @@ -1,6 +1,6 @@ import { expect, Page } from '@playwright/test' -import { waitForApiResponse } from './wait-for-response' -import { toUrl } from './to-url' +import { toUrl } from './to-url.js' +import { waitForApiResponse } from './wait-for-response.js' /** * Dismisses any visible toast notifications @@ -95,7 +95,9 @@ export const deleteBucket = async (page: Page, ref: string, bucketName: string) // Type bucket name in the confirmation textbox (placeholder: "Type bucket name") const confirmInput = page.getByPlaceholder('Type bucket name') - await expect(confirmInput, 'Confirmation input should be visible').toBeVisible() + await expect(confirmInput, 'Confirmation input should be visible').toBeVisible({ + timeout: 15_000, + }) await confirmInput.fill(bucketName) // Wait for API call and click Delete bucket button @@ -124,13 +126,26 @@ export const deleteBucket = async (page: Page, ref: string, bucketName: string) * @param bucketName - Name of the bucket to navigate to */ export const navigateToBucket = async (page: Page, ref: string, bucketName: string) => { - // Click on the bucket row to navigate + // Identify the bucket row to click const bucketRow = page.getByRole('row').filter({ hasText: bucketName }) await expect(bucketRow, `Bucket row for ${bucketName} should be visible`).toBeVisible() - await bucketRow.click() - // Wait for navigation to complete - await page.waitForURL(new RegExp(`/storage/files/buckets/${encodeURIComponent(bucketName)}`)) + // Click the bucket and wait for page to load + const navigationPromise = page.waitForURL( + new RegExp(`/storage/files/buckets/${encodeURIComponent(bucketName)}`) + ) + const apiPromise = waitForApiResponse( + page, + 'storage', + ref, + `buckets/${bucketName}/objects/list`, + { + method: 'POST', + } + ) + await bucketRow.click() + await navigationPromise + await apiPromise // Verify we're in the bucket by checking the breadcrumb or "Edit bucket" button await expect( @@ -181,7 +196,7 @@ export const uploadFile = async (page: Page, filePath: string, fileName: string) await expect( page.getByTitle(fileName), `File ${fileName} should be visible in explorer after upload` - ).toBeVisible({ timeout: 15_000 }) + ).toBeVisible() } /** @@ -234,7 +249,7 @@ export const renameItem = async (page: Page, oldName: string, newName: string) = // Verify item was renamed await expect(page.getByTitle(newName), `Item should be renamed to ${newName}`).toBeVisible({ - timeout: 10_000, + timeout: 30_000, }) await expect( page.getByTitle(oldName), diff --git a/package.json b/package.json index e4ebac495d249..1d2594c9ca21d 100644 --- a/package.json +++ b/package.json @@ -59,9 +59,9 @@ "url": "git+https://github.com/supabase/supabase.git" }, "engines": { - "pnpm": "10.18", + "pnpm": "10.24", "node": ">=22" }, "keywords": ["postgres", "firebase", "storage", "functions", "database", "auth"], - "packageManager": "pnpm@10.18.0" + "packageManager": "pnpm@10.24.0" } diff --git a/packages/eslint-config-supabase/package.json b/packages/eslint-config-supabase/package.json index 4a73e5f229c55..119ce5982fa34 100644 --- a/packages/eslint-config-supabase/package.json +++ b/packages/eslint-config-supabase/package.json @@ -12,6 +12,7 @@ "@eslint/eslintrc": "^3.0.0", "@eslint/js": "^9.0.0", "@tanstack/eslint-plugin-query": "^4.0.0", + "@typescript-eslint/eslint-plugin": "^8.48.0", "eslint-config-next": "^15.5.0", "eslint-config-prettier": "^10.0.0", "eslint-config-turbo": "^2.5.0", diff --git a/packages/ui/src/components/ShadowScrollArea/index.tsx b/packages/ui/src/components/ShadowScrollArea/index.tsx index c2f4a497c7525..49ac621073601 100644 --- a/packages/ui/src/components/ShadowScrollArea/index.tsx +++ b/packages/ui/src/components/ShadowScrollArea/index.tsx @@ -7,6 +7,7 @@ interface ShadowScrollAreaProps extends React.HTMLAttributes { children: React.ReactNode containerClassName?: string stickyLastColumn?: boolean + outerContainerRef?: React.Ref } /** @@ -16,7 +17,10 @@ interface ShadowScrollAreaProps extends React.HTMLAttributes { */ const ShadowScrollArea = React.forwardRef( - ({ className, containerClassName, children, stickyLastColumn, ...props }, ref) => { + ( + { className, containerClassName, children, stickyLastColumn, outerContainerRef, ...props }, + ref + ) => { const containerRef = React.useRef(null) const { hasHorizontalScroll, canScrollLeft, canScrollRight } = useHorizontalScroll(containerRef) @@ -34,7 +38,7 @@ const ShadowScrollArea = React.forwardRef ) return ( -
+
=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4839,14 +4842,11 @@ packages: '@next/bundle-analyzer@16.0.4': resolution: {integrity: sha512-6IajJ23QrXW5RTJj2lRHcBM8mxcEl+vgd7XXVODQG/BcJyjgIP1k5OefdRl+P80btPvHeHoV4fIgC1so25pXcg==} - '@next/env@15.3.3': - resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==} + '@next/env@15.5.7': + resolution: {integrity: sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==} - '@next/env@15.5.6': - resolution: {integrity: sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q==} - - '@next/env@16.0.4': - resolution: {integrity: sha512-FDPaVoB1kYhtOz6Le0Jn2QV7RZJ3Ngxzqri7YX4yu3Ini+l5lciR7nA9eNDpKTmDm7LWZtxSju+/CQnwRBn2pA==} + '@next/env@16.0.7': + resolution: {integrity: sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==} '@next/eslint-plugin-next@15.5.4': resolution: {integrity: sha512-SR1vhXNNg16T4zffhJ4TS7Xn7eq4NfKfcOsRwea7RIAHrjRpI9ALYbamqIJqkAhowLlERffiwk0FMvTLNdnVtw==} @@ -4862,110 +4862,6 @@ packages: '@mdx-js/react': optional: true - '@next/swc-darwin-arm64@15.5.6': - resolution: {integrity: sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-arm64@16.0.4': - resolution: {integrity: sha512-TN0cfB4HT2YyEio9fLwZY33J+s+vMIgC84gQCOLZOYusW7ptgjIn8RwxQt0BUpoo9XRRVVWEHLld0uhyux1ZcA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-x64@15.5.6': - resolution: {integrity: sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-darwin-x64@16.0.4': - resolution: {integrity: sha512-XsfI23jvimCaA7e+9f3yMCoVjrny2D11G6H8NCcgv+Ina/TQhKPXB9P4q0WjTuEoyZmcNvPdrZ+XtTh3uPfH7Q==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@15.5.6': - resolution: {integrity: sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-arm64-gnu@16.0.4': - resolution: {integrity: sha512-uo8X7qHDy4YdJUhaoJDMAbL8VT5Ed3lijip2DdBHIB4tfKAvB1XBih6INH2L4qIi4jA0Qq1J0ErxcOocBmUSwg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-arm64-musl@15.5.6': - resolution: {integrity: sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@next/swc-linux-arm64-musl@16.0.4': - resolution: {integrity: sha512-pvR/AjNIAxsIz0PCNcZYpH+WmNIKNLcL4XYEfo+ArDi7GsxKWFO5BvVBLXbhti8Coyv3DE983NsitzUsGH5yTw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@next/swc-linux-x64-gnu@15.5.6': - resolution: {integrity: sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-x64-gnu@16.0.4': - resolution: {integrity: sha512-2hebpsd5MRRtgqmT7Jj/Wze+wG+ZEXUK2KFFL4IlZ0amEEFADo4ywsifJNeFTQGsamH3/aXkKWymDvgEi+pc2Q==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-x64-musl@15.5.6': - resolution: {integrity: sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@next/swc-linux-x64-musl@16.0.4': - resolution: {integrity: sha512-pzRXf0LZZ8zMljH78j8SeLncg9ifIOp3ugAFka+Bq8qMzw6hPXOc7wydY7ardIELlczzzreahyTpwsim/WL3Sg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@next/swc-win32-arm64-msvc@15.5.6': - resolution: {integrity: sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-arm64-msvc@16.0.4': - resolution: {integrity: sha512-7G/yJVzum52B5HOqqbQYX9bJHkN+c4YyZ2AIvEssMHQlbAWOn3iIJjD4sM6ihWsBxuljiTKJovEYlD1K8lCUHw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@15.5.6': - resolution: {integrity: sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@next/swc-win32-x64-msvc@16.0.4': - resolution: {integrity: sha512-0Vy4g8SSeVkuU89g2OFHqGKM4rxsQtihGfenjx2tRckPrge5+gtFnRWGAAwvGXr0ty3twQvcnYjEyOrLHJ4JWA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - '@noble/ciphers@1.3.0': resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} engines: {node: ^14.21.3 || >=16} @@ -5524,10 +5420,6 @@ packages: resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.36.0': - resolution: {integrity: sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==} - engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.38.0': resolution: {integrity: sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==} engines: {node: '>=14'} @@ -9523,13 +9415,13 @@ packages: '@types/zxcvbn@4.4.2': resolution: {integrity: sha512-T7SEL8b/eN7AEhHQ8oFt7c6Y+l3p8OpH7KwJIe+5oBOPLMMioPeMsUTB3huNgEnXhiittV8Ohdw21Jg8E/f70Q==} - '@typescript-eslint/eslint-plugin@8.34.1': - resolution: {integrity: sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==} + '@typescript-eslint/eslint-plugin@8.48.0': + resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.34.1 + '@typescript-eslint/parser': ^8.48.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/parser@7.2.0': resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} @@ -9541,39 +9433,39 @@ packages: typescript: optional: true - '@typescript-eslint/project-service@8.34.1': - resolution: {integrity: sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==} + '@typescript-eslint/project-service@8.48.0': + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/scope-manager@7.2.0': resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/scope-manager@8.34.1': - resolution: {integrity: sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==} + '@typescript-eslint/scope-manager@8.48.0': + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.34.1': - resolution: {integrity: sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==} + '@typescript-eslint/tsconfig-utils@8.48.0': + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.34.1': - resolution: {integrity: sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==} + '@typescript-eslint/type-utils@8.48.0': + resolution: {integrity: sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/types@7.2.0': resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/types@8.34.1': - resolution: {integrity: sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==} + '@typescript-eslint/types@8.48.0': + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@7.2.0': @@ -9585,25 +9477,25 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@8.34.1': - resolution: {integrity: sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==} + '@typescript-eslint/typescript-estree@8.48.0': + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.34.1': - resolution: {integrity: sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==} + '@typescript-eslint/utils@8.48.0': + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/visitor-keys@7.2.0': resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/visitor-keys@8.34.1': - resolution: {integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==} + '@typescript-eslint/visitor-keys@8.48.0': + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript/vfs@1.6.1': @@ -11174,8 +11066,8 @@ packages: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} - d3-geo@3.1.0: - resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==} + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} engines: {node: '>=12'} d3-hierarchy@3.1.2: @@ -12533,9 +12425,6 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -15149,8 +15038,8 @@ packages: next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - next@15.5.6: - resolution: {integrity: sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ==} + next@15.5.7: + resolution: {integrity: sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -15170,8 +15059,8 @@ packages: sass: optional: true - next@16.0.4: - resolution: {integrity: sha512-vICcxKusY8qW7QFOzTvnRL1ejz2ClTqDKtm1AcUjm2mPv/lVAdgpGNsftsPRIDJOXOjRQO68i1dM8Lp8GZnqoA==} + next@16.0.7: + resolution: {integrity: sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -21145,7 +21034,7 @@ snapshots: '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 1.24.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.36.0 + '@opentelemetry/semantic-conventions': 1.38.0 chokidar: 3.6.0 hash-wasm: 4.11.0 inflection: 3.0.0 @@ -21488,11 +21377,6 @@ snapshots: '@esbuild/win32-x64@0.25.2': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))': - dependencies: - eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1) - eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.9.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))': dependencies: eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1) @@ -22843,11 +22727,11 @@ snapshots: '@mapbox/node-pre-gyp@2.0.0(encoding@0.1.13)(supports-color@8.1.1)': dependencies: consola: 3.4.2 - detect-libc: 2.0.4 + detect-libc: 2.1.2 https-proxy-agent: 7.0.6(supports-color@8.1.1) node-fetch: 2.7.0(encoding@0.1.13) nopt: 8.1.0 - semver: 7.7.2 + semver: 7.7.3 tar: 7.5.2 transitivePeerDependencies: - encoding @@ -23058,11 +22942,9 @@ snapshots: - bufferutil - utf-8-validate - '@next/env@15.3.3': {} + '@next/env@15.5.7': {} - '@next/env@15.5.6': {} - - '@next/env@16.0.4': {} + '@next/env@16.0.7': {} '@next/eslint-plugin-next@15.5.4': dependencies: @@ -23075,54 +22957,6 @@ snapshots: '@mdx-js/loader': 2.3.0(supports-color@8.1.1)(webpack@5.94.0) '@mdx-js/react': 2.3.0(react@18.3.1) - '@next/swc-darwin-arm64@15.5.6': - optional: true - - '@next/swc-darwin-arm64@16.0.4': - optional: true - - '@next/swc-darwin-x64@15.5.6': - optional: true - - '@next/swc-darwin-x64@16.0.4': - optional: true - - '@next/swc-linux-arm64-gnu@15.5.6': - optional: true - - '@next/swc-linux-arm64-gnu@16.0.4': - optional: true - - '@next/swc-linux-arm64-musl@15.5.6': - optional: true - - '@next/swc-linux-arm64-musl@16.0.4': - optional: true - - '@next/swc-linux-x64-gnu@15.5.6': - optional: true - - '@next/swc-linux-x64-gnu@16.0.4': - optional: true - - '@next/swc-linux-x64-musl@15.5.6': - optional: true - - '@next/swc-linux-x64-musl@16.0.4': - optional: true - - '@next/swc-win32-arm64-msvc@15.5.6': - optional: true - - '@next/swc-win32-arm64-msvc@16.0.4': - optional: true - - '@next/swc-win32-x64-msvc@15.5.6': - optional: true - - '@next/swc-win32-x64-msvc@16.0.4': - optional: true - '@noble/ciphers@1.3.0': {} '@noble/curves@1.9.7': @@ -23156,11 +22990,11 @@ snapshots: '@npmcli/fs@1.1.1': dependencies: '@gar/promisify': 1.1.3 - semver: 7.7.2 + semver: 7.7.3 '@npmcli/fs@3.1.1': dependencies: - semver: 7.7.2 + semver: 7.7.3 '@npmcli/git@4.1.0': dependencies: @@ -23252,7 +23086,7 @@ snapshots: pathe: 2.0.3 pkg-types: 2.3.0 prompts: 2.4.2 - semver: 7.7.2 + semver: 7.7.3 '@nuxt/devtools@2.6.4(supports-color@8.1.1)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))': dependencies: @@ -23313,7 +23147,7 @@ snapshots: pkg-types: 2.3.0 rc9: 2.1.2 scule: 1.3.0 - semver: 7.7.2 + semver: 7.7.3 std-env: 3.9.0 tinyglobby: 0.2.15 ufo: 1.6.1 @@ -23978,8 +23812,6 @@ snapshots: '@opentelemetry/semantic-conventions@1.28.0': {} - '@opentelemetry/semantic-conventions@1.36.0': {} - '@opentelemetry/semantic-conventions@1.38.0': {} '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': @@ -24312,12 +24144,12 @@ snapshots: '@payloadcms/live-preview@3.54.0': {} - '@payloadcms/next@3.52.0(@types/react@18.3.3)(graphql@16.11.0)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': + '@payloadcms/next@3.52.0(@types/react@18.3.3)(graphql@16.11.0)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': dependencies: '@dnd-kit/core': 6.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@payloadcms/graphql': 3.52.0(graphql@16.11.0)(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(typescript@5.9.2) '@payloadcms/translations': 3.52.0 - '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) + '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) busboy: 1.6.0 dequal: 2.0.3 file-type: 19.3.0 @@ -24325,7 +24157,7 @@ snapshots: graphql-http: 1.22.4(graphql@16.11.0) graphql-playground-html: 1.6.30 http-status: 2.1.0 - next: 15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) path-to-regexp: 6.3.0 payload: 3.52.0(graphql@16.11.0)(typescript@5.9.2) qs-esm: 7.0.2 @@ -24353,9 +24185,9 @@ snapshots: - aws-crt - encoding - '@payloadcms/plugin-cloud-storage@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': + '@payloadcms/plugin-cloud-storage@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': dependencies: - '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) + '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) find-node-modules: 2.1.3 payload: 3.52.0(graphql@16.11.0)(typescript@5.9.2) range-parser: 1.2.1 @@ -24368,9 +24200,9 @@ snapshots: - supports-color - typescript - '@payloadcms/plugin-form-builder@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': + '@payloadcms/plugin-form-builder@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': dependencies: - '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) + '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) escape-html: 1.0.3 payload: 3.52.0(graphql@16.11.0)(typescript@5.9.2) react: 18.3.1 @@ -24386,10 +24218,10 @@ snapshots: dependencies: payload: 3.52.0(graphql@16.11.0)(typescript@5.9.2) - '@payloadcms/plugin-seo@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': + '@payloadcms/plugin-seo@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': dependencies: '@payloadcms/translations': 3.52.0 - '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) + '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) payload: 3.52.0(graphql@16.11.0)(typescript@5.9.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -24400,7 +24232,7 @@ snapshots: - supports-color - typescript - '@payloadcms/richtext-lexical@3.52.0(@faceless-ui/modal@3.0.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@faceless-ui/scroll-info@2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@payloadcms/next@3.52.0(@types/react@18.3.3)(graphql@16.11.0)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2))(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)(yjs@13.6.27)': + '@payloadcms/richtext-lexical@3.52.0(@faceless-ui/modal@3.0.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@faceless-ui/scroll-info@2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@payloadcms/next@3.52.0(@types/react@18.3.3)(graphql@16.11.0)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2))(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)(yjs@13.6.27)': dependencies: '@faceless-ui/modal': 3.0.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@faceless-ui/scroll-info': 2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -24414,9 +24246,9 @@ snapshots: '@lexical/selection': 0.28.0 '@lexical/table': 0.28.0 '@lexical/utils': 0.28.0 - '@payloadcms/next': 3.52.0(@types/react@18.3.3)(graphql@16.11.0)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) + '@payloadcms/next': 3.52.0(@types/react@18.3.3)(graphql@16.11.0)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) '@payloadcms/translations': 3.52.0 - '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) + '@payloadcms/ui': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) '@types/uuid': 10.0.0 acorn: 8.12.1 bson-objectid: 2.0.4 @@ -24443,12 +24275,12 @@ snapshots: - typescript - yjs - '@payloadcms/storage-s3@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': + '@payloadcms/storage-s3@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': dependencies: '@aws-sdk/client-s3': 3.830.0 '@aws-sdk/lib-storage': 3.830.0(@aws-sdk/client-s3@3.830.0) '@aws-sdk/s3-request-presigner': 3.830.0 - '@payloadcms/plugin-cloud-storage': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) + '@payloadcms/plugin-cloud-storage': 3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2) payload: 3.52.0(graphql@16.11.0)(typescript@5.9.2) transitivePeerDependencies: - '@types/react' @@ -24464,7 +24296,7 @@ snapshots: dependencies: date-fns: 4.1.0 - '@payloadcms/ui@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': + '@payloadcms/ui@3.52.0(@types/react@18.3.3)(monaco-editor@0.52.2)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(payload@3.52.0(graphql@16.11.0)(typescript@5.9.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)(typescript@5.9.2)': dependencies: '@date-fns/tz': 1.2.0 '@dnd-kit/core': 6.0.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -24479,7 +24311,7 @@ snapshots: date-fns: 4.1.0 dequal: 2.0.3 md5: 2.3.0 - next: 15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) object-to-formdata: 4.5.1 payload: 3.52.0(graphql@16.11.0)(typescript@5.9.2) qs-esm: 7.0.2 @@ -26951,7 +26783,7 @@ snapshots: '@sentry/core@10.27.0': {} - '@sentry/nextjs@10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': + '@sentry/nextjs@10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.38.0 @@ -26964,7 +26796,7 @@ snapshots: '@sentry/react': 10.27.0(react@18.3.1) '@sentry/vercel-edge': 10.27.0 '@sentry/webpack-plugin': 4.6.1(encoding@0.1.13)(supports-color@8.1.1)(webpack@5.94.0) - next: 15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) resolve: 1.22.8 rollup: 4.50.2 stacktrace-parser: 0.1.10 @@ -26977,7 +26809,7 @@ snapshots: - supports-color - webpack - '@sentry/nextjs@10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.0.4(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': + '@sentry/nextjs@10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.0.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.38.0 @@ -26990,7 +26822,7 @@ snapshots: '@sentry/react': 10.27.0(react@18.3.1) '@sentry/vercel-edge': 10.27.0 '@sentry/webpack-plugin': 4.6.1(encoding@0.1.13)(supports-color@8.1.1)(webpack@5.94.0) - next: 16.0.4(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 16.0.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) resolve: 1.22.8 rollup: 4.50.2 stacktrace-parser: 0.1.10 @@ -28904,14 +28736,14 @@ snapshots: '@types/zxcvbn@4.4.2': {} - '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 '@typescript-eslint/parser': 7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.34.1 - '@typescript-eslint/type-utils': 8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) - '@typescript-eslint/utils': 8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.34.1 + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/type-utils': 8.48.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) + '@typescript-eslint/utils': 8.48.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.48.0 eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1) graphemer: 1.4.0 ignore: 7.0.5 @@ -28934,10 +28766,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.34.1(supports-color@8.1.1)(typescript@5.9.2)': + '@typescript-eslint/project-service@8.48.0(supports-color@8.1.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.9.2) - '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.2) + '@typescript-eslint/types': 8.48.0 debug: 4.4.3(supports-color@8.1.1) typescript: 5.9.2 transitivePeerDependencies: @@ -28948,19 +28780,20 @@ snapshots: '@typescript-eslint/types': 7.2.0 '@typescript-eslint/visitor-keys': 7.2.0 - '@typescript-eslint/scope-manager@8.34.1': + '@typescript-eslint/scope-manager@8.48.0': dependencies: - '@typescript-eslint/types': 8.34.1 - '@typescript-eslint/visitor-keys': 8.34.1 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 - '@typescript-eslint/tsconfig-utils@8.34.1(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.9.2)': dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.48.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.34.1(supports-color@8.1.1)(typescript@5.9.2) - '@typescript-eslint/utils': 8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(supports-color@8.1.1)(typescript@5.9.2) + '@typescript-eslint/utils': 8.48.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) debug: 4.4.3(supports-color@8.1.1) eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1) ts-api-utils: 2.1.0(typescript@5.9.2) @@ -28970,7 +28803,7 @@ snapshots: '@typescript-eslint/types@7.2.0': {} - '@typescript-eslint/types@8.34.1': {} + '@typescript-eslint/types@8.48.0': {} '@typescript-eslint/typescript-estree@7.2.0(supports-color@8.1.1)(typescript@5.9.2)': dependencies: @@ -28980,35 +28813,34 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.7.2 + semver: 7.7.3 ts-api-utils: 1.0.3(typescript@5.9.2) optionalDependencies: typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.34.1(supports-color@8.1.1)(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.48.0(supports-color@8.1.1)(typescript@5.9.2)': dependencies: - '@typescript-eslint/project-service': 8.34.1(supports-color@8.1.1)(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.9.2) - '@typescript-eslint/types': 8.34.1 - '@typescript-eslint/visitor-keys': 8.34.1 + '@typescript-eslint/project-service': 8.48.0(supports-color@8.1.1)(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.9.2) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 debug: 4.4.3(supports-color@8.1.1) - fast-glob: 3.3.3 - is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.2 + semver: 7.7.3 + tinyglobby: 0.2.15 ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)': + '@typescript-eslint/utils@8.48.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1)) - '@typescript-eslint/scope-manager': 8.34.1 - '@typescript-eslint/types': 8.34.1 - '@typescript-eslint/typescript-estree': 8.34.1(supports-color@8.1.1)(typescript@5.9.2) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1)) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(supports-color@8.1.1)(typescript@5.9.2) eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1) typescript: 5.9.2 transitivePeerDependencies: @@ -29019,9 +28851,9 @@ snapshots: '@typescript-eslint/types': 7.2.0 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@8.34.1': + '@typescript-eslint/visitor-keys@8.48.0': dependencies: - '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/types': 8.48.0 eslint-visitor-keys: 4.2.1 '@typescript/vfs@1.6.1(supports-color@8.1.1)(typescript@5.9.2)': @@ -29067,7 +28899,7 @@ snapshots: async-sema: 3.1.1 bindings: 1.5.0 estree-walker: 2.0.2 - glob: 10.4.5 + glob: 10.5.0 graceful-fs: 4.2.11 node-gyp-build: 4.8.4 picomatch: 4.0.3 @@ -29646,7 +29478,7 @@ snapshots: archiver-utils@5.0.2: dependencies: - glob: 10.4.5 + glob: 10.5.0 graceful-fs: 4.2.11 is-stream: 2.0.1 lazystream: 1.0.1 @@ -30071,7 +29903,7 @@ snapshots: dependencies: '@npmcli/fs': 3.1.1 fs-minipass: 3.0.3 - glob: 10.4.5 + glob: 10.5.0 lru-cache: 10.4.3 minipass: 7.1.2 minipass-collect: 2.0.1 @@ -30889,7 +30721,7 @@ snapshots: d3-format@3.1.0: {} - d3-geo@3.1.0: + d3-geo@3.1.1: dependencies: d3-array: 3.2.4 @@ -30980,7 +30812,7 @@ snapshots: d3-fetch: 3.0.1 d3-force: 3.0.0 d3-format: 3.1.0 - d3-geo: 3.1.0 + d3-geo: 3.1.1 d3-hierarchy: 3.1.2 d3-interpolate: 3.0.1 d3-path: 3.1.0 @@ -31210,8 +31042,7 @@ snapshots: detect-libc@2.0.4: {} - detect-libc@2.1.2: - optional: true + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -31660,7 +31491,7 @@ snapshots: dependencies: '@next/eslint-plugin-next': 15.5.4 '@rushstack/eslint-patch': 1.10.3 - '@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) '@typescript-eslint/parser': 7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2) eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1) eslint-import-resolver-node: 0.3.9(supports-color@8.1.1) @@ -32327,23 +32158,21 @@ snapshots: micromatch: 4.0.8 resolve-dir: 1.0.1 - flags@4.0.1(@opentelemetry/api@1.9.0)(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + flags@4.0.1(@opentelemetry/api@1.9.0)(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@edge-runtime/cookies': 5.0.2 jose: 5.9.6 optionalDependencies: '@opentelemetry/api': 1.9.0 - next: 15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) flat-cache@4.0.1: dependencies: - flatted: 3.3.2 + flatted: 3.3.3 keyv: 4.5.4 - flatted@3.3.2: {} - flatted@3.3.3: {} focus-trap@7.5.4: @@ -33370,7 +33199,7 @@ snapshots: acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) cjs-module-lexer: 1.4.1 - module-details-from-path: 1.0.3 + module-details-from-path: 1.0.4 import-meta-resolve@4.1.0: {} @@ -35752,12 +35581,12 @@ snapshots: neo-async@2.6.2: {} - next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1): + next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1): dependencies: '@contentlayer2/core': 0.4.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) '@contentlayer2/utils': 0.4.3 contentlayer2: 0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) - next: 15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -35781,19 +35610,19 @@ snapshots: dependencies: js-yaml-loader: 1.2.2 - next-router-mock@0.9.13(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1): + next-router-mock@0.9.13(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1): dependencies: - next: 15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 - next-router-mock@0.9.13(next@16.0.4(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1): + next-router-mock@0.9.13(next@16.0.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1): dependencies: - next: 16.0.4(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 16.0.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 - next-seo@6.5.0(next@15.5.6(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-seo@6.5.0(next@15.5.7(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -35804,9 +35633,9 @@ snapshots: next-tick@1.1.0: {} - next@15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): + next@15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): dependencies: - '@next/env': 15.5.6 + '@next/env': 15.5.7 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001743 postcss: 8.4.31 @@ -35814,25 +35643,17 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.6(@babel/core@7.28.4(supports-color@8.1.1))(babel-plugin-macros@3.1.0)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 15.5.6 - '@next/swc-darwin-x64': 15.5.6 - '@next/swc-linux-arm64-gnu': 15.5.6 - '@next/swc-linux-arm64-musl': 15.5.6 - '@next/swc-linux-x64-gnu': 15.5.6 - '@next/swc-linux-x64-musl': 15.5.6 - '@next/swc-win32-arm64-msvc': 15.5.6 - '@next/swc-win32-x64-msvc': 15.5.6 '@opentelemetry/api': 1.9.0 '@playwright/test': 1.56.1 sass: 1.77.4 - sharp: 0.34.3 + sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - next@16.0.4(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): + next@16.0.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): dependencies: - '@next/env': 16.0.4 + '@next/env': 16.0.7 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001743 postcss: 8.4.31 @@ -35840,14 +35661,6 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.6(@babel/core@7.28.4(supports-color@8.1.1))(babel-plugin-macros@3.1.0)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.4 - '@next/swc-darwin-x64': 16.0.4 - '@next/swc-linux-arm64-gnu': 16.0.4 - '@next/swc-linux-arm64-musl': 16.0.4 - '@next/swc-linux-x64-gnu': 16.0.4 - '@next/swc-linux-x64-musl': 16.0.4 - '@next/swc-win32-arm64-msvc': 16.0.4 - '@next/swc-win32-x64-msvc': 16.0.4 '@opentelemetry/api': 1.9.0 '@playwright/test': 1.56.1 sass: 1.77.4 @@ -36019,7 +35832,7 @@ snapshots: nopt: 5.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.7.2 + semver: 7.7.3 tar: 7.5.2 which: 2.0.2 transitivePeerDependencies: @@ -36098,7 +35911,7 @@ snapshots: npm-install-checks@6.3.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 npm-normalize-package-bin@3.0.1: {} @@ -36108,7 +35921,7 @@ snapshots: dependencies: hosted-git-info: 6.1.3 proc-log: 3.0.0 - semver: 7.7.2 + semver: 7.7.3 validate-npm-package-name: 5.0.1 npm-pick-manifest@8.0.2: @@ -36116,7 +35929,7 @@ snapshots: npm-install-checks: 6.3.0 npm-normalize-package-bin: 3.0.1 npm-package-arg: 10.1.0 - semver: 7.7.2 + semver: 7.7.3 npm-run-all@4.1.5: dependencies: @@ -36169,18 +35982,18 @@ snapshots: number-flow@0.3.7: {} - nuqs@1.19.1(next@15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)): + nuqs@1.19.1(next@15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)): dependencies: mitt: 3.0.1 - next: 15.5.6(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 15.5.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) - nuqs@2.7.1(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.0.4(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + nuqs@2.7.1(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.0.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: '@standard-schema/spec': 1.0.0 react: 18.3.1 optionalDependencies: '@tanstack/react-router': 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next: 16.0.4(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + next: 16.0.7(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react-router: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nuxt@4.1.2(@electric-sql/pglite@0.2.15)(@parcel/watcher@2.5.1)(@types/node@22.13.14)(@vue/compiler-sfc@3.5.21)(aws4fetch@1.0.20)(db0@0.3.2(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(pg@8.16.3))(encoding@0.1.13)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(ioredis@5.7.0(supports-color@8.1.1))(magicast@0.3.5)(rollup@4.50.2)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(yaml@2.8.1): @@ -36805,7 +36618,7 @@ snapshots: payload@3.52.0(graphql@16.11.0)(typescript@5.9.2): dependencies: - '@next/env': 15.3.3 + '@next/env': 15.5.7 '@payloadcms/translations': 3.52.0 '@types/busboy': 1.5.4 ajv: 8.17.1 @@ -37882,7 +37695,7 @@ snapshots: react-simple-maps@4.0.0-beta.6(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - d3-geo: 3.1.0 + d3-geo: 3.1.1 d3-selection: 3.0.0 d3-zoom: 3.0.0 prop-types: 15.8.1 @@ -38424,7 +38237,7 @@ snapshots: require-in-the-middle@8.0.1(supports-color@8.1.1): dependencies: debug: 4.4.3(supports-color@8.1.1) - module-details-from-path: 1.0.3 + module-details-from-path: 1.0.4 transitivePeerDependencies: - supports-color @@ -38690,8 +38503,7 @@ snapshots: semver@7.7.2: {} - semver@7.7.3: - optional: true + semver@7.7.3: {} send@0.19.0(supports-color@8.1.1): dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a431a583a8039..b51c4a3b6c7c5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,7 +13,7 @@ catalog: '@types/node': ^22.0.0 '@types/react': ^18.3.0 '@types/react-dom': ^18.3.0 - next: ^15.5.6 + next: ^15.5.7 react: ^18.3.0 react-dom: ^18.3.0 recharts: ^2.15.4 @@ -47,6 +47,8 @@ minimumReleaseAgeExclude: - js-yaml - supabase - iceberg-js + - next@15.5.7 || 16.0.7 + - '@next/env@15.5.7 || 16.0.7' onlyBuiltDependencies: - supabase