diff --git a/.npmrc b/.npmrc index a4047535344..19c081e2d4f 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,3 @@ engine-strict=true update-notifier=false +@jsr:registry=https://npm.jsr.io diff --git a/apps/design-system/registry/default/example/tree-view-demo.tsx b/apps/design-system/registry/default/example/tree-view-demo.tsx index bdf8cfc2708..2ffaabcb47a 100644 --- a/apps/design-system/registry/default/example/tree-view-demo.tsx +++ b/apps/design-system/registry/default/example/tree-view-demo.tsx @@ -26,7 +26,7 @@ export default function TreeViewDemo() { ( ( diff --git a/apps/design-system/registry/default/example/tree-view-edit.tsx b/apps/design-system/registry/default/example/tree-view-edit.tsx index 311cee5b0a3..b690c0fff43 100644 --- a/apps/design-system/registry/default/example/tree-view-edit.tsx +++ b/apps/design-system/registry/default/example/tree-view-edit.tsx @@ -80,6 +80,7 @@ export default function TreeViewDemo() { return ( { return ( diff --git a/apps/design-system/registry/default/example/tree-view-multi-select.tsx b/apps/design-system/registry/default/example/tree-view-multi-select.tsx index 669e59da782..983c616ce77 100644 --- a/apps/design-system/registry/default/example/tree-view-multi-select.tsx +++ b/apps/design-system/registry/default/example/tree-view-multi-select.tsx @@ -84,6 +84,7 @@ export default function TreeViewDemo() { togglableSelect clickAction="EXCLUSIVE_SELECT" multiSelect + className="w-[420px] border bg py-2" nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => { return ( diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index aec69939b3f..1106cc65477 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -652,6 +652,7 @@ export const auth = { name: 'Third-party auth', items: [ { name: 'Overview', url: '/guides/auth/third-party/overview' }, + { name: 'Clerk', url: '/guides/auth/third-party/clerk' }, { name: 'Firebase Auth', url: '/guides/auth/third-party/firebase-auth' }, { name: 'Auth0', url: '/guides/auth/third-party/auth0' }, { name: 'AWS Cognito (Amplify)', url: '/guides/auth/third-party/aws-cognito' }, diff --git a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx index c85641de578..41250bcb1dc 100644 --- a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx +++ b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx @@ -33,8 +33,9 @@ import { import { useCopy } from '~/hooks/useCopy' import { useBranchesQuery } from '~/lib/fetch/branches' import { useOrganizationsQuery } from '~/lib/fetch/organizations' +import { type SupavisorConfigData, useSupavisorConfigQuery } from '~/lib/fetch/pooler' import { useProjectApiQuery } from '~/lib/fetch/projectApi' -import { useProjectsQuery } from '~/lib/fetch/projects' +import { isProjectPaused, type ProjectsData, useProjectsQuery } from '~/lib/fetch/projects' import { LOCAL_STORAGE_KEYS, retrieve, storeOrRemoveNull } from '~/lib/storage' import { useOnLogout } from '~/lib/userAuth' @@ -59,6 +60,7 @@ type VariableDataState = | 'userLoading' | 'loggedOut' | 'loggedIn.noSelectedProject' + | 'loggedIn.selectedProject.projectPaused' | 'loggedIn.selectedProject.dataPending' | 'loggedIn.selectedProject.dataError' | 'loggedIn.selectedProject.dataSuccess' @@ -187,18 +189,19 @@ function BranchSelector() { const { selectedProject, selectedBranch, setSelectedBranch } = useSnapshot(projectsStore) + const projectPaused = isProjectPaused(selectedProject) const hasBranches = selectedProject?.is_branch_enabled ?? false const { data, isPending, isError } = useBranchesQuery( { projectRef: selectedProject?.ref }, - { enabled: isLoggedIn && hasBranches } + { enabled: isLoggedIn && !projectPaused && hasBranches } ) const stateSummary: BranchesDataState = userLoading ? 'userLoading' : !isLoggedIn ? 'loggedOut' - : !hasBranches + : !hasBranches || projectPaused ? 'loggedIn.noBranches' : isPending ? 'loggedIn.branches.dataPending' @@ -265,22 +268,36 @@ function VariableView({ variable, className }: { variable: Variable; className?: const isLoggedIn = useIsLoggedIn() const { selectedProject, selectedBranch } = useSnapshot(projectsStore) - + const projectPaused = isProjectPaused(selectedProject) const hasBranches = selectedProject?.is_branch_enabled ?? false const ref = hasBranches ? selectedBranch?.project_ref : selectedProject?.ref + const needsApiQuery = variable === 'anonKey' || variable === 'url' + const needsSupavisorQuery = variable === 'sessionPooler' + const { data: apiData, - isPending, - isError, + isPending: isApiPending, + isError: isApiError, } = useProjectApiQuery( { projectRef: ref, }, - { enabled: isLoggedIn && !!ref } + { enabled: isLoggedIn && !!ref && !projectPaused && needsApiQuery } ) - function isInvalid(apiData: ProjectApiData) { + const { + data: supavisorConfig, + isPending: isSupavisorPending, + isError: isSupavisorError, + } = useSupavisorConfigQuery( + { + projectRef: ref, + }, + { enabled: isLoggedIn && !!ref && !projectPaused && needsSupavisorQuery } + ) + + function isInvalidApiData(apiData: ProjectApiData) { switch (variable) { case 'url': return !apiData.app_config?.endpoint @@ -289,17 +306,27 @@ function VariableView({ variable, className }: { variable: Variable; className?: } } + function isInvalidSupavisorData(supavisorData: SupavisorConfigData) { + return supavisorData.length === 0 + } + const stateSummary: VariableDataState = isUserLoading ? 'userLoading' : !isLoggedIn ? 'loggedOut' : !ref ? 'loggedIn.noSelectedProject' - : isPending - ? 'loggedIn.selectedProject.dataPending' - : isError || isInvalid(apiData) - ? 'loggedIn.selectedProject.dataError' - : 'loggedIn.selectedProject.dataSuccess' + : projectPaused + ? 'loggedIn.selectedProject.projectPaused' + : (needsApiQuery ? isApiPending : isSupavisorPending) + ? 'loggedIn.selectedProject.dataPending' + : ( + needsApiQuery + ? isApiError || isInvalidApiData(apiData) + : isSupavisorError || isInvalidSupavisorData(supavisorConfig) + ) + ? 'loggedIn.selectedProject.dataError' + : 'loggedIn.selectedProject.dataSuccess' let variableValue: string = null if (stateSummary === 'loggedIn.selectedProject.dataSuccess') { @@ -310,6 +337,8 @@ function VariableView({ variable, className }: { variable: Variable; className?: case 'anonKey': variableValue = apiData.service_api_keys!.find((key) => key.tags === 'anon')!.api_key break + case 'sessionPooler': + variableValue = supavisorConfig[0].connection_string } } @@ -326,9 +355,11 @@ function VariableView({ variable, className }: { variable: Variable; className?: stateSummary === 'userLoading' || stateSummary === 'loggedIn.selectedProject.dataPending' ? 'Loading...' - : stateSummary === 'loggedIn.selectedProject.dataSuccess' - ? variableValue - : `YOUR ${prettyFormatVariable[variable].toUpperCase()}` + : stateSummary === 'loggedIn.selectedProject.projectPaused' + ? 'PROJECT PAUSED' + : stateSummary === 'loggedIn.selectedProject.dataSuccess' + ? variableValue + : `YOUR ${prettyFormatVariable[variable].toUpperCase()}` } /> diff --git a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts index 08ed725f754..1d1a125e3dd 100644 --- a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts +++ b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts @@ -6,7 +6,7 @@ export type Org = OrganizationsData[number] export type Project = ProjectsData[number] export type Branch = BranchesData[number] -export type Variable = 'url' | 'anonKey' +export type Variable = 'url' | 'anonKey' | 'sessionPooler' function removeDoubleQuotes(str: string) { return str.replaceAll('"', '') @@ -31,6 +31,7 @@ function unescapeDoubleQuotes(str: string) { export const prettyFormatVariable: Record = { url: 'Project URL', anonKey: 'Anon key', + sessionPooler: 'Connection string (pooler session mode)', } type DeepReadonly = { diff --git a/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx b/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx index 7a364eb7166..16e2d9725e8 100644 --- a/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx +++ b/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx @@ -68,7 +68,7 @@ Below is information about your project's SAML 2.0 configuration which you can s | SLO URL | `https://.supabase.co/auth/v1/sso/slo` | | `NameID` | Required `emailAddress` or `persistent` | -Note that SLO (Single Logout) is not supported at this time with Supabase Auth as it is a rarely supported feature by identity providers. However, the URL is registered and advertised for when this does become available. +Note that SLO (Single Logout) is not supported at this time with Supabase Auth as it is a rarely supported feature by identity providers. However, the URL is registered and advertised for when this does become available. SLO is a best-effort service, so we recommend considering [Session Timebox or Session Inactivity Timeout](/docs/guides/auth/sessions#limiting-session-lifetime-and-number-of-allowed-sessions-per-user) instead to force your end-users to authenticate regularly. Append `?download=true` to the Metadata URL to download the Metadata XML file. This is useful in cases where the identity provider requires a file. diff --git a/apps/docs/content/guides/auth/third-party/clerk.mdx b/apps/docs/content/guides/auth/third-party/clerk.mdx new file mode 100644 index 00000000000..3a422696986 --- /dev/null +++ b/apps/docs/content/guides/auth/third-party/clerk.mdx @@ -0,0 +1,143 @@ +--- +id: 'auth-third-party-clerk' +title: 'Clerk' +subtitle: 'Use Clerk with your Supabase project' +--- + +Clerk can be used as a third-party authentication provider alongside Supabase Auth, or standalone, with your Supabase project. + +## Getting started + +Getting started is incredibly easy. Start off by visiting [Clerk's Connect with Supabase page](https://dashboard.clerk.com/setup/supabase) to configure your Clerk instance for Supabase compatibility. + +Finally add a [new Third-Party Auth integration with Clerk](/dashboard/project/_/auth/third-party) in the Supabase dashboard. + +### Configure for local development or self-hosting + +When developing locally or self-hosting with the Supabase CLI, add the following config to your `supabase/config.toml` file: + +```toml +[auth.third_party.clerk] +enabled = true +domain = "example.clerk.accounts.dev" +``` + +You will still need to configure your Clerk instance for Supabase compatibility. + +### Manually configuring your Clerk instance + +If you are not able to use [Clerk's Connect with Supabase page](https://dashboard.clerk.com/setup/supabase) to configure your Clerk instance for working with Supabase, follow these steps. + +1. Add the `role` claim to [Clerk session tokens](https://clerk.com/docs/backend-requests/resources/session-tokens) by [customizing them](https://clerk.com/docs/backend-requests/custom-session-token). End-users who are authenticated should have the `authenticated` value for the claim. If you have an advanced Postgres setup where authenticated end-users use different Postgres roles to access the database, adjust the value to use the correct role name. +2. Once all Clerk session tokens for your instance contain the `role` claim, add a [new Third-Party Auth integration with Clerk](/dashboard/project/_/auth/third-party) in the Supabase dashboard or register it in the CLI as instructed above. + +## Setup the Supabase client library + + + + + +```typescript +import { createClient } from '@supabase/supabase-js' + +const supabase = createClient('https://.supabase.co', 'SUPABASE_ANON_KEY', { + accessToken: () => { + return Clerk.session?.getToken() + }, +}) +``` + + + + + +```dart +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +... + +await Supabase.initialize( + url: 'SUPABASE_URL', + anonKey: 'SUPABASE_ANON_KEY', + accessToken: () async { + final token = await ClerkAuth.of(context).sessionToken(); + return token.jwt; + }, +); +``` + + + + + +```swift +import Clerk +import Supabase + +let supabase = SupabaseClient( + supabaseURL: URL(string: "https://project-ref.supabase.io")!, + supabaseKey: "supabase.anon.key", + options: SupabaseClientOptions( + auth: SupabaseClientOptions.AuthOptions( + accessToken: { + try await Clerk.shared.session?.getToken()?.jwt + } + ) + ) +) +``` + + + + + +## Using RLS policies + +Once you've configured the Supabase client library to use Clerk session tokens, you can use RLS policies to secure access to your project's database, Storage objects and Realtime channels. + +The recommended way to design RLS policies with Clerk is to use claims present in your Clerk session token to allow or reject access to your project's data. Check [Clerk's docs](https://clerk.com/docs/backend-requests/resources/session-tokens) on the available JWT claims and their values. + +### Example: Check user organization role + +```sql +create policy "Only organization admins can insert in table" + on table_name + for insert + to authenticated + with check ( + ((select auth.jwt()->>'org_role') = 'org:admin') + and + (organization_id = (select auth.jwt()->>'org_id')) + ); +``` + +This RLS policy checks that the newly inserted row in the table has the user's declared organization ID (from the `org_id` session token claim) in the `organization_id` column. Additionally it ensures that they're an `org:admin`. + +This way only organization admins can add rows to the table, for organizations they're a member of. + +### Example: Check user has passed second factor verification + +```sql +create policy "Only users that have passed second factor verification can read from table" + on table_name + for select + as restrictive + to authenticated + using ( + ((select auth.jwt()->'fva'->>1) != '-1') + ); +``` + +This example uses a restrictive RLS policy checks that the [second factor verification](https://clerk.com/docs/guides/reverification) age element in the `fva` claim is not `'-1'` indicating the user has passed through second factor verification. + +## Deprecated integration with JWT templates + +As of 1st April 2025 the previously available [Clerk Integration with Supabase](https://supabase.com/partners/integrations/clerk) is considered deprecated and is no longer recommended for use. All projects using the deprecated integration will be excluded from Third-Party Monthly Active User (TP-MAU) charges until at least 1st January 2026. + +This integration used low-level primitives that are still available in Supabase and Clerk, such as a [configurable JWT secret](/dashboard/project/_/settings/api) and [JWT templates from Clerk](https://clerk.com/docs/backend-requests/jwt-templates). This enables you to keep using it in an unofficial manner, though only limited support will be provided from Supabase. + +Deprecation is done for the following reasons: + +- Sharing your project's JWT secret with a third-party is a problematic security practice +- Rotating the project's JWT secret in this case almost always results in significant downtime for your application +- Additional latency to [generate a new JWT](https://clerk.com/docs/backend-requests/jwt-templates#generate-a-jwt) for use with Supabase, instead of using the Clerk [session tokens](https://clerk.com/docs/backend-requests/resources/session-tokens) diff --git a/apps/docs/content/guides/auth/third-party/firebase-auth.mdx b/apps/docs/content/guides/auth/third-party/firebase-auth.mdx index b4af5e48ebc..6cc9b93f214 100644 --- a/apps/docs/content/guides/auth/third-party/firebase-auth.mdx +++ b/apps/docs/content/guides/auth/third-party/firebase-auth.mdx @@ -27,7 +27,7 @@ import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://.supabase.co', 'SUPABASE_ANON_KEY', { accessToken: async () => { - return await firebase.auth().currentUser?.getIdToken(/* forceRefresh */ false)) ?? null + return (await firebase.auth().currentUser?.getIdToken(/* forceRefresh */ false)) ?? null }, }) ``` diff --git a/apps/docs/content/guides/auth/third-party/overview.mdx b/apps/docs/content/guides/auth/third-party/overview.mdx index ff0be76c689..447f9f79f78 100644 --- a/apps/docs/content/guides/auth/third-party/overview.mdx +++ b/apps/docs/content/guides/auth/third-party/overview.mdx @@ -6,6 +6,7 @@ subtitle: 'First-class support for authentication providers' Supabase has first-class support for these third-party authentication providers: +- [Clerk](/docs/guides/auth/third-party/clerk) - [Firebase Auth](/docs/guides/auth/third-party/firebase-auth) - [Auth0](/docs/guides/auth/third-party/auth0) - [AWS Cognito (with or without AWS Amplify)](/docs/guides/auth/third-party/aws-cognito) diff --git a/apps/docs/content/guides/deployment/going-into-prod.mdx b/apps/docs/content/guides/deployment/going-into-prod.mdx index 65b85e9fa17..8d36e73e709 100644 --- a/apps/docs/content/guides/deployment/going-into-prod.mdx +++ b/apps/docs/content/guides/deployment/going-into-prod.mdx @@ -50,7 +50,7 @@ After developing your project and deciding it's Production Ready, you should run - Use your own SMTP credentials so that you have full control over the deliverability of your transactional auth emails (see Settings > Auth) - you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc. You can refer to our [SMTP guide](/docs/guides/auth/auth-smtp) for more details. - The default rate limit for auth emails when using a custom SMTP provider is 30 new users per hour, if doing a major public announcement you will likely require more than this. -- If your application is on the Free Plan and is **not** expected to be queried at least once every 7 days, then it may be paused by Supabase to save on server resources. +- Applications on the Free Plan that exhibit extremely low activity in a 7 day period may be paused by Supabase to save on server resources. - You can restore paused projects from the Supabase dashboard. - Upgrade to Pro to guarantee that your project will not be paused for inactivity. - Database backups are not available for download on the Free Plan. diff --git a/apps/docs/content/guides/getting-started/mcp.mdx b/apps/docs/content/guides/getting-started/mcp.mdx index 9d9987297ac..e6f8ade1e38 100644 --- a/apps/docs/content/guides/getting-started/mcp.mdx +++ b/apps/docs/content/guides/getting-started/mcp.mdx @@ -27,11 +27,19 @@ To get started, you will need to retrieve your database connection string. These #### For a hosted Supabase instance -When running a hosted instance of Supabase, you can find your connection string by: + + +Log in and choose a project above to find your connection string, or find it in the Dashboard: 1. Navigating to your project's [Connection settings](/dashboard/project/_/settings/database?showConnect=true) 1. Copying the connection string found under **Session pooler**. + + +Be sure to replace `[YOUR-PASSWORD]` with your database password. + + + #### For a local Supabase instance When running a local instance of Supabase via the [CLI](/docs/reference/cli/introduction), you can find your connection string by running: diff --git a/apps/docs/content/guides/local-development/declarative-database-schemas.mdx b/apps/docs/content/guides/local-development/declarative-database-schemas.mdx index 021359f1b5a..663704b7b5d 100644 --- a/apps/docs/content/guides/local-development/declarative-database-schemas.mdx +++ b/apps/docs/content/guides/local-development/declarative-database-schemas.mdx @@ -236,6 +236,24 @@ supabase db dump > supabase/schemas/prod.sql From there, you can start breaking down your schema into smaller files and generate migrations. You can do this all at once, or incrementally as you make changes to your schema. +### Rolling back a schema change + +During development, you may want to rollback a migration to keep your new schema changes in a single migration file. This can be done by resetting your local database to a previous version. + +```bash Terminal +supabase db reset --version 20241005112233 +``` + +After a reset, you can [edit the schema](#updating-your-schema) and regenerate a new migration file. Note that you should not reset a version that's already deployed to production. + +If you need to rollback a migration that's already deployed, you should first revert changes to the schema files. Then you can generate a new migration file containing the down migration. This ensures your production migrations are always rolling forward. + + + +SQL statements generated in a down migration are usually destructive. You must review them carefully to avoid unintentional data loss. + + + ## Known caveats The `migra` diff tool used for generating schema diff is capable of tracking most database changes. However, there are edge cases where it can fail. diff --git a/apps/docs/lib/fetch/pooler.ts b/apps/docs/lib/fetch/pooler.ts new file mode 100644 index 00000000000..2d708ca6442 --- /dev/null +++ b/apps/docs/lib/fetch/pooler.ts @@ -0,0 +1,42 @@ +import { type UseQueryOptions, useQuery } from '@tanstack/react-query' + +import type { ResponseError } from '~/types/fetch' +import { get } from './fetchWrappers' + +const poolerKeys = { + supavisorConfig: (projectRef: string | undefined) => ['supavisor', 'config', projectRef] as const, +} + +export interface SupavisorConfigVariables { + projectRef?: string +} + +async function getSupavisorConfig({ projectRef }: SupavisorConfigVariables, signal?: AbortSignal) { + if (!projectRef) throw Error('projectRef is required') + + const { data, error } = await get(`/platform/projects/{ref}/config/supavisor`, { + params: { path: { ref: projectRef } }, + signal, + }) + if (error) throw error + + return data +} + +export type SupavisorConfigData = Awaited> +type SupavisorConfigError = ResponseError + +export function useSupavisorConfigQuery( + { projectRef }: SupavisorConfigVariables, + { + enabled = true, + ...options + }: Omit, 'queryKey'> +) { + return useQuery({ + queryKey: poolerKeys.supavisorConfig(projectRef), + queryFn: ({ signal }) => getSupavisorConfig({ projectRef }, signal), + enabled, + ...options, + }) +} diff --git a/apps/docs/lib/fetch/projects.ts b/apps/docs/lib/fetch/projects.ts index a4ca1a5872d..de4771ee14f 100644 --- a/apps/docs/lib/fetch/projects.ts +++ b/apps/docs/lib/fetch/projects.ts @@ -1,6 +1,7 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' import type { ResponseError } from '~/types/fetch' import { get } from './fetchWrappers' +import { type ReadonlyRecursive } from '~/types/utils' const projectKeys = { list: () => ['all-projects'] as const, @@ -26,3 +27,9 @@ export function useProjectsQuery({ ...options, }) } + +export function isProjectPaused( + project: ReadonlyRecursive | null +): boolean | undefined { + return !project ? undefined : project.status === 'INACTIVE' +} diff --git a/apps/docs/types/utils.ts b/apps/docs/types/utils.ts new file mode 100644 index 00000000000..909ccea7bff --- /dev/null +++ b/apps/docs/types/utils.ts @@ -0,0 +1,3 @@ +export type ReadonlyRecursive = { + readonly [P in keyof T]: ReadonlyRecursive +} diff --git a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AddIntegrationDropdown.tsx b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AddIntegrationDropdown.tsx index 35d7b81d707..39585f539d1 100644 --- a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AddIntegrationDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/AddIntegrationDropdown.tsx @@ -1,8 +1,6 @@ import { ChevronDown } from 'lucide-react' import Image from 'next/image' -import { useSelectedProject } from 'hooks/misc/useSelectedProject' -import { useFlag } from 'hooks/ui/useFlag' import { Button, cn, @@ -49,17 +47,6 @@ const ProviderDropdownItem = ({ export const AddIntegrationDropdown = ({ onSelectIntegrationType, }: AddIntegrationDropdownProps) => { - const selectedProject = useSelectedProject() - - const isClerkTPAEnabledFlag = useFlag('isClerkTPAEnabledOnProjects') - const isClerkTPAEnabled = - selectedProject?.ref && - isClerkTPAEnabledFlag && - isClerkTPAEnabledFlag - .split(',') - .map((it) => it.trim()) - .includes(selectedProject.ref) - return ( @@ -72,10 +59,7 @@ export const AddIntegrationDropdown = ({ - - {isClerkTPAEnabled && ( - - )} + diff --git a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/CreateClerkAuthDialog.tsx b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/CreateClerkAuthDialog.tsx index 54ceb48dad1..84f36debcc1 100644 --- a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/CreateClerkAuthDialog.tsx +++ b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/CreateClerkAuthDialog.tsx @@ -106,8 +106,11 @@ export const CreateClerkAuthIntegrationDialog = ({

- This will enable a Clerk session token (JWT) from a specific access data from this - Supabase project.{' '} + Register your Clerk domain. Visit{' '} + + Clerk's Connect with Supabase page + {' '} + to configure your Clerk instance.

diff --git a/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx index 21ca6157310..d6d5b162be5 100644 --- a/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx @@ -340,7 +340,7 @@ export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => {
, text: 'Remove MFA factors', @@ -409,12 +409,13 @@ export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => { onConfirm={() => handleDeleteFactors()} alert={{ base: { variant: 'warning' }, - title: 'Removing MFA factors is irreversible', - description: 'This will log the user out of all active sessions.', + title: + "Removing MFA factors will drop the user's authentication assurance level (AAL) to AAL1", + description: 'Note that this does not sign the user out', }} >

- This is permanent! Are you sure you want to remove the MFA factors for the user{' '} + Are you sure you want to remove the MFA factors for the user{' '} {user.email ?? user.phone ?? 'this user'}?

diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts b/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts index 162ee824607..bef3303bfca 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts @@ -104,8 +104,8 @@ export const calculateIOPSPrice = ({ 0, newProvisionedIOPS - DISK_LIMITS[DiskType.GP3].includedIops ) - const oldPrice = oldChargeableIOPS * DISK_PRICING[oldStorageType]?.iops ?? 0 - const newPrice = newChargeableIOPS * DISK_PRICING[newStorageType]?.iops ?? 0 + const oldPrice = oldChargeableIOPS * (DISK_PRICING[oldStorageType]?.iops ?? 0) + const newPrice = newChargeableIOPS * (DISK_PRICING[newStorageType]?.iops ?? 0) return { oldPrice: (oldPrice * (1 + numReplicas)).toFixed(2), @@ -116,8 +116,8 @@ export const calculateIOPSPrice = ({ oldStorageType === 'gp3' ? (oldProvisionedIOPS - DISK_LIMITS[oldStorageType].includedIops) * DISK_PRICING[oldStorageType].iops - : oldProvisionedIOPS * DISK_PRICING[oldStorageType]?.iops ?? 0 - const newPrice = newProvisionedIOPS * DISK_PRICING[newStorageType]?.iops ?? 0 + : oldProvisionedIOPS * (DISK_PRICING[oldStorageType]?.iops ?? 0) + const newPrice = newProvisionedIOPS * (DISK_PRICING[newStorageType]?.iops ?? 0) return { oldPrice: (oldPrice * (1 + numReplicas)).toFixed(2), newPrice: (newPrice * (1 + numReplicas)).toFixed(2), diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index 1de204394bd..c76965a4e80 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -242,7 +242,10 @@ export function DiskManagementForm() { ) { willUpdateDiskConfiguration = true - if (RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(payload.computeSize)) { + if ( + payload.storageType === 'gp3' && + RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(payload.computeSize) + ) { payload.provisionedIOPS = IOPS_RANGE[DiskType.GP3].min } diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx index f5681a51bba..3b36909419e 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx @@ -382,6 +382,12 @@ export const DiskManagementReviewAndSubmitDialog = ({ + {numReplicas > 0 && ( +
+ {replicaTooltipText} +
+ )} + + + +
+

Download via CLI

+ +
+ +
+ +
+
+ {edgeFunctionCreate && !!functionSlug && ( -
)} + + {isSuccessLoadingFiles && ( + <> + +
+ + ) : ( +
+ +
+ ) + } + tooltip={{ + content: { + side: 'top', + text: !canDeployFunction + ? 'You need additional permissions to update edge functions' + : undefined, + }, + }} + > + Deploy updates +
+
+ + )} + + setShowDeployWarning(false)} + onConfirm={handleDeployConfirm} + /> ) } diff --git a/apps/ui-library/__registry__/index.tsx b/apps/ui-library/__registry__/index.tsx index 7f59bed5d7b..84127d56e42 100644 --- a/apps/ui-library/__registry__/index.tsx +++ b/apps/ui-library/__registry__/index.tsx @@ -11,9 +11,9 @@ export const Index: Record = { name: "password-based-auth-nextjs", type: "registry:block", registryDependencies: ["button","card","input","label"], - component: React.lazy(() => import("@/registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx")), + component: React.lazy(() => import("@/registry/default/blocks/password-based-auth-nextjs/app/auth/login/page.tsx")), source: "", - files: ["registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/confirm/route.ts","registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx","registry/default/blocks/password-based-auth-nextjs/middleware.ts","registry/default/blocks/password-based-auth-nextjs/app/sign-up/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx","registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx","registry/default/blocks/password-based-auth-nextjs/app/forgot-password/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/update-password/page.tsx","registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx","registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx","registry/default/clients/nextjs/lib/supabase/client.ts","registry/default/clients/nextjs/lib/supabase/middleware.ts","registry/default/clients/nextjs/lib/supabase/server.ts"], + files: ["registry/default/blocks/password-based-auth-nextjs/app/auth/login/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/auth/error/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/auth/confirm/route.ts","registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx","registry/default/blocks/password-based-auth-nextjs/middleware.ts","registry/default/blocks/password-based-auth-nextjs/app/auth/sign-up/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/auth/sign-up-success/page.tsx","registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx","registry/default/blocks/password-based-auth-nextjs/app/auth/forgot-password/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/auth/update-password/page.tsx","registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx","registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx","registry/default/blocks/password-based-auth-nextjs/components/logout-button.tsx","registry/default/clients/nextjs/lib/supabase/client.ts","registry/default/clients/nextjs/lib/supabase/middleware.ts","registry/default/clients/nextjs/lib/supabase/server.ts"], category: "undefined", subcategory: "undefined", chunks: [] @@ -58,7 +58,7 @@ export const Index: Record = { "dropzone-nextjs": { name: "dropzone-nextjs", type: "registry:component", - registryDependencies: ["button","tooltip","progress"], + registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/blocks/dropzone/components/dropzone.tsx")), source: "", files: ["registry/default/blocks/dropzone/components/dropzone.tsx","registry/default/blocks/dropzone/hooks/use-supabase-upload.ts","registry/default/clients/nextjs/lib/supabase/client.ts","registry/default/clients/nextjs/lib/supabase/middleware.ts","registry/default/clients/nextjs/lib/supabase/server.ts"], @@ -70,7 +70,7 @@ export const Index: Record = { "dropzone-react": { name: "dropzone-react", type: "registry:component", - registryDependencies: ["button","tooltip","progress"], + registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/blocks/dropzone/components/dropzone.tsx")), source: "", files: ["registry/default/blocks/dropzone/components/dropzone.tsx","registry/default/blocks/dropzone/hooks/use-supabase-upload.ts","registry/default/clients/react/lib/supabase/client.ts"], @@ -82,7 +82,7 @@ export const Index: Record = { "dropzone-react-router": { name: "dropzone-react-router", type: "registry:component", - registryDependencies: ["button","tooltip","progress"], + registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/blocks/dropzone/components/dropzone.tsx")), source: "", files: ["registry/default/blocks/dropzone/components/dropzone.tsx","registry/default/blocks/dropzone/hooks/use-supabase-upload.ts","registry/default/clients/react-router/lib/supabase/client.ts","registry/default/clients/react-router/lib/supabase/server.ts"], @@ -94,7 +94,7 @@ export const Index: Record = { "dropzone-tanstack": { name: "dropzone-tanstack", type: "registry:component", - registryDependencies: ["button","tooltip","progress"], + registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/blocks/dropzone/components/dropzone.tsx")), source: "", files: ["registry/default/blocks/dropzone/components/dropzone.tsx","registry/default/blocks/dropzone/hooks/use-supabase-upload.ts","registry/default/clients/tanstack/lib/supabase/client.ts","registry/default/clients/tanstack/lib/supabase/server.ts"], @@ -247,6 +247,54 @@ export const Index: Record = { chunks: [] } , + "realtime-chat-nextjs": { + name: "realtime-chat-nextjs", + type: "registry:component", + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/default/blocks/realtime-chat/components/chat-message.tsx")), + source: "", + files: ["registry/default/blocks/realtime-chat/components/chat-message.tsx","registry/default/blocks/realtime-chat/components/realtime-chat.tsx","registry/default/blocks/realtime-chat/hooks/use-realtime-chat.tsx","registry/default/blocks/realtime-chat/hooks/use-chat-scroll.tsx","registry/default/clients/nextjs/lib/supabase/client.ts","registry/default/clients/nextjs/lib/supabase/middleware.ts","registry/default/clients/nextjs/lib/supabase/server.ts"], + category: "undefined", + subcategory: "undefined", + chunks: [] + } + , + "realtime-chat-react": { + name: "realtime-chat-react", + type: "registry:component", + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/default/blocks/realtime-chat/components/chat-message.tsx")), + source: "", + files: ["registry/default/blocks/realtime-chat/components/chat-message.tsx","registry/default/blocks/realtime-chat/components/realtime-chat.tsx","registry/default/blocks/realtime-chat/hooks/use-realtime-chat.tsx","registry/default/blocks/realtime-chat/hooks/use-chat-scroll.tsx","registry/default/clients/react/lib/supabase/client.ts"], + category: "undefined", + subcategory: "undefined", + chunks: [] + } + , + "realtime-chat-react-router": { + name: "realtime-chat-react-router", + type: "registry:component", + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/default/blocks/realtime-chat/components/chat-message.tsx")), + source: "", + files: ["registry/default/blocks/realtime-chat/components/chat-message.tsx","registry/default/blocks/realtime-chat/components/realtime-chat.tsx","registry/default/blocks/realtime-chat/hooks/use-realtime-chat.tsx","registry/default/blocks/realtime-chat/hooks/use-chat-scroll.tsx","registry/default/clients/react-router/lib/supabase/client.ts","registry/default/clients/react-router/lib/supabase/server.ts"], + category: "undefined", + subcategory: "undefined", + chunks: [] + } + , + "realtime-chat-tanstack": { + name: "realtime-chat-tanstack", + type: "registry:component", + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/default/blocks/realtime-chat/components/chat-message.tsx")), + source: "", + files: ["registry/default/blocks/realtime-chat/components/chat-message.tsx","registry/default/blocks/realtime-chat/components/realtime-chat.tsx","registry/default/blocks/realtime-chat/hooks/use-realtime-chat.tsx","registry/default/blocks/realtime-chat/hooks/use-chat-scroll.tsx","registry/default/clients/tanstack/lib/supabase/client.ts","registry/default/clients/tanstack/lib/supabase/server.ts"], + category: "undefined", + subcategory: "undefined", + chunks: [] + } + , "supabase-client-nextjs": { name: "supabase-client-nextjs", type: "registry:lib", diff --git a/apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx b/apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx index 500ffedd0c6..2c495f5cdda 100644 --- a/apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx +++ b/apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx @@ -81,18 +81,18 @@ export default async function DocPage({ params }: DocPageProps) { const toc = await getTableOfContents(doc.body.raw) return ( -
+
Docs
{doc.title}
-
+
-

{doc.title}

+

{doc.title}

{doc.description && ( -

+

{doc.description}

)} diff --git a/apps/ui-library/app/(app)/layout.tsx b/apps/ui-library/app/(app)/layout.tsx index fcb11fde122..32526719839 100644 --- a/apps/ui-library/app/(app)/layout.tsx +++ b/apps/ui-library/app/(app)/layout.tsx @@ -1,8 +1,5 @@ -import SideNavigation from '@/components/side-navigation' +import { Sidebar } from '@/components/sidebar' import { SiteFooter } from '@/components/site-footer' -// import ThemeSettings from '@/components/theme-settings' -import TopNavigation from '@/components/top-navigation' -import { ScrollArea } from 'ui' interface AppLayoutProps { children: React.ReactNode @@ -11,19 +8,14 @@ interface AppLayoutProps { export default function AppLayout({ children }: AppLayoutProps) { return ( <> - {/* */} {/* main container */} -
+
{/* main content */} -
+
{/* {children} */}
-
- +
+
{children}
diff --git a/apps/ui-library/app/(app)/page.tsx b/apps/ui-library/app/(app)/page.tsx index e4d67ef7e2c..bc5852aa173 100644 --- a/apps/ui-library/app/(app)/page.tsx +++ b/apps/ui-library/app/(app)/page.tsx @@ -1,7 +1,7 @@ import { BlockPreview } from '@/components/block-preview' import { ComponentPreview } from '@/components/component-preview' -import { Button } from '@/registry/default/components/ui/button' import Link from 'next/link' +import { Button_Shadcn_ } from 'ui' // Horizontal grid line component const HorizontalGridLine = () =>
@@ -33,19 +33,18 @@ export default function Home() { data-grid-item className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-8 pb-8" > -
+

UI Blocks for Supabase Projects

- A set of beautifully-designed, accessible components that connect your - Supabase back-end to your front-end. Works with your favorite frameworks. Open - Source. Open Code. + A collection of components and blocks built on the shadcn/ui library that + connect your front-end to your Supabase back-end via a single command.

- +
@@ -63,7 +62,7 @@ export default function Home() { className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative" >
- +
@@ -81,8 +80,8 @@ export default function Home() { className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative" >
- - + +
@@ -140,6 +139,24 @@ export default function Home() {
+ + {/* Realtime Chat */} +
+ Realtime Chat +
+ +
+
+ + +
+
diff --git a/apps/ui-library/app/Providers.tsx b/apps/ui-library/app/Providers.tsx index c10426f161d..97e0ebdbc72 100644 --- a/apps/ui-library/app/Providers.tsx +++ b/apps/ui-library/app/Providers.tsx @@ -6,6 +6,7 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes' import { ThemeProviderProps } from 'next-themes/dist/types' import { FrameworkProvider } from '@/context/framework-context' +import { MobileMenuProvider } from '@/context/mobile-menu-context' import { TooltipProvider } from 'ui' export function ThemeProvider({ children, ...props }: ThemeProviderProps) { @@ -13,9 +14,11 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) { - - {children} - + + + {children} + + diff --git a/apps/ui-library/app/example/base-injector.tsx b/apps/ui-library/app/example/base-injector.tsx index 5d8941bfafd..ae29b2484c1 100644 --- a/apps/ui-library/app/example/base-injector.tsx +++ b/apps/ui-library/app/example/base-injector.tsx @@ -13,6 +13,12 @@ export function BaseInjector() { base.href = `${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/example/password-based-auth/` document.head.prepend(base) + // Add preview class to body to use shadcn theme vars + const bodyElement = document.querySelector('body') + if (bodyElement) { + bodyElement.classList.add('preview') + } + return () => { base.remove() } diff --git a/apps/ui-library/app/example/password-based-auth/forgot-password/page.tsx b/apps/ui-library/app/example/password-based-auth/auth/forgot-password/page.tsx similarity index 100% rename from apps/ui-library/app/example/password-based-auth/forgot-password/page.tsx rename to apps/ui-library/app/example/password-based-auth/auth/forgot-password/page.tsx diff --git a/apps/ui-library/app/example/password-based-auth/login/page.tsx b/apps/ui-library/app/example/password-based-auth/auth/login/page.tsx similarity index 100% rename from apps/ui-library/app/example/password-based-auth/login/page.tsx rename to apps/ui-library/app/example/password-based-auth/auth/login/page.tsx diff --git a/apps/ui-library/app/example/password-based-auth/sign-up-success/page.tsx b/apps/ui-library/app/example/password-based-auth/auth/sign-up-success/page.tsx similarity index 100% rename from apps/ui-library/app/example/password-based-auth/sign-up-success/page.tsx rename to apps/ui-library/app/example/password-based-auth/auth/sign-up-success/page.tsx diff --git a/apps/ui-library/app/example/password-based-auth/sign-up/page.tsx b/apps/ui-library/app/example/password-based-auth/auth/sign-up/page.tsx similarity index 100% rename from apps/ui-library/app/example/password-based-auth/sign-up/page.tsx rename to apps/ui-library/app/example/password-based-auth/auth/sign-up/page.tsx diff --git a/apps/ui-library/app/example/password-based-auth/update-password/page.tsx b/apps/ui-library/app/example/password-based-auth/auth/update-password/page.tsx similarity index 100% rename from apps/ui-library/app/example/password-based-auth/update-password/page.tsx rename to apps/ui-library/app/example/password-based-auth/auth/update-password/page.tsx diff --git a/apps/ui-library/app/example/password-based-auth/protected/SignOutButton.tsx b/apps/ui-library/app/example/password-based-auth/protected/SignOutButton.tsx deleted file mode 100644 index 50947760f00..00000000000 --- a/apps/ui-library/app/example/password-based-auth/protected/SignOutButton.tsx +++ /dev/null @@ -1,22 +0,0 @@ -'use client' - -import { Button } from '@/registry/default/components/ui/button' -import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' -import { useRouter } from 'next/navigation' - -export function SignOutButton() { - const supabase = createClient() - const router = useRouter() - - return ( - - ) -} diff --git a/apps/ui-library/app/example/password-based-auth/protected/page.tsx b/apps/ui-library/app/example/password-based-auth/protected/page.tsx index f610f6151c0..204123552db 100644 --- a/apps/ui-library/app/example/password-based-auth/protected/page.tsx +++ b/apps/ui-library/app/example/password-based-auth/protected/page.tsx @@ -1,20 +1,19 @@ -import { redirect } from 'next/navigation' - +import { LogoutButton } from '@/registry/default/blocks/password-based-auth-nextjs/components/logout-button' import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/server' -import { SignOutButton } from './SignOutButton' +import { redirect } from 'next/navigation' export default async function ProtectedPage() { const supabase = await createClient() const { data, error } = await supabase.auth.getUser() if (error || !data?.user) { - redirect('/login') + redirect('/example/password-based-auth/auth/login') } return ( <>

Hello {data.user.email}

- + ) } diff --git a/apps/ui-library/app/example/realtime-avatar-stack-demo/layout.tsx b/apps/ui-library/app/example/realtime-avatar-stack-demo/layout.tsx new file mode 100644 index 00000000000..2e352bd3d6a --- /dev/null +++ b/apps/ui-library/app/example/realtime-avatar-stack-demo/layout.tsx @@ -0,0 +1,19 @@ +import { Metadata } from 'next' +import { BaseInjector } from './../base-injector' + +export const metadata: Metadata = { + title: 'Realtime Avatar Stack Example', +} + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + <> + +
+
+ {children} +
+
+ + ) +} diff --git a/apps/ui-library/app/example/realtime-avatar-stack-demo/page.tsx b/apps/ui-library/app/example/realtime-avatar-stack-demo/page.tsx new file mode 100644 index 00000000000..d17192cb797 --- /dev/null +++ b/apps/ui-library/app/example/realtime-avatar-stack-demo/page.tsx @@ -0,0 +1,9 @@ +'use client' + +import Demo from '@/registry/default/examples/realtime-avatar-stack-demo' + +const RealtimeAvatarStackDemoPage = () => { + return +} + +export default RealtimeAvatarStackDemoPage diff --git a/apps/ui-library/app/example/realtime-chat-demo/layout.tsx b/apps/ui-library/app/example/realtime-chat-demo/layout.tsx new file mode 100644 index 00000000000..f4413161db8 --- /dev/null +++ b/apps/ui-library/app/example/realtime-chat-demo/layout.tsx @@ -0,0 +1,19 @@ +import { Metadata } from 'next' +import { BaseInjector } from './../base-injector' + +export const metadata: Metadata = { + title: 'Realtime Chat Example', +} + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + <> + +
+
+ {children} +
+
+ + ) +} diff --git a/apps/ui-library/app/example/realtime-chat-demo/page.tsx b/apps/ui-library/app/example/realtime-chat-demo/page.tsx new file mode 100644 index 00000000000..778d0692d49 --- /dev/null +++ b/apps/ui-library/app/example/realtime-chat-demo/page.tsx @@ -0,0 +1,9 @@ +'use client' + +import Demo from '@/registry/default/examples/realtime-chat-demo' + +const RealtimeChatDemoPage = () => { + return +} + +export default RealtimeChatDemoPage diff --git a/apps/ui-library/app/example/realtime-cursor/layout.tsx b/apps/ui-library/app/example/realtime-cursor-demo/layout.tsx similarity index 100% rename from apps/ui-library/app/example/realtime-cursor/layout.tsx rename to apps/ui-library/app/example/realtime-cursor-demo/layout.tsx diff --git a/apps/ui-library/app/example/realtime-cursor/page.tsx b/apps/ui-library/app/example/realtime-cursor-demo/page.tsx similarity index 57% rename from apps/ui-library/app/example/realtime-cursor/page.tsx rename to apps/ui-library/app/example/realtime-cursor-demo/page.tsx index c767de6d511..b5f8458073b 100644 --- a/apps/ui-library/app/example/realtime-cursor/page.tsx +++ b/apps/ui-library/app/example/realtime-cursor-demo/page.tsx @@ -2,8 +2,8 @@ import Demo from '@/registry/default/examples/realtime-cursor-demo' -const RealtimeCursorPage = () => { +const RealtimeCursorDemoPage = () => { return } -export default RealtimeCursorPage +export default RealtimeCursorDemoPage diff --git a/apps/ui-library/app/layout.tsx b/apps/ui-library/app/layout.tsx index 63ad05e019f..a03636ea6a5 100644 --- a/apps/ui-library/app/layout.tsx +++ b/apps/ui-library/app/layout.tsx @@ -1,5 +1,6 @@ -import '@/styles/globals.css' import type { Metadata } from 'next' + +import '@/styles/globals.css' import { Inter } from 'next/font/google' import { ThemeProvider } from './Providers' import { SonnerToaster } from './SonnerToast' @@ -26,7 +27,6 @@ export default async function Layout({ children }: RootLayoutProps) { enableSystem > {children} - diff --git a/apps/ui-library/components/block-item-code.tsx b/apps/ui-library/components/block-item-code.tsx index aedd1d4e561..2236f273283 100644 --- a/apps/ui-library/components/block-item-code.tsx +++ b/apps/ui-library/components/block-item-code.tsx @@ -91,8 +91,8 @@ export function BlockItemCode({ files }: BlockItemCodeProps) { isSelected={isSelected} level={level} icon={} - xPadding={16} name={element.name} + className="gap-1.5" /> )} /> diff --git a/apps/ui-library/components/block-item.tsx b/apps/ui-library/components/block-item.tsx index fe6d00963d6..178ca591d50 100644 --- a/apps/ui-library/components/block-item.tsx +++ b/apps/ui-library/components/block-item.tsx @@ -1,6 +1,10 @@ -import { Command } from '@/components/command' import { OpenInV0Button } from '@/components/open-in-v0-button' +import dynamic from 'next/dynamic' + +//The dynamic import is to prevent the command component from being rendered on the server and cause hydration errors +const Command = dynamic(() => import('./command').then((mod) => mod.Command), { ssr: false }) + interface BlockItemProps { name: string } diff --git a/apps/ui-library/components/block-preview.tsx b/apps/ui-library/components/block-preview.tsx index 0524448923a..77e3a9fc251 100644 --- a/apps/ui-library/components/block-preview.tsx +++ b/apps/ui-library/components/block-preview.tsx @@ -19,7 +19,7 @@ export function BlockPreview({ name, wide = false, isPair = false }: ComponentPr } >