diff --git a/apps/docs/app/contributing/content.mdx b/apps/docs/app/contributing/content.mdx index a310714f4a360..9dc50751a977f 100644 --- a/apps/docs/app/contributing/content.mdx +++ b/apps/docs/app/contributing/content.mdx @@ -472,14 +472,6 @@ void main() async { -### Info Tooltip - -The InfoTooltip component is used to add more context to a word or phrase via tooltip. - -```mdx -Supabase -``` - ## Partials We incorporate content reuse in the docs to avoid duplication. If you find yourself writing the same content over and over, you can put it in a partial instead. Here are some examples of commonly used partials: diff --git a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox.tsx b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox.tsx index 6b88f8ae343f8..a88c010ebd394 100644 --- a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox.tsx +++ b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox.tsx @@ -86,6 +86,7 @@ export function ComboBox({ role="combobox" disabled={disabled} aria-expanded={open} + aria-label={`Select your ${name}`} className={cn( 'overflow-hidden', 'h-auto min-h-10', diff --git a/apps/docs/components/RealtimeLimitsEstimator/RealtimeLimitsEstimator.tsx b/apps/docs/components/RealtimeLimitsEstimator/RealtimeLimitsEstimator.tsx index 660566d03f5aa..a1e5e965e526d 100644 --- a/apps/docs/components/RealtimeLimitsEstimator/RealtimeLimitsEstimator.tsx +++ b/apps/docs/components/RealtimeLimitsEstimator/RealtimeLimitsEstimator.tsx @@ -2,7 +2,7 @@ import throughputTable from '~/data/realtime/throughput.json' import { ChevronDown } from 'lucide-react' import { useState } from 'react' import { - Button, + cn, Collapsible, CollapsibleContent, CollapsibleTrigger, @@ -138,22 +138,17 @@ export default function RealtimeLimitsEstimater({}) { )} - -
-

View raw throughput table

-
+ + View raw throughput table +
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 1611efb5a2432..fe8e6a4a866ea 100644 --- a/apps/docs/content/guides/local-development/declarative-database-schemas.mdx +++ b/apps/docs/content/guides/local-development/declarative-database-schemas.mdx @@ -7,7 +7,7 @@ subtitle: 'Manage your database schemas in one place and generate versioned migr ## Overview -Declarative schemas provide a developer-friendly way to maintain

Files of SQL statements that track the evolution of your database schema over time.
They allow you to version control your database schema alongside your application code.

See the database migrations guide to learn more.

}>schema migrations
. +Declarative schemas provide a developer-friendly way to maintain [schema migrations](#schema-migrations). [Migrations](/docs/guides/deployment/database-migrations) are traditionally managed imperatively (you provide the instructions on how exactly to change the database). This can lead to related information being scattered over multiple migration files. With declarative schemas, you instead declare the state you want your database to be in, and the instructions are generated for you. diff --git a/apps/docs/features/docs/MdxBase.shared.tsx b/apps/docs/features/docs/MdxBase.shared.tsx index 355c3f129ab3b..5e18f0134f842 100644 --- a/apps/docs/features/docs/MdxBase.shared.tsx +++ b/apps/docs/features/docs/MdxBase.shared.tsx @@ -27,7 +27,6 @@ import { NamedCodeBlock } from '~/features/directives/CodeTabs.components' import { MdxAnchor } from '~/features/docs/MdxAnchor' import { Accordion, AccordionItem } from '~/features/ui/Accordion' import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock' -import InfoTooltip from '~/features/ui/InfoTooltip' import { ShowUntil } from '~/features/ui/ShowUntil' import { TabPanel, Tabs } from '~/features/ui/Tabs' import { ArrowDown, Check, X } from 'lucide-react' @@ -114,7 +113,6 @@ const components = { TabPanel, TerraformProviderSchema, WrapperDashboardIntegration, - InfoTooltip, a: MdxAnchor, h2: (props: ComponentPropsWithoutRef<'h2'>) => ( diff --git a/apps/docs/features/ui/InfoTooltip.tsx b/apps/docs/features/ui/InfoTooltip.tsx deleted file mode 100644 index 84cd2767d05fe..0000000000000 --- a/apps/docs/features/ui/InfoTooltip.tsx +++ /dev/null @@ -1,127 +0,0 @@ -'use client' - -import { useBreakpoint } from 'common' -import { InfoIcon, XIcon } from 'lucide-react' -import React, { - useCallback, - useEffect, - useId, - useRef, - useState, - type PropsWithChildren, -} from 'react' -import { ErrorBoundary } from 'react-error-boundary' -import { - Button, - cn, - CommandEmpty, - Sheet, - SheetContent, - SheetHeader, - Tooltip, - TooltipContent, - TooltipTrigger, -} from 'ui' - -interface PopUpProps extends PropsWithChildren { - tooltipContent: React.ReactNode - className?: string - contentContainerClassName?: string -} - -const buttonClassName = cn( - 'relative px-1 py-0 -my-px', - 'rounded-sm bg-surface-200 border border-dashed', - 'transition-colors hover:border-strong group/inline-popup' -) - -const InfoTooltip = ({ - children, - className, - tooltipContent, - contentContainerClassName, -}: PopUpProps) => { - const id = useId().replaceAll(':', '') - const timeout = useRef | null>(null) - - const [mobileSheetOpen, setMobileSheetOpen] = useState(false) - const [tooltipOpen, _setTooltipOpen] = useState(false) - const isMobile = useBreakpoint('md') - - const setTooltipOpen = useCallback( - (open: boolean) => { - _setTooltipOpen(open) - setMobileSheetOpen(true) - - timeout.current = setTimeout(() => { - if (isMobile) return - const targetElem: HTMLElement | null = document.querySelector(`#tooltip-content-${id}`) - targetElem?.focus() - }) - }, - [_setTooltipOpen, id, isMobile] - ) - - useEffect(() => { - return () => { - if (timeout.current) { - clearTimeout(timeout.current) - } - } - }, []) - - return ( - <> - !isMobile && setTooltipOpen(open)}> - - setMobileSheetOpen(true)} - className={cn(buttonClassName, className)} - > - {children} - - - - - {tooltipContent} - - - {isMobile && ( - - - }> - -
- -

{children}

-
- -
- {tooltipContent} -
-
-
- )} - - ) -} - -export default InfoTooltip diff --git a/apps/docs/layouts/MainSkeleton.tsx b/apps/docs/layouts/MainSkeleton.tsx index 25ac4a5819974..5d49d78f7b715 100644 --- a/apps/docs/layouts/MainSkeleton.tsx +++ b/apps/docs/layouts/MainSkeleton.tsx @@ -214,6 +214,8 @@ const MobileHeader = memo(function MobileHeader(props: MobileHeaderProps) { mobileMenuOpen && 'mt-0.5' )} onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)} + aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'} + aria-expanded={mobileMenuOpen} > - diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.test.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.test.tsx index 7f4480086bfeb..4699369cdadef 100644 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.test.tsx +++ b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.test.tsx @@ -235,8 +235,17 @@ describe('NewScopedTokenSheet', () => { await waitFor(async () => expect(await window.navigator.clipboard.readText()).toEqual('a_token_value') ) + expect(mockTrack).toHaveBeenCalledWith('access_token_copied', { tokenType: 'scoped' }) fireEvent.click(await screen.findByLabelText('I have copied the key and stored it securely')) + expect(mockTrack).toHaveBeenCalledWith('access_token_stored_checkbox_clicked', { + tokenType: 'scoped', + isChecked: true, + }) fireEvent.click(await screen.findByRole('button', { name: 'Done' })) + expect(mockTrack).toHaveBeenCalledWith('access_token_done_button_clicked', { + tokenType: 'scoped', + hasCopiedToken: true, + }) // Dialog has been closed await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull()) }, 10_000) @@ -451,8 +460,13 @@ describe('NewScopedTokenSheet', () => { expiryPreset: '7d', resourceAccess: 'account', }) + expect(mockTrack).toHaveBeenCalledWith('access_token_copied', { tokenType: 'classic' }) fireEvent.click(await screen.findByLabelText('I have copied the key and stored it securely')) fireEvent.click(await screen.findByRole('button', { name: 'Done' })) + expect(mockTrack).toHaveBeenCalledWith('access_token_done_button_clicked', { + tokenType: 'classic', + hasCopiedToken: true, + }) // Dialog has been closed await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull()) }) diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.tsx index b78759675420a..77eca42f91f64 100644 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.tsx +++ b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.tsx @@ -42,12 +42,15 @@ export const NewScopedTokenSheet = ({ onCreateExperimentalToken }: NewScopedToke const [step, setStep] = useState<'form' | 'success'>('form') const [createdToken, setCreatedToken] = useState< - NewScopedAccessToken | NewAccessToken | undefined + { token: NewScopedAccessToken | NewAccessToken; tokenType: 'classic' | 'scoped' } | undefined >() - const showCreatedToken = (data: NewScopedAccessToken | NewAccessToken) => { + const showCreatedToken = ( + data: NewScopedAccessToken | NewAccessToken, + tokenType: 'classic' | 'scoped' + ) => { toast.success('Access token created successfully') - setCreatedToken(data) + setCreatedToken({ token: data, tokenType }) setStep('success') } @@ -66,7 +69,7 @@ export const NewScopedTokenSheet = ({ onCreateExperimentalToken }: NewScopedToke expiryPreset: values.expiresAt, resourceAccess: 'account', }) - showCreatedToken(data) + showCreatedToken(data, 'classic') }, } ) @@ -94,7 +97,7 @@ export const NewScopedTokenSheet = ({ onCreateExperimentalToken }: NewScopedToke resourceAccess: values.resourceAccess, permissionCount: permissions.length, }) - showCreatedToken(data) + showCreatedToken(data, 'scoped') }, }) } @@ -103,6 +106,13 @@ export const NewScopedTokenSheet = ({ onCreateExperimentalToken }: NewScopedToke // as we need to make sure they copied the new token first const handleOpenChange = (open: boolean, isSafe = false) => { if (open === false && step === 'success' && !isSafe) return + if (open === false) { + track('access_token_creation_sheet_dismissed', { + // Can be non when users closes the sheet without completing the token creation + tokenType: createdToken?.tokenType ?? 'none', + step, + }) + } setStep('form') setIsOpen(open) } @@ -134,8 +144,9 @@ export const NewScopedTokenSheet = ({ onCreateExperimentalToken }: NewScopedToke {step === 'success' && createdToken ? ( handleOpenChange(false, true)} /> ) : ( diff --git a/apps/studio/components/interfaces/Account/Preferences/ChangeEmailAddress.tsx b/apps/studio/components/interfaces/Account/Preferences/ChangeEmailAddress.tsx index 6a9fe4cfd1a21..fed4b6bdcd983 100644 --- a/apps/studio/components/interfaces/Account/Preferences/ChangeEmailAddress.tsx +++ b/apps/studio/components/interfaces/Account/Preferences/ChangeEmailAddress.tsx @@ -17,7 +17,7 @@ export const GitHubChangeEmailAddress = () => { Email addresses for GitHub identities should be updated through GitHub

    -
  1. Log out of Supabase
  2. +
  3. Sign out of Supabase
  4. Change your Primary Email in{' '} @@ -25,9 +25,9 @@ export const GitHubChangeEmailAddress = () => { {' '} (your primary email)
  5. -
  6. Log out of GitHub
  7. -
  8. Log back into GitHub (with the new, desired email set as primary)
  9. -
  10. Log back into Supabase
  11. +
  12. Sign out of GitHub
  13. +
  14. Sign back into GitHub (with the new, desired email set as primary)
  15. +
  16. Sign back into Supabase
) diff --git a/apps/studio/components/interfaces/Database/Backups/BackupItem.test.tsx b/apps/studio/components/interfaces/Database/Backups/BackupItem.test.tsx new file mode 100644 index 0000000000000..9c7d2d89c52d8 --- /dev/null +++ b/apps/studio/components/interfaces/Database/Backups/BackupItem.test.tsx @@ -0,0 +1,69 @@ +import { fireEvent, screen } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { BackupItem } from './BackupItem' +import type { DatabaseBackup } from '@/data/database/backups-query' +import { customRender } from '@/tests/lib/custom-render' + +const { mockUseAsyncCheckPermissions } = vi.hoisted(() => ({ + mockUseAsyncCheckPermissions: vi.fn(), +})) + +vi.mock('@/hooks/misc/useCheckPermissions', () => ({ + useAsyncCheckPermissions: mockUseAsyncCheckPermissions, +})) + +const backup: DatabaseBackup = { + id: 1, + inserted_at: '2024-01-01T00:00:00Z', + isPhysicalBackup: false, + project_id: 1, + status: 'COMPLETED', +} + +describe('BackupItem', () => { + beforeEach(() => { + vi.clearAllMocks() + mockUseAsyncCheckPermissions.mockReturnValue({ can: true }) + }) + + it('enables restoring for a healthy project that is not High Availability', () => { + customRender( + + ) + + expect(screen.getByRole('button', { name: 'Restore' })).toBeEnabled() + }) + + it('disables restoring with a tooltip on High Availability projects', async () => { + customRender( + + ) + + const button = screen.getByRole('button', { name: 'Restore' }) + expect(button).toBeDisabled() + + // Radix opens the tooltip on pointermove; userEvent does not synthesize + // pointer events on disabled buttons + fireEvent.pointerMove(button) + expect( + await screen.findAllByText( + 'Restoring from a backup is unavailable on High Availability projects', + {}, + { timeout: 2000 } + ) + ).not.toHaveLength(0) + }) +}) diff --git a/apps/studio/components/interfaces/Database/Backups/BackupItem.tsx b/apps/studio/components/interfaces/Database/Backups/BackupItem.tsx index 85ca7e62f9f19..b5a7c913c16eb 100644 --- a/apps/studio/components/interfaces/Database/Backups/BackupItem.tsx +++ b/apps/studio/components/interfaces/Database/Backups/BackupItem.tsx @@ -13,11 +13,18 @@ import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' interface BackupItemProps { index: number isHealthy: boolean + isHighAvailability: boolean backup: DatabaseBackup onSelectBackup: () => void } -export const BackupItem = ({ index, isHealthy, backup, onSelectBackup }: BackupItemProps) => { +export const BackupItem = ({ + index, + isHealthy, + isHighAvailability, + backup, + onSelectBackup, +}: BackupItemProps) => { const { ref: projectRef } = useParams() const { can: canTriggerScheduledBackups } = useAsyncCheckPermissions( PermissionAction.INFRA_EXECUTE, @@ -37,22 +44,30 @@ export const BackupItem = ({ index, isHealthy, backup, onSelectBackup }: BackupI }, }) + function getTooltipText() { + if (isHighAvailability) { + return 'Restoring from a backup is unavailable on High Availability projects' + } else if (!isHealthy) { + return 'Cannot be restored as project is not active' + } else if (!canTriggerScheduledBackups) { + return 'You need additional permissions to trigger a restore' + } else { + return undefined + } + } + const generateSideButtons = (backup: DatabaseBackup) => { if (backup.status === 'COMPLETED') return (
diff --git a/apps/studio/components/interfaces/Database/Backups/BackupsList.tsx b/apps/studio/components/interfaces/Database/Backups/BackupsList.tsx index dadc2ee082014..46075e4e99707 100644 --- a/apps/studio/components/interfaces/Database/Backups/BackupsList.tsx +++ b/apps/studio/components/interfaces/Database/Backups/BackupsList.tsx @@ -17,7 +17,7 @@ import { useBackupRestoreMutation } from '@/data/database/backup-restore-mutatio import { DatabaseBackup, useBackupsQuery } from '@/data/database/backups-query' import { useSetProjectStatus } from '@/data/projects/project-detail-query' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' -import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { useIsHighAvailability, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { PROJECT_STATUS } from '@/lib/constants' export const BackupsList = () => { @@ -29,6 +29,7 @@ export const BackupsList = () => { const { setProjectStatus } = useSetProjectStatus() const { data: selectedProject } = useSelectedProjectQuery() const isHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY + const isHighAvailability = useIsHighAvailability() const { data: backups } = useBackupsQuery({ projectRef }) const { @@ -88,6 +89,7 @@ export const BackupsList = () => { backup={x} index={i} isHealthy={isHealthy} + isHighAvailability={isHighAvailability} onSelectBackup={() => setSelectedBackup(x)} /> ) diff --git a/apps/studio/components/interfaces/Database/RestoreToNewProject/RestoreToNewProject.tsx b/apps/studio/components/interfaces/Database/RestoreToNewProject/RestoreToNewProject.tsx index 30319bc6fd30c..8b2b38a8b7af6 100644 --- a/apps/studio/components/interfaces/Database/RestoreToNewProject/RestoreToNewProject.tsx +++ b/apps/studio/components/interfaces/Database/RestoreToNewProject/RestoreToNewProject.tsx @@ -28,6 +28,7 @@ import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useIsAwsK8sCloudProvider, + useIsHighAvailability, useIsOrioleDb, useSelectedProjectQuery, } from '@/hooks/misc/useSelectedProject' @@ -41,6 +42,7 @@ export const RestoreToNewProject = () => { useCheckEntitlements('backup.restore_to_new_project') const isOrioleDb = useIsOrioleDb() const isAwsK8s = useIsAwsK8sCloudProvider() + const isHighAvailability = useIsHighAvailability() const [refetchInterval, setRefetchInterval] = useState(false) const [selectedBackupId, setSelectedBackupId] = useState(null) @@ -133,6 +135,16 @@ export const RestoreToNewProject = () => { ) } + if (isHighAvailability) { + return ( + + ) + } + if (isAwsK8s) { return ( user_id field.

- Supabase already has built in the routes to sign up, login, and log out for managing - users in your apps and websites. + Supabase already has built in the routes to sign up, sign in, and sign out for + managing users in your apps and websites.

} @@ -69,12 +69,12 @@ export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps /> -

If an account is created, users can login to your app.

+

If an account is created, users can sign in to your app.

- After they have logged in, all interactions using the Supabase JS client will be + After they have signed in, all interactions using the Supabase JS client will be performed as "that user".

@@ -107,7 +107,7 @@ export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps />

@@ -133,7 +133,7 @@ export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps />

@@ -142,7 +142,7 @@ export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps

You must enter your own twilio credentials on the auth settings page to enable - SMS-based Logins. + SMS-based sign-in.

} @@ -178,12 +178,12 @@ export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps {authenticationSignInProviders && (

- Users can log in with Third Party OAuth like Google, Facebook, GitHub, and more. You - must first enable each of these in the Auth Providers settings{' '} + Users can sign in with third-party OAuth like Google, Facebook, GitHub, and more. + You must first enable each of these in the Auth Providers settings{' '} here @@ -198,7 +198,7 @@ export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps

- After they have logged in, all interactions using the Supabase JS client will be + After they have signed in, all interactions using the Supabase JS client will be performed as "that user".

@@ -229,7 +229,7 @@ export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps Get the JSON object for the logged in user.

} + content={

Get the JSON object for the signed-in user.

} snippets={ - Sends the user a log in link via email. Once logged in you should direct the user to a + Sends the user a sign-in link via email. Once signed in you should direct the user to a new password form. And use "Update User" below to save the new password.

} @@ -271,10 +271,10 @@ export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps /> - After calling log out, all interactions using the Supabase JS client will be + After calling sign out, all interactions using the Supabase JS client will be "anonymous".

} @@ -290,7 +290,7 @@ export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps title="Send a User an Invite over Email" content={ <> -

Send a user a passwordless link which they can use to sign up and log in.

+

Send a user a passwordless link which they can use to sign up and sign in.

After they have clicked the link, all interactions using the Supabase JS client will be performed as "that user". diff --git a/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.test.tsx b/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.test.tsx index f6f4201b7ccb7..a76d7420e1f22 100644 --- a/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.test.tsx +++ b/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.test.tsx @@ -140,7 +140,7 @@ describe('OrganizationInvite', () => { 'href', '/sign-in?returnTo=%2Fjoin%3Ftoken%3Dinvite-token%26slug%3Dacme-corp' ) - expect(screen.getByRole('link', { name: 'Create an account' })).toHaveAttribute( + expect(screen.getByRole('link', { name: 'Sign up' })).toHaveAttribute( 'href', '/sign-up?returnTo=%2Fjoin%3Ftoken%3Dinvite-token%26slug%3Dacme-corp' ) diff --git a/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.tsx b/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.tsx index a5a92456a5582..e6913c0582077 100644 --- a/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.tsx +++ b/apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.tsx @@ -117,7 +117,7 @@ export const OrganizationInvite = () => { {isSignUpEnabled && ( )}

diff --git a/apps/studio/components/interfaces/Settings/Addons/Addons.tsx b/apps/studio/components/interfaces/Settings/Addons/Addons.tsx index 3f2090f4edcb0..751d3fdbafe83 100644 --- a/apps/studio/components/interfaces/Settings/Addons/Addons.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/Addons.tsx @@ -30,9 +30,9 @@ import { getAddons, subscriptionHasHipaaAddon, } from '@/components/interfaces/Billing/Subscription/Subscription.utils' -import { ProjectUpdateDisabledTooltip } from '@/components/interfaces/Organization/BillingSettings/ProjectUpdateDisabledTooltip' import { SupportLink } from '@/components/interfaces/Support/SupportLink' import { AlertError } from '@/components/ui/AlertError' +import { HighAvailabilityDisabledSectionNotice } from '@/components/ui/HighAvailability/HighAvailabilityDisabledSectionNotice' import { InlineLink } from '@/components/ui/InlineLink' import { ResourceItem } from '@/components/ui/Resource/ResourceItem' import { ResourceList } from '@/components/ui/Resource/ResourceList' @@ -44,6 +44,7 @@ import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useIsAwsCloudProvider, + useIsHighAvailability, useIsOrioleDbInAws, useIsProjectActive, useSelectedProjectQuery, @@ -59,6 +60,7 @@ export const Addons = () => { const isAws = useIsAwsCloudProvider() const isProjectActive = useIsProjectActive() const isOrioleDbInAws = useIsOrioleDbInAws() === true + const isHighAvailability = useIsHighAvailability() const { projectSettingsCustomDomains, projectAddonsDedicatedIpv4Address } = useIsFeatureEnabled([ 'project_settings:custom_domains', @@ -100,14 +102,19 @@ export const Addons = () => { const customDomainEnabled = customDomain !== undefined const canOpenIPv4 = - isAws && isProjectActive && !projectUpdateDisabled && (canUpdateIPv4 || ipv4Enabled) + isAws && + isProjectActive && + !projectUpdateDisabled && + (canUpdateIPv4 || ipv4Enabled) && + !isHighAvailability const canOpenPITR = isProjectActive && !projectUpdateDisabled && sufficientPgVersion && !hasHipaaAddon && - !isOrioleDbInAws - const canOpenCustomDomain = isProjectActive && !projectUpdateDisabled + !isOrioleDbInAws && + !isHighAvailability + const canOpenCustomDomain = isProjectActive && !projectUpdateDisabled && !isHighAvailability const ipv4DisabledReason = getIPv4DisabledReason({ isAws, @@ -115,6 +122,7 @@ export const Addons = () => { projectUpdateDisabled, canUpdateIPv4, ipv4Enabled, + isHighAvailability, }) const pitrDisabledReason = getPitrDisabledReason({ @@ -123,11 +131,13 @@ export const Addons = () => { hasHipaaAddon, sufficientPgVersion, isOrioleDbInAws, + isHighAvailability, }) const customDomainDisabledReason = getCustomDomainDisabledReason({ isProjectActive, projectUpdateDisabled, + isHighAvailability, }) const pitrAlertState = getPitrAlertState({ hasHipaaAddon, @@ -190,6 +200,11 @@ export const Addons = () => { return ( + {isBranch && ( { } meta={
- - {ipv4Enabled ? ( - Enabled - ) : ( - Disabled - )} - + {ipv4Enabled ? ( + Enabled + ) : ( + Disabled + )} + {!canOpenIPv4 && ipv4DisabledReason && ( + + + + + {ipv4DisabledReason} + + )}
} > @@ -384,9 +401,13 @@ export const Addons = () => { )} - - - + {!isHighAvailability && ( + <> + + + + + )}
) diff --git a/apps/studio/components/interfaces/Settings/Addons/Addons.utils.test.ts b/apps/studio/components/interfaces/Settings/Addons/Addons.utils.test.ts index 525d3a5f6d460..06bfd53783817 100644 --- a/apps/studio/components/interfaces/Settings/Addons/Addons.utils.test.ts +++ b/apps/studio/components/interfaces/Settings/Addons/Addons.utils.test.ts @@ -8,6 +8,19 @@ import { } from './Addons.utils' describe('getIPv4DisabledReason', () => { + it('returns the High Availability message before other checks', () => { + expect( + getIPv4DisabledReason({ + isAws: false, + isProjectActive: false, + projectUpdateDisabled: true, + canUpdateIPv4: false, + ipv4Enabled: false, + isHighAvailability: true, + }) + ).toBe('Dedicated IPv4 address is unavailable on High Availability projects') + }) + it('returns the AWS-only message for non-AWS projects', () => { expect( getIPv4DisabledReason({ @@ -16,6 +29,7 @@ describe('getIPv4DisabledReason', () => { projectUpdateDisabled: false, canUpdateIPv4: true, ipv4Enabled: false, + isHighAvailability: false, }) ).toBe('Dedicated IPv4 address is only available for AWS projects') }) @@ -28,6 +42,7 @@ describe('getIPv4DisabledReason', () => { projectUpdateDisabled: true, canUpdateIPv4: false, ipv4Enabled: false, + isHighAvailability: false, }) ).toBe('Project must be active to update IPv4') }) @@ -40,6 +55,7 @@ describe('getIPv4DisabledReason', () => { projectUpdateDisabled: true, canUpdateIPv4: true, ipv4Enabled: false, + isHighAvailability: false, }) ).toBe('Project updates are currently disabled') }) @@ -52,6 +68,7 @@ describe('getIPv4DisabledReason', () => { projectUpdateDisabled: false, canUpdateIPv4: false, ipv4Enabled: false, + isHighAvailability: false, }) ).toBe('You can only add IPv4 when your project network configuration is set to IPv6') }) @@ -64,12 +81,26 @@ describe('getIPv4DisabledReason', () => { projectUpdateDisabled: false, canUpdateIPv4: false, ipv4Enabled: true, + isHighAvailability: false, }) ).toBeUndefined() }) }) describe('getPitrDisabledReason', () => { + it('returns the High Availability message before other checks', () => { + expect( + getPitrDisabledReason({ + isProjectActive: false, + projectUpdateDisabled: true, + hasHipaaAddon: true, + sufficientPgVersion: false, + isOrioleDbInAws: true, + isHighAvailability: true, + }) + ).toBe('Point in time recovery is unavailable on High Availability projects') + }) + it('returns the inactive-project message first', () => { expect( getPitrDisabledReason({ @@ -78,6 +109,7 @@ describe('getPitrDisabledReason', () => { hasHipaaAddon: true, sufficientPgVersion: false, isOrioleDbInAws: true, + isHighAvailability: false, }) ).toBe('Project must be active to update PITR') }) @@ -90,6 +122,7 @@ describe('getPitrDisabledReason', () => { hasHipaaAddon: true, sufficientPgVersion: false, isOrioleDbInAws: true, + isHighAvailability: false, }) ).toBe('Project updates are currently disabled') }) @@ -102,6 +135,7 @@ describe('getPitrDisabledReason', () => { hasHipaaAddon: true, sufficientPgVersion: true, isOrioleDbInAws: false, + isHighAvailability: false, }) ).toBe('PITR cannot be changed with HIPAA enabled') }) @@ -114,6 +148,7 @@ describe('getPitrDisabledReason', () => { hasHipaaAddon: false, sufficientPgVersion: false, isOrioleDbInAws: false, + isHighAvailability: false, }) ).toBe('Your project is too old to enable PITR') }) @@ -126,6 +161,7 @@ describe('getPitrDisabledReason', () => { hasHipaaAddon: false, sufficientPgVersion: true, isOrioleDbInAws: true, + isHighAvailability: false, }) ).toBe('Point in time recovery is not supported with OrioleDB') }) @@ -138,17 +174,29 @@ describe('getPitrDisabledReason', () => { hasHipaaAddon: false, sufficientPgVersion: true, isOrioleDbInAws: false, + isHighAvailability: false, }) ).toBeUndefined() }) }) describe('getCustomDomainDisabledReason', () => { + it('returns the High Availability message before other checks', () => { + expect( + getCustomDomainDisabledReason({ + isProjectActive: false, + projectUpdateDisabled: true, + isHighAvailability: true, + }) + ).toBe('Custom domain is unavailable on High Availability projects') + }) + it('returns the inactive-project message', () => { expect( getCustomDomainDisabledReason({ isProjectActive: false, projectUpdateDisabled: true, + isHighAvailability: false, }) ).toBe('Project must be active to update custom domain') }) @@ -158,6 +206,7 @@ describe('getCustomDomainDisabledReason', () => { getCustomDomainDisabledReason({ isProjectActive: true, projectUpdateDisabled: true, + isHighAvailability: false, }) ).toBe('Project updates are currently disabled') }) @@ -167,6 +216,7 @@ describe('getCustomDomainDisabledReason', () => { getCustomDomainDisabledReason({ isProjectActive: true, projectUpdateDisabled: false, + isHighAvailability: false, }) ).toBeUndefined() }) diff --git a/apps/studio/components/interfaces/Settings/Addons/Addons.utils.ts b/apps/studio/components/interfaces/Settings/Addons/Addons.utils.ts index 957296111082f..072d32a749426 100644 --- a/apps/studio/components/interfaces/Settings/Addons/Addons.utils.ts +++ b/apps/studio/components/interfaces/Settings/Addons/Addons.utils.ts @@ -4,6 +4,7 @@ export interface IPv4DisabledReasonOptions { projectUpdateDisabled: boolean canUpdateIPv4: boolean ipv4Enabled: boolean + isHighAvailability: boolean } export const getIPv4DisabledReason = ({ @@ -12,7 +13,12 @@ export const getIPv4DisabledReason = ({ projectUpdateDisabled, canUpdateIPv4, ipv4Enabled, + isHighAvailability, }: IPv4DisabledReasonOptions) => { + if (isHighAvailability) { + return 'Dedicated IPv4 address is unavailable on High Availability projects' + } + if (!isAws) { return 'Dedicated IPv4 address is only available for AWS projects' } @@ -38,6 +44,7 @@ export interface PitrDisabledReasonOptions { hasHipaaAddon: boolean sufficientPgVersion: boolean isOrioleDbInAws: boolean + isHighAvailability: boolean } export const getPitrDisabledReason = ({ @@ -46,7 +53,12 @@ export const getPitrDisabledReason = ({ hasHipaaAddon, sufficientPgVersion, isOrioleDbInAws, + isHighAvailability, }: PitrDisabledReasonOptions) => { + if (isHighAvailability) { + return 'Point in time recovery is unavailable on High Availability projects' + } + if (!isProjectActive) { return 'Project must be active to update PITR' } @@ -73,12 +85,18 @@ export const getPitrDisabledReason = ({ export interface CustomDomainDisabledReasonOptions { isProjectActive: boolean projectUpdateDisabled: boolean + isHighAvailability: boolean } export const getCustomDomainDisabledReason = ({ isProjectActive, projectUpdateDisabled, + isHighAvailability, }: CustomDomainDisabledReasonOptions) => { + if (isHighAvailability) { + return 'Custom domain is unavailable on High Availability projects' + } + if (!isProjectActive) { return 'Project must be active to update custom domain' } diff --git a/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.test.tsx b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.test.tsx new file mode 100644 index 0000000000000..0be7102c2fb7a --- /dev/null +++ b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.test.tsx @@ -0,0 +1,75 @@ +import { fireEvent, screen } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { PauseProjectButton } from './PauseProjectButton' +import { customRender } from '@/tests/lib/custom-render' + +const { + mockUseAsyncCheckPermissions, + mockUseCheckEntitlements, + mockUseIsHighAvailability, + mockUseSelectedOrganizationQuery, + mockUseSelectedProjectQuery, +} = vi.hoisted(() => ({ + mockUseAsyncCheckPermissions: vi.fn(), + mockUseCheckEntitlements: vi.fn(), + mockUseIsHighAvailability: vi.fn(), + mockUseSelectedOrganizationQuery: vi.fn(), + mockUseSelectedProjectQuery: vi.fn(), +})) + +vi.mock('@/hooks/misc/useCheckEntitlements', () => ({ + useCheckEntitlements: mockUseCheckEntitlements, +})) + +vi.mock('@/hooks/misc/useCheckPermissions', () => ({ + useAsyncCheckPermissions: mockUseAsyncCheckPermissions, +})) + +vi.mock('@/hooks/misc/useSelectedOrganization', () => ({ + useSelectedOrganizationQuery: mockUseSelectedOrganizationQuery, +})) + +vi.mock('@/hooks/misc/useSelectedProject', () => ({ + useIsHighAvailability: mockUseIsHighAvailability, + useIsProjectActive: () => true, + useSelectedProjectQuery: mockUseSelectedProjectQuery, +})) + +describe('PauseProjectButton', () => { + beforeEach(() => { + vi.clearAllMocks() + mockUseAsyncCheckPermissions.mockReturnValue({ can: true }) + mockUseCheckEntitlements.mockReturnValue({ hasAccess: true }) + mockUseSelectedOrganizationQuery.mockReturnValue({ data: { plan: { id: 'free' } } }) + mockUseSelectedProjectQuery.mockReturnValue({ + data: { ref: 'default', status: 'ACTIVE_HEALTHY' }, + }) + mockUseIsHighAvailability.mockReturnValue(false) + }) + + it('enables pausing for an active project that is not High Availability', () => { + customRender() + + expect(screen.getByRole('button', { name: 'Pause project' })).toBeEnabled() + }) + + it('disables pausing with a tooltip on High Availability projects', async () => { + mockUseIsHighAvailability.mockReturnValue(true) + customRender() + + const button = screen.getByRole('button', { name: 'Pause project' }) + expect(button).toBeDisabled() + + // Radix opens the tooltip on pointermove; userEvent does not synthesize + // pointer events on disabled buttons + fireEvent.pointerMove(button) + expect( + await screen.findAllByText( + 'Pausing is unavailable on High Availability projects', + {}, + { timeout: 2000 } + ) + ).not.toHaveLength(0) + }) +}) diff --git a/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx index 923ccf3c7d050..c449a2ae13b07 100644 --- a/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx @@ -20,7 +20,11 @@ import { useProjectPauseMutation } from '@/data/projects/project-pause-mutation' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' -import { useIsProjectActive, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { + useIsHighAvailability, + useIsProjectActive, + useSelectedProjectQuery, +} from '@/hooks/misc/useSelectedProject' import { PROJECT_STATUS } from '@/lib/constants' export const PauseProjectButton = () => { @@ -30,6 +34,7 @@ export const PauseProjectButton = () => { const { setProjectStatus } = useSetProjectStatus() const isProjectActive = useIsProjectActive() + const isHighAvailability = useIsHighAvailability() const isProjectUnhealthy = project?.status === PROJECT_STATUS.ACTIVE_UNHEALTHY const [isModalOpen, setIsModalOpen] = useState(false) @@ -68,11 +73,14 @@ export const PauseProjectButton = () => { project === undefined || isPaused || !canPauseProject || - !isProjectActive + !isProjectActive || + isHighAvailability function getTooltipText() { if (isPaused) { return `Your ${entityLabel} is already paused` + } else if (isHighAvailability) { + return 'Pausing is unavailable on High Availability projects' } else if (!canPauseProject) { return `You need additional permissions to pause this ${entityLabel}` } else if (isProjectUnhealthy) { diff --git a/apps/studio/components/ui/HighAvailability/HighAvailabilityDisabledSectionNotice.tsx b/apps/studio/components/ui/HighAvailability/HighAvailabilityDisabledSectionNotice.tsx index 28a928bcd9cbf..09e7ec47e449f 100644 --- a/apps/studio/components/ui/HighAvailability/HighAvailabilityDisabledSectionNotice.tsx +++ b/apps/studio/components/ui/HighAvailability/HighAvailabilityDisabledSectionNotice.tsx @@ -10,18 +10,20 @@ const DEFAULT_DESCRIPTION = interface HighAvailabilityDisabledSectionNoticeProps { title?: string description?: ReactNode + className?: string } export function HighAvailabilityDisabledSectionNotice({ title = DEFAULT_TITLE, description = DEFAULT_DESCRIPTION, + className, }: HighAvailabilityDisabledSectionNoticeProps) { const isHighAvailability = useIsHighAvailability() if (!isHighAvailability) return null return ( - +

{description}

) diff --git a/apps/studio/pages/forgot-password.tsx b/apps/studio/pages/forgot-password.tsx index 69e4ad4728d86..118d79d4e0c2b 100644 --- a/apps/studio/pages/forgot-password.tsx +++ b/apps/studio/pages/forgot-password.tsx @@ -14,7 +14,7 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
Already have an account?{' '} - Sign In + Sign in
diff --git a/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx b/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx index 547e0018607ac..1f3fa15292e88 100644 --- a/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx +++ b/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx @@ -1,6 +1,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' -import { Info } from 'lucide-react' +import { DatabaseBackup, Info } from 'lucide-react' import { Admonition } from 'ui-patterns/Admonition' import { PageContainer } from 'ui-patterns/PageContainer' import { @@ -19,15 +19,40 @@ import DatabaseLayout from '@/components/layouts/DatabaseLayout/DatabaseLayout' import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { AlertError } from '@/components/ui/AlertError' import { DocsButton } from '@/components/ui/DocsButton' +import { HighAvailabilityDisabledEmptyState } from '@/components/ui/HighAvailability/HighAvailabilityDisabledEmptyState' import InformationBox from '@/components/ui/InformationBox' import { NoPermission } from '@/components/ui/NoPermission' import { useBackupsQuery } from '@/data/database/backups-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' -import { useIsOrioleDbInAws } from '@/hooks/misc/useSelectedProject' +import { useIsHighAvailability, useIsOrioleDbInAws } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' const DatabaseScheduledBackups: NextPageWithLayout = () => { + return ( + <> + + + + Database Backups + + + + + + + + + + + + + + + ) +} + +const ScheduledBackups = () => { const { ref: projectRef } = useParams() const { @@ -39,6 +64,7 @@ const DatabaseScheduledBackups: NextPageWithLayout = () => { } = useBackupsQuery({ projectRef }) const isOrioleDbInAws = useIsOrioleDbInAws() + const isHighAvailability = useIsHighAvailability() const isPitrEnabled = backups?.pitr_enabled const { can: canReadScheduledBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( @@ -46,81 +72,74 @@ const DatabaseScheduledBackups: NextPageWithLayout = () => { 'back_ups' ) + if (isOrioleDbInAws) { + return ( + + + + ) + } + + if (isHighAvailability) { + return ( + + ) + } + return ( - <> - - - - Database Backups - - - - - - - - - - {isOrioleDbInAws ? ( - - - - ) : ( -
- {isLoading && } +
+ {isLoading && } - {isError && ( - - )} + {isError && } - {isSuccess && ( - <> - {!isPitrEnabled && ( -

- Projects are backed up daily around midnight of your project’s region and - can be restored at any time. -

- )} + {isSuccess && ( + <> + {!isPitrEnabled && ( +

+ Projects are backed up daily around midnight of your project’s region and can be + restored at any time. +

+ )} - {isPitrEnabled && ( - } - title="Point-In-Time-Recovery (PITR) enabled" - description={ -
- Your project uses PITR and full daily backups are no longer taken. PITR - lets you restore to a specific time (down to the second) within your - selected PITR retention period.{' '} - - Learn more - -
- } - /> - )} + {isPitrEnabled && ( + } + title="Point-In-Time-Recovery (PITR) enabled" + description={ +
+ Your project uses PITR and full daily backups are no longer taken. PITR lets you + restore to a specific time (down to the second) within your selected PITR + retention period.{' '} + + Learn more + +
+ } + /> + )} - {isPermissionsLoaded && !canReadScheduledBackups ? ( - - ) : ( - - )} - - )} -
- )} - - - - + {isPermissionsLoaded && !canReadScheduledBackups ? ( + + ) : ( + + )} + + )} +
) } diff --git a/apps/ui-library/public/r/password-based-auth-nextjs.json b/apps/ui-library/public/r/password-based-auth-nextjs.json index e4cfaeb897f77..8f66491d9a192 100644 --- a/apps/ui-library/public/r/password-based-auth-nextjs.json +++ b/apps/ui-library/public/r/password-based-auth-nextjs.json @@ -42,7 +42,7 @@ }, { "path": "registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx", - "content": "'use client'\n\nimport { useRouter } from 'next/navigation'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport Link from 'next/link'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const router = useRouter()\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n if (error) throw error\n // Update this route to redirect to an authenticated route. The user already has an active session.\n const next = new URLSearchParams(window.location.search).get('next')\n router.push(safeNextPath(next, '/protected'))\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Login\n Enter your email below to login to your account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n \n Forgot your password?\n \n
\n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n )\n}\n", + "content": "'use client'\n\nimport { useRouter } from 'next/navigation'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport Link from 'next/link'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const router = useRouter()\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n if (error) throw error\n // Update this route to redirect to an authenticated route. The user already has an active session.\n const next = new URLSearchParams(window.location.search).get('next')\n router.push(safeNextPath(next, '/protected'))\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Sign in\n Enter your email below to sign in to your account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n \n Forgot your password?\n \n
\n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:component" }, { @@ -65,7 +65,7 @@ }, { "path": "registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx", - "content": "'use client'\n\nimport { useRouter } from 'next/navigation'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport Link from 'next/link'\n\nexport function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [repeatPassword, setRepeatPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const router = useRouter()\n\n const handleSignUp = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n if (password !== repeatPassword) {\n setError('Passwords do not match')\n setIsLoading(false)\n return\n }\n\n try {\n const { error } = await supabase.auth.signUp({\n email,\n password,\n options: {\n emailRedirectTo: `${window.location.origin}/protected`,\n },\n })\n if (error) throw error\n router.push('/auth/sign-up-success')\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Sign up\n Create a new account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n
\n setPassword(e.target.value)}\n />\n
\n
\n
\n \n
\n setRepeatPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n
\n )\n}\n", + "content": "'use client'\n\nimport { useRouter } from 'next/navigation'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport Link from 'next/link'\n\nexport function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [repeatPassword, setRepeatPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const router = useRouter()\n\n const handleSignUp = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n if (password !== repeatPassword) {\n setError('Passwords do not match')\n setIsLoading(false)\n return\n }\n\n try {\n const { error } = await supabase.auth.signUp({\n email,\n password,\n options: {\n emailRedirectTo: `${window.location.origin}/protected`,\n },\n })\n if (error) throw error\n router.push('/auth/sign-up-success')\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Sign up\n Create a new account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n
\n setPassword(e.target.value)}\n />\n
\n
\n
\n \n
\n setRepeatPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Sign in\n \n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:component" }, { @@ -82,7 +82,7 @@ }, { "path": "registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx", - "content": "'use client'\n\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport Link from 'next/link'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState(null)\n const [success, setSuccess] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: `${window.location.origin}/auth/update-password`,\n })\n if (error) throw error\n setSuccess(true)\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\n )\n}\n", + "content": "'use client'\n\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport Link from 'next/link'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState(null)\n const [success, setSuccess] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: `${window.location.origin}/auth/update-password`,\n })\n if (error) throw error\n setSuccess(true)\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Sign in\n \n
\n
\n
\n
\n )}\n
\n )\n}\n", "type": "registry:component" }, { @@ -92,7 +92,7 @@ }, { "path": "registry/default/blocks/password-based-auth-nextjs/components/logout-button.tsx", - "content": "'use client'\n\nimport { useRouter } from 'next/navigation'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\n\nexport function LogoutButton() {\n const router = useRouter()\n\n const logout = async () => {\n const supabase = createClient()\n await supabase.auth.signOut()\n router.push('/auth/login')\n }\n\n return \n}\n", + "content": "'use client'\n\nimport { useRouter } from 'next/navigation'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\n\nexport function LogoutButton() {\n const router = useRouter()\n\n const logout = async () => {\n const supabase = createClient()\n await supabase.auth.signOut()\n router.push('/auth/login')\n }\n\n return \n}\n", "type": "registry:component" }, { diff --git a/apps/ui-library/public/r/password-based-auth-nuxtjs.json b/apps/ui-library/public/r/password-based-auth-nuxtjs.json index 099e598b34938..f727856ca861f 100644 --- a/apps/ui-library/public/r/password-based-auth-nuxtjs.json +++ b/apps/ui-library/public/r/password-based-auth-nuxtjs.json @@ -17,19 +17,19 @@ "files": [ { "path": "registry/default/password-based-auth/nuxtjs/app/components/login-form.vue", - "content": "\n\n\n", + "content": "\n\n\n", "type": "registry:file", "target": "app/components/login-form.vue" }, { "path": "registry/default/password-based-auth/nuxtjs/app/components/sign-up-form.vue", - "content": "\n\n\n", + "content": "\n\n\n", "type": "registry:file", "target": "app/components/sign-up-form.vue" }, { "path": "registry/default/password-based-auth/nuxtjs/app/components/forgot-password-form.vue", - "content": "\n\n\n", + "content": "\n\n\n", "type": "registry:file", "target": "app/components/forgot-password-form.vue" }, @@ -77,7 +77,7 @@ }, { "path": "registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue", - "content": "\n\n\n", + "content": "\n\n\n", "type": "registry:file", "target": "app/pages/protected/index.vue" }, diff --git a/apps/ui-library/public/r/password-based-auth-react-router.json b/apps/ui-library/public/r/password-based-auth-react-router.json index 1474873cd46bb..9b6867a3e3d4d 100644 --- a/apps/ui-library/public/r/password-based-auth-react-router.json +++ b/apps/ui-library/public/r/password-based-auth-react-router.json @@ -32,13 +32,13 @@ }, { "path": "registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx", - "content": "import {\n data,\n Link,\n redirect,\n useFetcher,\n useSearchParams,\n type ActionFunctionArgs,\n} from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const formData = await request.formData()\n const email = formData.get('email') as string\n\n const { supabase, headers } = createClient(request)\n const origin = new URL(request.url).origin\n\n // Send the actual reset password email\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: `${origin}/auth/confirm?next=/update-password`,\n })\n\n if (error) {\n return data(\n {\n error: error instanceof Error ? error.message : 'An error occurred',\n data: { email },\n },\n { headers }\n )\n }\n\n return redirect('/forgot-password?success')\n}\n\nexport default function ForgotPassword() {\n const fetcher = useFetcher()\n let [searchParams] = useSearchParams()\n\n const success = !!searchParams.has('success')\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n \n
\n
\n \n \n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\n
\n
\n )\n}\n", + "content": "import {\n data,\n Link,\n redirect,\n useFetcher,\n useSearchParams,\n type ActionFunctionArgs,\n} from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const formData = await request.formData()\n const email = formData.get('email') as string\n\n const { supabase, headers } = createClient(request)\n const origin = new URL(request.url).origin\n\n // Send the actual reset password email\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: `${origin}/auth/confirm?next=/update-password`,\n })\n\n if (error) {\n return data(\n {\n error: error instanceof Error ? error.message : 'An error occurred',\n data: { email },\n },\n { headers }\n )\n }\n\n return redirect('/forgot-password?success')\n}\n\nexport default function ForgotPassword() {\n const fetcher = useFetcher()\n let [searchParams] = useSearchParams()\n\n const success = !!searchParams.has('success')\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n \n
\n
\n \n \n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Sign in\n \n
\n
\n
\n
\n )}\n
\n
\n
\n )\n}\n", "type": "registry:file", "target": "app/routes/forgot-password.tsx" }, { "path": "registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx", - "content": "import { Link, redirect, useFetcher, useSearchParams, type ActionFunctionArgs } from 'react-router'\n\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { supabase, headers } = createClient(request)\n const origin = new URL(request.url).origin\n\n const formData = await request.formData()\n\n const email = formData.get('email') as string\n const password = formData.get('password') as string\n\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n\n if (error) {\n return {\n error: error instanceof Error ? error.message : 'An error occurred',\n }\n }\n\n // Update this route to redirect to an authenticated route. The user already has an active session.\n return redirect(safeNextPath(formData.get('next'), '/protected', origin), { headers })\n}\n\nexport default function Login() {\n const fetcher = useFetcher()\n const [searchParams] = useSearchParams()\n\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n \n \n Login\n Enter your email below to login to your account\n \n \n \n \n
\n
\n \n \n
\n
\n
\n \n \n Forgot your password?\n \n
\n \n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n
\n
\n )\n}\n", + "content": "import { Link, redirect, useFetcher, useSearchParams, type ActionFunctionArgs } from 'react-router'\n\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { supabase, headers } = createClient(request)\n const origin = new URL(request.url).origin\n\n const formData = await request.formData()\n\n const email = formData.get('email') as string\n const password = formData.get('password') as string\n\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n\n if (error) {\n return {\n error: error instanceof Error ? error.message : 'An error occurred',\n }\n }\n\n // Update this route to redirect to an authenticated route. The user already has an active session.\n return redirect(safeNextPath(formData.get('next'), '/protected', origin), { headers })\n}\n\nexport default function Login() {\n const fetcher = useFetcher()\n const [searchParams] = useSearchParams()\n\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n \n \n Sign in\n Enter your email below to sign in to your account\n \n \n \n \n
\n
\n \n \n
\n
\n
\n \n \n Forgot your password?\n \n
\n \n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:file", "target": "app/routes/login.tsx" }, @@ -50,13 +50,13 @@ }, { "path": "registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx", - "content": "import { redirect, useLoaderData, type LoaderFunctionArgs } from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { supabase } = createClient(request)\n\n const { data, error } = await supabase.auth.getUser()\n if (error || !data?.user) {\n return redirect('/login')\n }\n\n return data\n}\n\nexport default function ProtectedPage() {\n let data = useLoaderData()\n\n return (\n
\n

\n Hello {data.user.email}\n

\n \n \n \n
\n )\n}\n", + "content": "import { redirect, useLoaderData, type LoaderFunctionArgs } from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { supabase } = createClient(request)\n\n const { data, error } = await supabase.auth.getUser()\n if (error || !data?.user) {\n return redirect('/login')\n }\n\n return data\n}\n\nexport default function ProtectedPage() {\n let data = useLoaderData()\n\n return (\n
\n

\n Hello {data.user.email}\n

\n \n \n \n
\n )\n}\n", "type": "registry:file", "target": "app/routes/protected.tsx" }, { "path": "registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx", - "content": "import { Link, redirect, useFetcher, useSearchParams, type ActionFunctionArgs } from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { supabase } = createClient(request)\n\n const url = new URL(request.url)\n const origin = url.origin\n\n const formData = await request.formData()\n\n const email = formData.get('email') as string\n const password = formData.get('password') as string\n const repeatPassword = formData.get('repeat-password') as string\n\n if (!password) {\n return {\n error: 'Password is required',\n }\n }\n\n if (password !== repeatPassword) {\n return { error: 'Passwords do not match' }\n }\n\n const { error } = await supabase.auth.signUp({\n email,\n password,\n options: {\n emailRedirectTo: `${origin}/protected`,\n },\n })\n\n if (error) {\n return { error: error.message }\n }\n\n return redirect('/sign-up?success')\n}\n\nexport default function SignUp() {\n const fetcher = useFetcher()\n let [searchParams] = useSearchParams()\n\n const success = !!searchParams.has('success')\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n {success ? (\n \n \n Thank you for signing up!\n Check your email to confirm\n \n \n

\n You've successfully signed up. Please check your email to confirm your\n account before signing in.\n

\n
\n
\n ) : (\n \n \n Sign up\n Create a new account\n \n \n \n
\n
\n \n \n
\n
\n
\n \n
\n \n
\n
\n
\n \n
\n \n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\n
\n
\n )\n}\n", + "content": "import { Link, redirect, useFetcher, useSearchParams, type ActionFunctionArgs } from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { supabase } = createClient(request)\n\n const url = new URL(request.url)\n const origin = url.origin\n\n const formData = await request.formData()\n\n const email = formData.get('email') as string\n const password = formData.get('password') as string\n const repeatPassword = formData.get('repeat-password') as string\n\n if (!password) {\n return {\n error: 'Password is required',\n }\n }\n\n if (password !== repeatPassword) {\n return { error: 'Passwords do not match' }\n }\n\n const { error } = await supabase.auth.signUp({\n email,\n password,\n options: {\n emailRedirectTo: `${origin}/protected`,\n },\n })\n\n if (error) {\n return { error: error.message }\n }\n\n return redirect('/sign-up?success')\n}\n\nexport default function SignUp() {\n const fetcher = useFetcher()\n let [searchParams] = useSearchParams()\n\n const success = !!searchParams.has('success')\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n {success ? (\n \n \n Thank you for signing up!\n Check your email to confirm\n \n \n

\n You've successfully signed up. Please check your email to confirm your\n account before signing in.\n

\n
\n
\n ) : (\n \n \n Sign up\n Create a new account\n \n \n \n
\n
\n \n \n
\n
\n
\n \n
\n \n
\n
\n
\n \n
\n \n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Sign in\n \n
\n
\n
\n
\n )}\n
\n
\n
\n )\n}\n", "type": "registry:file", "target": "app/routes/sign-up.tsx" }, diff --git a/apps/ui-library/public/r/password-based-auth-react.json b/apps/ui-library/public/r/password-based-auth-react.json index afe60913e0228..5a32ba95e0ea1 100644 --- a/apps/ui-library/public/r/password-based-auth-react.json +++ b/apps/ui-library/public/r/password-based-auth-react.json @@ -17,17 +17,17 @@ "files": [ { "path": "registry/default/blocks/password-based-auth-react/components/login-form.tsx", - "content": "import { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/react/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const supabase = createClient()\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n if (error) throw error\n // Update this route to redirect to an authenticated route. The user already has an active session.\n const next = new URLSearchParams(window.location.search).get('next')\n location.href = safeNextPath(next, '/protected')\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Login\n Enter your email below to login to your account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n \n Forgot your password?\n \n
\n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n )\n}\n", + "content": "import { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/react/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const supabase = createClient()\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n if (error) throw error\n // Update this route to redirect to an authenticated route. The user already has an active session.\n const next = new URLSearchParams(window.location.search).get('next')\n location.href = safeNextPath(next, '/protected')\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Sign in\n Enter your email below to sign in to your account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n \n Forgot your password?\n \n
\n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:component" }, { "path": "registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx", - "content": "import { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/react/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [repeatPassword, setRepeatPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const [success, setSuccess] = useState(false)\n\n const handleSignUp = async (e: React.FormEvent) => {\n const supabase = createClient()\n e.preventDefault()\n setError(null)\n\n if (password !== repeatPassword) {\n setError('Passwords do not match')\n return\n }\n setIsLoading(true)\n\n try {\n const { error } = await supabase.auth.signUp({\n email,\n password,\n })\n if (error) throw error\n setSuccess(true)\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n {success ? (\n \n \n Thank you for signing up!\n Check your email to confirm\n \n \n

\n You've successfully signed up. Please check your email to confirm your account\n before signing in.\n

\n
\n
\n ) : (\n \n \n Sign up\n Create a new account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n
\n setPassword(e.target.value)}\n />\n
\n
\n
\n \n
\n setRepeatPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\n )\n}\n", + "content": "import { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/react/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [repeatPassword, setRepeatPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const [success, setSuccess] = useState(false)\n\n const handleSignUp = async (e: React.FormEvent) => {\n const supabase = createClient()\n e.preventDefault()\n setError(null)\n\n if (password !== repeatPassword) {\n setError('Passwords do not match')\n return\n }\n setIsLoading(true)\n\n try {\n const { error } = await supabase.auth.signUp({\n email,\n password,\n })\n if (error) throw error\n setSuccess(true)\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n {success ? (\n \n \n Thank you for signing up!\n Check your email to confirm\n \n \n

\n You've successfully signed up. Please check your email to confirm your account\n before signing in.\n

\n
\n
\n ) : (\n \n \n Sign up\n Create a new account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n
\n setPassword(e.target.value)}\n />\n
\n
\n
\n \n
\n setRepeatPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Sign in\n \n
\n
\n
\n
\n )}\n
\n )\n}\n", "type": "registry:component" }, { "path": "registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx", - "content": "import { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/react/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState(null)\n const [success, setSuccess] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n const supabase = createClient()\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: 'http://localhost:3000/update-password',\n })\n if (error) throw error\n setSuccess(true)\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\n )\n}\n", + "content": "import { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/react/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState(null)\n const [success, setSuccess] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n const supabase = createClient()\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: 'http://localhost:3000/update-password',\n })\n if (error) throw error\n setSuccess(true)\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Sign in\n \n
\n
\n
\n
\n )}\n
\n )\n}\n", "type": "registry:component" }, { diff --git a/apps/ui-library/public/r/password-based-auth-tanstack.json b/apps/ui-library/public/r/password-based-auth-tanstack.json index 209f5534c1295..69f334149652d 100644 --- a/apps/ui-library/public/r/password-based-auth-tanstack.json +++ b/apps/ui-library/public/r/password-based-auth-tanstack.json @@ -48,7 +48,7 @@ }, { "path": "registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx", - "content": "import { Link } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n if (error) throw error\n // Follow the `next` query parameter if it is a same-origin relative path, e.g. when\n // the OAuth consent screen sent the user here to sign in first. It may point outside\n // the typed route tree, so it needs a full navigation.\n const next = new URLSearchParams(window.location.search).get('next')\n window.location.assign(safeNextPath(next, '/protected'))\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Login\n Enter your email below to login to your account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n \n Forgot your password?\n \n
\n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n )\n}\n", + "content": "import { Link } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n if (error) throw error\n // Follow the `next` query parameter if it is a same-origin relative path, e.g. when\n // the OAuth consent screen sent the user here to sign in first. It may point outside\n // the typed route tree, so it needs a full navigation.\n const next = new URLSearchParams(window.location.search).get('next')\n window.location.assign(safeNextPath(next, '/protected'))\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Sign in\n Enter your email below to sign in to your account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n \n Forgot your password?\n \n
\n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:component" }, { @@ -65,7 +65,7 @@ }, { "path": "registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx", - "content": "import { Link, useNavigate } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [repeatPassword, setRepeatPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const navigate = useNavigate()\n\n const handleSignUp = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setError(null)\n\n if (password !== repeatPassword) {\n setError('Passwords do not match')\n return\n }\n setIsLoading(true)\n\n try {\n const { error } = await supabase.auth.signUp({\n email,\n password,\n options: {\n emailRedirectTo: `${window.location.origin}/protected`,\n },\n })\n if (error) throw error\n await navigate({ to: '/sign-up-success' })\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Sign up\n Create a new account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n
\n setPassword(e.target.value)}\n />\n
\n
\n
\n \n
\n setRepeatPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n
\n )\n}\n", + "content": "import { Link, useNavigate } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [repeatPassword, setRepeatPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const navigate = useNavigate()\n\n const handleSignUp = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setError(null)\n\n if (password !== repeatPassword) {\n setError('Passwords do not match')\n return\n }\n setIsLoading(true)\n\n try {\n const { error } = await supabase.auth.signUp({\n email,\n password,\n options: {\n emailRedirectTo: `${window.location.origin}/protected`,\n },\n })\n if (error) throw error\n await navigate({ to: '/sign-up-success' })\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Sign up\n Create a new account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n
\n setPassword(e.target.value)}\n />\n
\n
\n
\n \n
\n setRepeatPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Sign in\n \n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:component" }, { @@ -82,7 +82,7 @@ }, { "path": "registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx", - "content": "import { Link } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState(null)\n const [success, setSuccess] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: 'http://localhost:3000/update-password',\n })\n if (error) throw error\n setSuccess(true)\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\n )\n}\n", + "content": "import { Link } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState(null)\n const [success, setSuccess] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: 'http://localhost:3000/update-password',\n })\n if (error) throw error\n setSuccess(true)\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Sign in\n \n
\n
\n
\n
\n )}\n
\n )\n}\n", "type": "registry:component" }, { 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 a3edccc7173dc..f37ece80c75c6 100644 --- a/apps/ui-library/public/r/password-based-auth-vue.json +++ b/apps/ui-library/public/r/password-based-auth-vue.json @@ -16,19 +16,19 @@ "files": [ { "path": "registry/default/password-based-auth/vue/components/login-form.vue", - "content": "\n\n\n", + "content": "\n\n\n", "type": "registry:component", "target": "components/login-form.vue" }, { "path": "registry/default/password-based-auth/vue/components/sign-up-form.vue", - "content": "\n\n\n", + "content": "\n\n\n", "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", + "content": "\n\n\n", "type": "registry:component", "target": "components/forgot-password-form.vue" }, diff --git a/apps/ui-library/public/r/social-auth-nextjs.json b/apps/ui-library/public/r/social-auth-nextjs.json index a49a2280c0e86..b22a8a96452ff 100644 --- a/apps/ui-library/public/r/social-auth-nextjs.json +++ b/apps/ui-library/public/r/social-auth-nextjs.json @@ -40,7 +40,7 @@ }, { "path": "registry/default/blocks/social-auth-nextjs/components/login-form.tsx", - "content": "'use client'\n\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleSocialLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const next = new URLSearchParams(window.location.search).get('next')\n const { error } = await supabase.auth.signInWithOAuth({\n provider: 'github',\n options: {\n redirectTo: `${window.location.origin}/auth/oauth?next=${encodeURIComponent(safeNextPath(next, '/protected'))}`,\n },\n })\n\n if (error) throw error\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Welcome!\n Sign in to your account to continue\n \n \n
\n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n )\n}\n", + "content": "'use client'\n\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleSocialLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const next = new URLSearchParams(window.location.search).get('next')\n const { error } = await supabase.auth.signInWithOAuth({\n provider: 'github',\n options: {\n redirectTo: `${window.location.origin}/auth/oauth?next=${encodeURIComponent(safeNextPath(next, '/protected'))}`,\n },\n })\n\n if (error) throw error\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Welcome!\n Sign in to your account to continue\n \n \n
\n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:component" }, { @@ -51,7 +51,7 @@ }, { "path": "registry/default/blocks/social-auth-nextjs/components/logout-button.tsx", - "content": "'use client'\n\nimport { useRouter } from 'next/navigation'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\n\nexport function LogoutButton() {\n const router = useRouter()\n\n const logout = async () => {\n const supabase = createClient()\n await supabase.auth.signOut()\n router.push('/auth/login')\n }\n\n return \n}\n", + "content": "'use client'\n\nimport { useRouter } from 'next/navigation'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\n\nexport function LogoutButton() {\n const router = useRouter()\n\n const logout = async () => {\n const supabase = createClient()\n await supabase.auth.signOut()\n router.push('/auth/login')\n }\n\n return \n}\n", "type": "registry:component" }, { diff --git a/apps/ui-library/public/r/social-auth-nuxtjs.json b/apps/ui-library/public/r/social-auth-nuxtjs.json index 1d42467b7237e..64605348e0d11 100644 --- a/apps/ui-library/public/r/social-auth-nuxtjs.json +++ b/apps/ui-library/public/r/social-auth-nuxtjs.json @@ -17,13 +17,13 @@ "files": [ { "path": "registry/default/social-auth/nuxtjs/app/components/login-form.vue", - "content": "\n\n\n", + "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", + "content": "\n\n\n", "type": "registry:file", "target": "app/components/logout-button.vue" }, diff --git a/apps/ui-library/public/r/social-auth-react-router.json b/apps/ui-library/public/r/social-auth-react-router.json index 3956d8fe86b29..085d9fc0220a0 100644 --- a/apps/ui-library/public/r/social-auth-react-router.json +++ b/apps/ui-library/public/r/social-auth-react-router.json @@ -30,7 +30,7 @@ }, { "path": "registry/default/blocks/social-auth-react-router/app/routes/login.tsx", - "content": "import { redirect, useFetcher, useSearchParams, type ActionFunctionArgs } from 'react-router'\n\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { supabase, headers } = createClient(request)\n const origin = new URL(request.url).origin\n\n const formData = await request.formData()\n const next = safeNextPath(formData.get('next'), '/protected', origin)\n\n const { data, error } = await supabase.auth.signInWithOAuth({\n provider: 'github',\n options: {\n redirectTo: `${origin}/auth/oauth?next=${encodeURIComponent(next)}`,\n },\n })\n\n if (data.url) {\n return redirect(data.url, { headers })\n }\n\n if (error) {\n return {\n error: error instanceof Error ? error.message : 'An error occurred',\n }\n }\n}\n\nexport default function Login() {\n const fetcher = useFetcher()\n const [searchParams] = useSearchParams()\n\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n \n \n Welcome!\n Sign in to your account to continue\n \n \n \n \n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n
\n
\n )\n}\n", + "content": "import { redirect, useFetcher, useSearchParams, type ActionFunctionArgs } from 'react-router'\n\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { supabase, headers } = createClient(request)\n const origin = new URL(request.url).origin\n\n const formData = await request.formData()\n const next = safeNextPath(formData.get('next'), '/protected', origin)\n\n const { data, error } = await supabase.auth.signInWithOAuth({\n provider: 'github',\n options: {\n redirectTo: `${origin}/auth/oauth?next=${encodeURIComponent(next)}`,\n },\n })\n\n if (data.url) {\n return redirect(data.url, { headers })\n }\n\n if (error) {\n return {\n error: error instanceof Error ? error.message : 'An error occurred',\n }\n }\n}\n\nexport default function Login() {\n const fetcher = useFetcher()\n const [searchParams] = useSearchParams()\n\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n \n \n Welcome!\n Sign in to your account to continue\n \n \n \n \n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:file", "target": "app/routes/login.tsx" }, @@ -42,7 +42,7 @@ }, { "path": "registry/default/blocks/social-auth-react-router/app/routes/protected.tsx", - "content": "import { redirect, useLoaderData, type LoaderFunctionArgs } from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { supabase } = createClient(request)\n\n const { data, error } = await supabase.auth.getUser()\n if (error || !data?.user) {\n return redirect('/login')\n }\n\n return data\n}\n\nexport default function ProtectedPage() {\n let data = useLoaderData()\n\n return (\n
\n

\n Hello {data.user.email}\n

\n \n \n \n
\n )\n}\n", + "content": "import { redirect, useLoaderData, type LoaderFunctionArgs } from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { supabase } = createClient(request)\n\n const { data, error } = await supabase.auth.getUser()\n if (error || !data?.user) {\n return redirect('/login')\n }\n\n return data\n}\n\nexport default function ProtectedPage() {\n let data = useLoaderData()\n\n return (\n
\n

\n Hello {data.user.email}\n

\n \n \n \n
\n )\n}\n", "type": "registry:file", "target": "app/routes/protected.tsx" }, diff --git a/apps/ui-library/public/r/social-auth-react.json b/apps/ui-library/public/r/social-auth-react.json index d11394b824952..a6c3adf3c6aa3 100644 --- a/apps/ui-library/public/r/social-auth-react.json +++ b/apps/ui-library/public/r/social-auth-react.json @@ -14,7 +14,7 @@ "files": [ { "path": "registry/default/blocks/social-auth-react/components/login-form.tsx", - "content": "'use client'\n\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/react/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleSocialLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithOAuth({\n provider: 'github',\n })\n\n if (error) throw error\n location.href = '/protected'\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Welcome!\n Sign in to your account to continue\n \n \n
\n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n )\n}\n", + "content": "'use client'\n\nimport { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/react/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleSocialLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithOAuth({\n provider: 'github',\n })\n\n if (error) throw error\n location.href = '/protected'\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Welcome!\n Sign in to your account to continue\n \n \n
\n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:component" }, { diff --git a/apps/ui-library/public/r/social-auth-tanstack.json b/apps/ui-library/public/r/social-auth-tanstack.json index 6f5aae7f630dd..05657beee9ff7 100644 --- a/apps/ui-library/public/r/social-auth-tanstack.json +++ b/apps/ui-library/public/r/social-auth-tanstack.json @@ -16,7 +16,7 @@ "files": [ { "path": "registry/default/blocks/social-auth-tanstack/components/login-form.tsx", - "content": "import { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleSocialLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const next = new URLSearchParams(window.location.search).get('next')\n const { error } = await supabase.auth.signInWithOAuth({\n provider: 'github',\n options: {\n redirectTo: `${window.location.origin}/auth/confirm?next=${encodeURIComponent(safeNextPath(next, '/protected'))}`,\n },\n })\n\n if (error) throw error\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Welcome!\n Sign in to your account to continue\n \n \n
\n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n )\n}\n", + "content": "import { useState } from 'react'\n\nimport { cn } from '@/lib/utils'\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleSocialLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const next = new URLSearchParams(window.location.search).get('next')\n const { error } = await supabase.auth.signInWithOAuth({\n provider: 'github',\n options: {\n redirectTo: `${window.location.origin}/auth/confirm?next=${encodeURIComponent(safeNextPath(next, '/protected'))}`,\n },\n })\n\n if (error) throw error\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Welcome!\n Sign in to your account to continue\n \n \n
\n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:component" }, { diff --git a/apps/ui-library/public/r/social-auth-vue.json b/apps/ui-library/public/r/social-auth-vue.json index 870daed4a91ff..4450964b338a8 100644 --- a/apps/ui-library/public/r/social-auth-vue.json +++ b/apps/ui-library/public/r/social-auth-vue.json @@ -14,7 +14,7 @@ "files": [ { "path": "registry/default/social-auth/vue/components/login-form.vue", - "content": "\n\n\n", + "content": "\n\n\n", "type": "registry:component", "target": "components/login-form.vue" } diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx index b5d5a227bfb62..4a95c351cad26 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx @@ -87,7 +87,7 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
Already have an account?{' '} - Login + Sign in
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx index 39bc9640d7bb2..5fefdcc762e67 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx @@ -51,8 +51,8 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
- Login - Enter your email below to login to your account + Sign in + Enter your email below to sign in to your account
@@ -88,7 +88,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/logout-button.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/logout-button.tsx index 6b50eb5850bad..be6dd9c4a5fdb 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/logout-button.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/logout-button.tsx @@ -14,5 +14,5 @@ export function LogoutButton() { router.push('/example/password-based-auth/auth/login') } - return + return } diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx index 91947d1fbe802..6236274475b5d 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx @@ -107,7 +107,7 @@ export function SignUpForm({ className, ...props }: React.ComponentPropsWithoutR
Already have an account?{' '} - Login + Sign in
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx index 783dbeda707e5..64fd339b7f7e5 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx @@ -98,7 +98,7 @@ export default function ForgotPassword() {
Already have an account?{' '} - Login + Sign in
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx index 15d9defc3429f..732171942d14d 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx @@ -50,8 +50,8 @@ export default function Login() {
- Login - Enter your email below to login to your account + Sign in + Enter your email below to sign in to your account @@ -81,7 +81,7 @@ export default function Login() {
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx index 7c8625273b56b..ce189bdf3da89 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx @@ -23,7 +23,7 @@ export default function ProtectedPage() { Hello {data.user.email}

- +
) diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx index ae5218f566858..01c32a5b42d09 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx @@ -113,7 +113,7 @@ export default function SignUp() {
Already have an account?{' '} - Login + Sign in
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx index ca15a7695719d..b87d1b48bfaf7 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx @@ -84,7 +84,7 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
Already have an account?{' '} - Login + Sign in
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/login-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/login-form.tsx index 9b0c71b95cf18..6269bef302726 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/login-form.tsx @@ -46,8 +46,8 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
- Login - Enter your email below to login to your account + Sign in + Enter your email below to sign in to your account
@@ -83,7 +83,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx index d6d0e8739bfd2..36703dfa29464 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx @@ -113,7 +113,7 @@ export function SignUpForm({ className, ...props }: React.ComponentPropsWithoutR
Already have an account?{' '} - Login + Sign in
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx index 9f6e89a93e54d..8e87b7fa5ed97 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx @@ -85,7 +85,7 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
Already have an account?{' '} - Login + Sign in
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx index 00824c9fad808..ef16768f2ed55 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx @@ -49,8 +49,8 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
- Login - Enter your email below to login to your account + Sign in + Enter your email below to sign in to your account
@@ -86,7 +86,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx index 8cb97647f4eb2..6ccd6a1fdff21 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx @@ -103,7 +103,7 @@ export function SignUpForm({ className, ...props }: React.ComponentPropsWithoutR
Already have an account?{' '} - Login + Sign in
diff --git a/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx index fb8c80893c26b..cbaa6357f8501 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx @@ -52,7 +52,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/logout-button.tsx b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/logout-button.tsx index 6b50eb5850bad..be6dd9c4a5fdb 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/logout-button.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/logout-button.tsx @@ -14,5 +14,5 @@ export function LogoutButton() { router.push('/example/password-based-auth/auth/login') } - return + return } diff --git a/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/login.tsx b/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/login.tsx index 70ff49bc99c98..b652332873c6a 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/login.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/login.tsx @@ -58,7 +58,7 @@ export default function Login() {
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/protected.tsx b/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/protected.tsx index 7c8625273b56b..ce189bdf3da89 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/protected.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/protected.tsx @@ -23,7 +23,7 @@ export default function ProtectedPage() { Hello {data.user.email}

- +
) diff --git a/apps/ui-library/registry/default/blocks/social-auth-react/components/login-form.tsx b/apps/ui-library/registry/default/blocks/social-auth-react/components/login-form.tsx index ad56db33edc76..46566af35ed3e 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-react/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-react/components/login-form.tsx @@ -48,7 +48,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/social-auth-tanstack/components/login-form.tsx b/apps/ui-library/registry/default/blocks/social-auth-tanstack/components/login-form.tsx index 8f16711a82f2b..14ee179be8b1d 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-tanstack/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-tanstack/components/login-form.tsx @@ -50,7 +50,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
{error &&

{error}

}
diff --git a/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/forgot-password-form.vue b/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/forgot-password-form.vue index d40c5cfa521a5..1660190079e08 100644 --- a/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/forgot-password-form.vue +++ b/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/forgot-password-form.vue @@ -79,7 +79,7 @@ const handleForgotPassword = async (e: Event) => {
Already have an account? - Login + Sign in
diff --git a/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/login-form.vue b/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/login-form.vue index 79e868c0600d7..5fcb09b574305 100644 --- a/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/login-form.vue +++ b/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/login-form.vue @@ -78,7 +78,7 @@ const handleForgotPassword = async (e: Event) => {
Already have an account? - Login + Sign in
diff --git a/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/sign-up-form.vue b/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/sign-up-form.vue index 9a15f32197dcf..6299c695c46b6 100644 --- a/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/sign-up-form.vue +++ b/blocks/vue/registry/default/password-based-auth/nuxtjs/app/components/sign-up-form.vue @@ -116,7 +116,7 @@ const handleSignUp = async () => {
Already have an account? - Login + Sign in
diff --git a/blocks/vue/registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue b/blocks/vue/registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue index c21be7818bce3..d42e516710a5d 100644 --- a/blocks/vue/registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue +++ b/blocks/vue/registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue @@ -38,7 +38,7 @@ const handleLogout = async () => { class="rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium hover:bg-primary/90" @click="handleLogout" > - Logout + Sign out diff --git a/blocks/vue/registry/default/password-based-auth/vue/components/forgot-password-form.vue b/blocks/vue/registry/default/password-based-auth/vue/components/forgot-password-form.vue index d40c5cfa521a5..1660190079e08 100644 --- a/blocks/vue/registry/default/password-based-auth/vue/components/forgot-password-form.vue +++ b/blocks/vue/registry/default/password-based-auth/vue/components/forgot-password-form.vue @@ -79,7 +79,7 @@ const handleForgotPassword = async (e: Event) => {
Already have an account? - Login + Sign in
diff --git a/blocks/vue/registry/default/password-based-auth/vue/components/login-form.vue b/blocks/vue/registry/default/password-based-auth/vue/components/login-form.vue index 79e868c0600d7..5fcb09b574305 100644 --- a/blocks/vue/registry/default/password-based-auth/vue/components/login-form.vue +++ b/blocks/vue/registry/default/password-based-auth/vue/components/login-form.vue @@ -78,7 +78,7 @@ const handleForgotPassword = async (e: Event) => {
Already have an account? - Login + Sign in
diff --git a/blocks/vue/registry/default/password-based-auth/vue/components/sign-up-form.vue b/blocks/vue/registry/default/password-based-auth/vue/components/sign-up-form.vue index 9a15f32197dcf..6299c695c46b6 100644 --- a/blocks/vue/registry/default/password-based-auth/vue/components/sign-up-form.vue +++ b/blocks/vue/registry/default/password-based-auth/vue/components/sign-up-form.vue @@ -116,7 +116,7 @@ const handleSignUp = async () => {
Already have an account? - Login + Sign in
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 index ef5ebeb466cc2..b5f3e1ae739af 100644 --- 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 @@ -51,7 +51,7 @@ const handleSocialLogin = async (e: Event) => {

{{ error }}

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 index 066aa0067548a..8fb89e4651da6 100644 --- 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 @@ -13,5 +13,5 @@ const logout = async () => { 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 index 6744a764eb0a7..651e3f9cd29d6 100644 --- a/blocks/vue/registry/default/social-auth/vue/components/login-form.vue +++ b/blocks/vue/registry/default/social-auth/vue/components/login-form.vue @@ -47,7 +47,7 @@ const handleSocialLogin = async (e: Event) => {

{{ error }}

diff --git a/e2e/docs/utils/axe-helpers.ts b/e2e/docs/utils/axe-helpers.ts index afc86ac8c1e29..c0a00d65d669b 100644 --- a/e2e/docs/utils/axe-helpers.ts +++ b/e2e/docs/utils/axe-helpers.ts @@ -5,7 +5,7 @@ import { scan } from '../../shared/axe.ts' export const WCAG_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'] -export const ENFORCED_RULES = ['heading-order', 'page-has-heading-one'] +export const ENFORCED_RULES = ['heading-order', 'page-has-heading-one', 'button-name'] export const EXCLUDED_RULES = [ 'color-contrast', @@ -40,7 +40,7 @@ export async function scanArticle( include: string ): Promise { const reported = await scan(page, { tags: WCAG_TAGS, excludeRules: EXCLUDED_RULES, include }) - const enforced = await scan(page, { rules: ENFORCED_RULES, include }) + const enforced = await scan(page, { rules: ENFORCED_RULES }) const byRule = new Map([...reported, ...enforced].map((violation) => [violation.id, violation])) diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index dbe916ee052e7..e01b9126af449 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -3533,6 +3533,22 @@ export interface AccessTokenCreatedEvent { groups: Omit } +/** + * Triggered when an access token creation sheet is closed. + * + * @group Events + * @source studio + * @page /account/tokens + */ +export interface AccessTokenCreationSheetDismissedEvent { + action: 'access_token_creation_sheet_dismissed' + properties: { + tokenType: 'classic' | 'scoped' | 'none' + step: 'form' | 'success' + } + groups: Omit +} + /** * Triggered when an access token is successfully deleted. * @@ -3548,6 +3564,57 @@ export interface AccessTokenRemovedEvent { groups: Omit } +/** + * Triggered when the copy button is used on the token value shown after creation. The value is + * only ever displayed once, so this measures how many users leave with a usable token. + * + * @group Events + * @source studio + * @page /account/tokens (token created step of the generate token sheet) + */ +export interface AccessTokenCopiedEvent { + action: 'access_token_copied' + properties: { + tokenType: 'classic' | 'scoped' + } + groups: Omit +} + +/** + * Triggered when the "I have copied the key and stored it securely" checkbox is toggled on the + * token created step. `isChecked` is the resulting state, so unticking is tracked too. + * + * @group Events + * @source studio + * @page /account/tokens (token created step of the generate token sheet) + */ +export interface AccessTokenStoredCheckboxClickedEvent { + action: 'access_token_stored_checkbox_clicked' + properties: { + tokenType: 'classic' | 'scoped' + /** The state the checkbox was toggled into */ + isChecked: boolean + } + groups: Omit +} + +/** + * Triggered when the "Done" button dismisses the token created step, completing the creation flow. + * + * @group Events + * @source studio + * @page /account/tokens (token created step of the generate token sheet) + */ +export interface AccessTokenDoneButtonClickedEvent { + action: 'access_token_done_button_clicked' + properties: { + tokenType: 'classic' | 'scoped' + /** Whether the copy button was used before finishing, as opposed to copying the value manually */ + hasCopiedToken: boolean + } + groups: Omit +} + /** * User clicked the "Upgrade to Pro" CTA. Fired from each CTA placement surface, with * `placement` identifying which one (the user dropdown or the org project-list usage card). @@ -4008,7 +4075,11 @@ export type TelemetryEvent = | UpgradeCtaClickedEvent | PricingPanelPlanPresentationExperimentExposedEvent | AccessTokenCreatedEvent + | AccessTokenCreationSheetDismissedEvent | AccessTokenRemovedEvent + | AccessTokenCopiedEvent + | AccessTokenStoredCheckboxClickedEvent + | AccessTokenDoneButtonClickedEvent | ResourceExhaustionBannerUpgradeClickedEvent | ResourceExhaustionBannerAiAssistantClickedEvent | UnifiedLogsRowClickedEvent diff --git a/packages/config/typography.config.js b/packages/config/typography.config.js index 8607488bbfdbc..fb506975f0cab 100644 --- a/packages/config/typography.config.js +++ b/packages/config/typography.config.js @@ -114,7 +114,7 @@ module.exports = { border: '1px solid var(--border-default)', borderRadius: 'var(--radius-lg)', }, - td: { + 'tbody tr:not(:last-child) td': { borderBottom: '1px solid var(--background-surface-200)', }, code: {