From 2bcab95dc68404c6d35e2ee19829e8a80697367f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 18 Mar 2026 16:20:04 +0800 Subject: [PATCH 01/21] Small QoL improvements to MFA (#43861) ## Context Resolves FE-2794 Just addressing some friction points i ran into when setting up MFA for my account under account preferences ## Changes involved - Hitting enter at each step of setting up MFA will submit and proceed (Previously didn't, had to click buttons) - When logging in with MFA, automatically submit after entering 6 digits - Refactored to use react hook form for `AddNewFactorModal` in `FirstStep` and `SecondStep` + simplified the logic a little --- .../Account/TOTPFactors/AddNewFactorModal.tsx | 210 +++++++++++------- .../interfaces/SignIn/SignInMfaForm.tsx | 13 +- 2 files changed, 139 insertions(+), 84 deletions(-) diff --git a/apps/studio/components/interfaces/Account/TOTPFactors/AddNewFactorModal.tsx b/apps/studio/components/interfaces/Account/TOTPFactors/AddNewFactorModal.tsx index d72c035224c..2b2170cac67 100644 --- a/apps/studio/components/interfaces/Account/TOTPFactors/AddNewFactorModal.tsx +++ b/apps/studio/components/interfaces/Account/TOTPFactors/AddNewFactorModal.tsx @@ -1,7 +1,5 @@ +import { zodResolver } from '@hookform/resolvers/zod' import { useQueryClient } from '@tanstack/react-query' -import { Dispatch, SetStateAction, useEffect, useState } from 'react' -import { toast } from 'sonner' - import { LOCAL_STORAGE_KEYS } from 'common' import InformationBox from 'components/ui/InformationBox' import { organizationKeys } from 'data/organizations/keys' @@ -9,9 +7,16 @@ import { useMfaChallengeAndVerifyMutation } from 'data/profile/mfa-challenge-and import { useMfaEnrollMutation } from 'data/profile/mfa-enroll-mutation' import { useMfaUnenrollMutation } from 'data/profile/mfa-unenroll-mutation' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' -import { Input } from 'ui' +import { useEffect, useState } from 'react' +import { useForm, type SubmitHandler } from 'react-hook-form' +import { toast } from 'sonner' +import { Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, Input, Input_Shadcn_ } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' +import { z } from 'zod' + +type TOTP = { qr_code: string; secret: string; uri: string } interface AddNewFactorModalProps { visible: boolean @@ -19,32 +24,24 @@ interface AddNewFactorModalProps { } export const AddNewFactorModal = ({ visible, onClose }: AddNewFactorModalProps) => { - // Generate a name with a number between 0 and 1000 - const [name, setName] = useState(`App ${Math.floor(Math.random() * 1000)}`) const { data, mutate: enroll, isPending: isEnrolling, reset } = useMfaEnrollMutation() useEffect(() => { - // reset has to be called because the state is kept between if the process is canceled during - // the second step. - if (!visible) { - setName(`App ${Math.floor(Math.random() * 1000)}`) - reset() - } + if (!visible) reset() }, [reset, visible]) return ( <> } isLoading={isEnrolling} onClose={onClose} @@ -55,14 +52,33 @@ export const AddNewFactorModal = ({ visible, onClose }: AddNewFactorModalProps) interface FirstStepProps { visible: boolean - name: string - setName: Dispatch> - enroll: (params: { factorType: 'totp'; friendlyName?: string }) => void isEnrolling: boolean + reset: () => void + enroll: (params: { factorType: 'totp'; friendlyName?: string }) => void onClose: () => void } -const FirstStep = ({ visible, name, enroll, setName, isEnrolling, onClose }: FirstStepProps) => { +const FirstStep = ({ visible, isEnrolling, reset, enroll, onClose }: FirstStepProps) => { + const FormSchema = z.object({ + name: z.string().min(1, 'Please provide a name to identify this app'), + }) + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { name: '' }, + mode: 'onChange', + }) + + const onSubmit: SubmitHandler> = async (values) => { + enroll({ factorType: 'totp', friendlyName: values.name }) + } + + useEffect(() => { + if (!visible) { + // Generate a name with a number between 0 and 1000 + form.reset({ name: `App ${Math.floor(Math.random() * 1000)}` }) + } + }, [form, visible]) + return ( { - enroll({ - factorType: 'totp', - friendlyName: name, - }) - }} + onConfirm={form.handleSubmit(onSubmit)} > - setName(e.target.value)} - /> + +
+ ( + + + + + + )} + /> + +
) } @@ -96,11 +124,7 @@ interface SecondStepProps { factor?: { id: string type: 'totp' - totp: { - qr_code: string - secret: string - uri: string - } + totp: TOTP } isLoading: boolean onClose: () => void @@ -113,14 +137,23 @@ const SecondStep = ({ isLoading, onClose, }: SecondStepProps) => { - const [code, setCode] = useState('') const queryClient = useQueryClient() - const [lastVisitedOrganization] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION, '' ) + const FormSchema = z.object({ + code: z.string().min(1, 'Please provide a code from your authenticator app'), + }) + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { code: '' }, + mode: 'onChange', + }) + + const [factor, setFactor] = useState<{ id: string; type: 'totp'; totp: TOTP } | null>(null) + const { mutate: unenroll } = useMfaUnenrollMutation({ onSuccess: () => onClose() }) const { mutate: challengeAndVerify, isPending: isVerifying } = useMfaChallengeAndVerifyMutation({ onError: (error) => { @@ -137,15 +170,10 @@ const SecondStep = ({ }, }) - const [factor, setFactor] = useState<{ - id: string - type: 'totp' - totp: { - qr_code: string - secret: string - uri: string - } - } | null>(null) + const onSubmit: SubmitHandler> = async (values) => { + if (!factor) return toast.error('Factor required') + challengeAndVerify({ factorId: factor.id, code: values.code }) + } // this useEffect is to keep the factor until a new one comes. This is a fix to an issue which // happens when closing the modal, the outer factor is reset to null too soon and the modal @@ -153,6 +181,7 @@ const SecondStep = ({ useEffect(() => { if (outerFactor && factor?.id !== outerFactor.id) { setFactor(outerFactor) + form.reset({ code: '' }) } }, [outerFactor]) @@ -160,6 +189,7 @@ const SecondStep = ({ factor && challengeAndVerify({ factorId: factor.id, code })} + onConfirm={form.handleSubmit(onSubmit)} > -
- - Use an authenticator app to scan the following QR code, and provide the code from the app - to complete the enrolment. - -
+

+ Use an authenticator app to scan the following QR code, and provide the code from the app to + complete the enrolment. +

+ {isLoading && (
)} + {factor && ( - <> +
{factor.totp.uri}
-
- - } - /> -
-
- setCode(e.target.value)} - /> -
- + + + } + /> + + +
+ ( + + + + + + )} + /> + +
+
)}
) diff --git a/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx b/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx index d14f1f645ce..f408cdbaadb 100644 --- a/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx +++ b/apps/studio/components/interfaces/SignIn/SignInMfaForm.tsx @@ -11,7 +11,7 @@ import { getReturnToPath } from 'lib/gotrue' import { Lock } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { Button, Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, Input_Shadcn_ } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' @@ -34,12 +34,15 @@ export const SignInMfaForm = ({ context = 'sign-in' }: SignInMfaFormProps) => { const router = useRouter() const signOut = useSignOut() const queryClient = useQueryClient() + const [selectedFactor, setSelectedFactor] = useState(null) const form = useForm>({ resolver: zodResolver(schema), defaultValues: { code: '' }, }) + const { code } = form.watch() + const { data: factors, error: factorsError, @@ -89,6 +92,10 @@ export const SignInMfaForm = ({ context = 'sign-in' }: SignInMfaFormProps) => { } }, [factors?.totp, isSuccessFactors, router, queryClient]) + useEffect(() => { + if (code.length === 6) form.handleSubmit(onSubmit)() + }, [code]) + const error = useAuthError() if (error) { @@ -134,7 +141,7 @@ export const SignInMfaForm = ({ context = 'sign-in' }: SignInMfaFormProps) => { { )} /> -
+
- -
+ additionalActions={ + } /> ) @@ -39,18 +52,27 @@ export const InstallationError = ({ if (error === 'install') { return ( - - - -
+ + {isUpgrade ? 'Retry upgrade' : 'Retry installation'} + } /> ) diff --git a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/InstallationOverview.tsx b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/InstallationOverview.tsx index d1a3343376f..fbcd7c929b6 100644 --- a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/InstallationOverview.tsx +++ b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/InstallationOverview.tsx @@ -45,8 +45,6 @@ import { } from './stripe-sync-status' import { StripeSyncChangesCard } from './StripeSyncChangesCard' import { useStripeSyncStatus } from '@/components/interfaces/Integrations/templates/StripeSyncEngine/useStripeSyncStatus' -import { InlineLink } from '@/components/ui/InlineLink' -import { useSSLEnforcementQuery } from '@/data/ssl-enforcement/ssl-enforcement-query' const installFormSchema = z.object({ stripeSecretKey: z.string().min(1, 'Stripe API key is required'), @@ -71,7 +69,10 @@ export const StripeSyncInstallationPage = () => { }) const { - parsedSchema: { status: installationStatus }, + schemaComment, + schemaComment: { status: installationStatus }, + latestAvailableVersion, + timedOut, } = useStripeSyncStatus({ projectRef: project?.ref, connectionString: project?.connectionString, @@ -91,6 +92,22 @@ export const StripeSyncInstallationPage = () => { const installDone = isInstallDone(installationStatus) const uninstallDone = isUninstallDone(installationStatus) + // Detect if this is an upgrade (both old and new versions present) + let oldVersion + let newVersion + if (installed) { + // when installed we compare the installed version against the latest available + oldVersion = schemaComment?.newVersion + newVersion = latestAvailableVersion + } else { + // otherwise compare the old and new versions from the schema + oldVersion = schemaComment?.oldVersion + newVersion = schemaComment?.newVersion + } + + const upgradeAvailable = !!(oldVersion && newVersion && oldVersion !== newVersion) + const upgradeDone = latestAvailableVersion == schemaComment?.newVersion + const { mutate: installStripeSync, isPending: isInstallRequested, @@ -98,7 +115,9 @@ export const StripeSyncInstallationPage = () => { reset: resetInstallError, } = useStripeSyncInstallMutation({ onSuccess: () => { - toast.success('Stripe Sync installation started') + toast.success( + upgradeAvailable ? 'Stripe Sync upgrade started' : 'Stripe Sync installation started' + ) setShouldShowInstallSheet(false) form.reset() setIsInstallInitiated(true) @@ -115,11 +134,12 @@ export const StripeSyncInstallationPage = () => { }) // Combine schema status with mutation/initiated states for UI - const installing = installInProgress || isInstallRequested || isInstallInitiated - const uninstalling = uninstallInProgress || isUninstallRequested || isUninstallInitiated + const installing = (installInProgress || isInstallRequested || isInstallInitiated) && !timedOut + const uninstalling = + (uninstallInProgress || isUninstallRequested || isUninstallInitiated) && !timedOut const canInstall = checkCanInstall(installationStatus) && !installed && !installing - const hasError = (uninstallError || installError) && !uninstalling && !installing + const hasError = (uninstallError || installError) && ((!uninstalling && !installing) || timedOut) // Poll for schema changes during transitions useSchemasQuery( @@ -127,19 +147,12 @@ export const StripeSyncInstallationPage = () => { { refetchInterval: installing || uninstalling ? 5000 : false } ) - const { data: sslEnforcementConfiguration, isSuccess: isSuccessSslEnforcement } = - useSSLEnforcementQuery({ - projectRef: project?.ref, - }) - const isSSLEnforced = - sslEnforcementConfiguration?.appliedSuccessfully && - sslEnforcementConfiguration?.currentConfig.database - const handleUninstall = useCallback(() => { if (!project?.ref) return uninstallStripeSync({ projectRef: project.ref, + startTime: Date.now(), }) }, [project?.ref, uninstallStripeSync]) @@ -173,12 +186,13 @@ export const StripeSyncInstallationPage = () => { } }, [installError, track]) - // Clear install initiated flag once schema reflects completion or error + // Clear install initiated flag once schema reflects successful completion + // For errors, the flag is cleared when user manually retries (handleOpenInstallSheet) useEffect(() => { - if (isInstallInitiated && installDone) { + if (isInstallInitiated && installDone && upgradeDone && !installError) { setIsInstallInitiated(false) } - }, [isInstallInitiated, installDone]) + }, [isInstallInitiated, installDone, upgradeDone, installError]) // Clear uninstall initiated flag once schema is removed or error useEffect(() => { @@ -195,20 +209,30 @@ export const StripeSyncInstallationPage = () => { error={uninstallError ? 'uninstall' : 'install'} handleUninstall={handleUninstall} handleOpenInstallSheet={handleOpenInstallSheet} + isUpgrade={upgradeAvailable} + installing={installing} + uninstalling={uninstalling} /> ) : null } status={ } actions={ !installed && !uninstalling && !uninstallError ? ( <> - +
{ }, }} > - {installError ? 'Retry installation' : 'Install integration'} + {installError + ? upgradeAvailable + ? 'Retry upgrade' + : 'Retry installation' + : upgradeAvailable + ? 'Upgrade integration' + : 'Install integration'} {installError && ( diff --git a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StatusDisplay.tsx b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StatusDisplay.tsx index da6e6ee44c7..488845d21c8 100644 --- a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StatusDisplay.tsx +++ b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StatusDisplay.tsx @@ -1,4 +1,5 @@ import { AlertCircle, Check, RefreshCwIcon } from 'lucide-react' +import { SchemaInstallationStatus } from 'stripe-experiment-sync/supabase' import { hasInstallError, @@ -6,17 +7,24 @@ import { isInstalled, isInstalling, isUninstalling, - StripeInstallationStatus, } from './stripe-sync-status' export const StatusDisplay = ({ status, isInstallRequested, + isInstallInitiated, isUninstallRequested, + isUninstallInitiated, + isUpgrade, + timedOut, }: { - status: StripeInstallationStatus + status: SchemaInstallationStatus isInstallRequested: boolean + isInstallInitiated: boolean isUninstallRequested: boolean + isUninstallInitiated: boolean + isUpgrade?: boolean + timedOut: boolean }) => { const installed = isInstalled(status) const installError = hasInstallError(status) @@ -24,17 +32,10 @@ export const StatusDisplay = ({ const installInProgress = isInstalling(status) const uninstallInProgress = isUninstalling(status) - const installing = installInProgress || isInstallRequested - const uninstalling = uninstallInProgress || isUninstallRequested + const installing = (installInProgress || isInstallRequested || isInstallInitiated) && !timedOut + const uninstalling = + (uninstallInProgress || isUninstallRequested || isUninstallInitiated) && !timedOut - if (uninstallError) { - return ( - - - Uninstallation error - - ) - } if (uninstalling) { return ( @@ -43,11 +44,11 @@ export const StatusDisplay = ({ ) } - if (installError) { + if (uninstallError) { return ( - Installation error + Uninstallation error ) } @@ -55,7 +56,15 @@ export const StatusDisplay = ({ return ( - Installing... + {isUpgrade ? 'Upgrading...' : 'Installing...'} + + ) + } + if (installError) { + return ( + + + {isUpgrade ? 'Upgrade error' : 'Installation error'} ) } diff --git a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncChangesCard.tsx b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncChangesCard.tsx index 98db11118c3..a0bb8094a39 100644 --- a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncChangesCard.tsx +++ b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncChangesCard.tsx @@ -1,5 +1,6 @@ import { EdgeFunctions } from 'icons' -import { Layers, Table } from 'lucide-react' +import { Layers, Loader2, Table } from 'lucide-react' +import { SchemaInstallationStatus } from 'stripe-experiment-sync/supabase' import { Card, CardContent, cn } from 'ui' import { @@ -10,12 +11,12 @@ import { isInstalling, isUninstallDone, isUninstalling, - StripeInstallationStatus, } from './stripe-sync-status' type StripeSyncChangesCardProps = { - installationStatus: StripeInstallationStatus + installationStatus: SchemaInstallationStatus className?: string + isUpgrade?: boolean } const ListItemClassName = 'flex items-center gap-x-3 py-2 px-3 border-b' @@ -23,6 +24,7 @@ const ListItemClassName = 'flex items-center gap-x-3 py-2 px-3 border-b' export const StripeSyncChangesCard = ({ installationStatus, className, + isUpgrade, }: StripeSyncChangesCardProps) => { const installed = isInstalled(installationStatus) const installError = hasInstallError(installationStatus) @@ -32,53 +34,70 @@ export const StripeSyncChangesCard = ({ const installDone = isInstallDone(installationStatus) const uninstallDone = isUninstallDone(installationStatus) - const title = - uninstallDone || installError + // Special case: installed integration with upgrade available (shown in sheet context) + const isInProgress = installInProgress || uninstallInProgress + const isInstalledWithUpgrade = installed && isUpgrade + + const title = isInstalledWithUpgrade + ? 'This integration will upgrade your Supabase project:' + : uninstallDone || installError ? 'This integration will modify your Supabase project:' - : installInProgress || uninstallInProgress + : isInProgress ? 'This integration is modifying your Supabase project:' : installDone || installed || uninstallError ? 'This integration has modified your Supabase project:' : '' - const dbLine = - uninstallDone || installError - ? 'Creates a new database schema named ' + const dbLine = isInstalledWithUpgrade + ? 'Upgrades the database schema named' + : uninstallDone || installError + ? 'Creates a new database schema named' : installInProgress - ? 'Creating a new database schema named ' + ? isUpgrade + ? 'Upgrading database schema named' + : 'Creating a new database schema named' : installDone || installed || uninstallError - ? 'Created a new database schema named ' + ? 'Created a new database schema named' : uninstallInProgress - ? 'Dropping database schema named ' + ? 'Dropping database schema named' : '' - const tableAndViewLine = - uninstallDone || installError - ? 'Creates tables and views in the ' + const tableAndViewLine = isInstalledWithUpgrade + ? 'Upgrades tables and views in the' + : uninstallDone || installError + ? 'Creates tables and views in the' : installInProgress - ? 'Creating tables and views in the ' + ? isUpgrade + ? 'Upgrading tables and views in the' + : 'Creating tables and views in the' : installDone || installed || uninstallError - ? 'Created tables and views in the ' + ? 'Created tables and views in the' : uninstallInProgress - ? 'Dropping tables and views in the ' + ? 'Dropping tables and views in the' : '' - const edgeFunctionsLine = - uninstallDone || installError + const edgeFunctionsLine = isInstalledWithUpgrade + ? 'Upgrades Edge Functions to handle incoming webhooks from Stripe' + : uninstallDone || installError ? 'Deploys Edge Functions to handle incoming webhooks from Stripe' : installInProgress - ? 'Deploying Edge Functions to handle incoming webhooks from Stripe' + ? isUpgrade + ? 'Upgrading Edge Functions to handle incoming webhooks from Stripe' + : 'Deploying Edge Functions to handle incoming webhooks from Stripe' : installDone || installed || uninstallError ? 'Deployed Edge Functions to handle incoming webhooks from Stripe' : uninstallInProgress ? 'Undeploying Edge Functions to handle incoming webhooks from Stripe' : '' - const scheduleLine = - uninstallDone || installError + const scheduleLine = isInstalledWithUpgrade + ? 'Upgrades automatic Stripe data syncs using Supabase Queues' + : uninstallDone || installError ? 'Schedules automatic Stripe data syncs using Supabase Queues' : installInProgress - ? 'Scheduling automatic Stripe data syncs using Supabase Queues' + ? isUpgrade + ? 'Upgrading automatic Stripe data syncs using Supabase Queues' + : 'Scheduling automatic Stripe data syncs using Supabase Queues' : installDone || installed || uninstallError ? 'Scheduled automatic Stripe data syncs using Supabase Queues' : uninstallInProgress @@ -92,28 +111,44 @@ export const StripeSyncChangesCard = ({
  • - + {isInProgress ? ( + + ) : ( +
    + )} {dbLine} stripe
  • -
  • + {isInProgress ? ( + + ) : ( +
    + )} {tableAndViewLine} stripe schema for synced Stripe data
  • - + {isInProgress ? ( + + ) : ( + + )} {edgeFunctionsLine}
  • - + {isInProgress ? ( + + ) : ( + + )} {scheduleLine}
  • diff --git a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncSettingsPage.tsx b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncSettingsPage.tsx index 311e61e292c..5114ef2c1a2 100644 --- a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncSettingsPage.tsx +++ b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncSettingsPage.tsx @@ -1,10 +1,9 @@ import { formatRelative } from 'date-fns' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { BadgeCheck, RefreshCwIcon, Table2 } from 'lucide-react' +import { BadgeCheck, RefreshCwIcon } from 'lucide-react' import Link from 'next/link' -import { useRouter } from 'next/router' -import { Button, Card, CardContent } from 'ui' -import { Admonition } from 'ui-patterns' +import { Button, Card, CardContent, CardHeader, CardTitle } from 'ui' +import { Admonition, ShimmeringLoader, TimestampInfo } from 'ui-patterns' import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, @@ -19,13 +18,11 @@ import { isInstalled, isSyncRunning, isUninstalling } from './stripe-sync-status import { useStripeSyncStatus } from '@/components/interfaces/Integrations/templates/StripeSyncEngine/useStripeSyncStatus' export const StripeSyncSettingsPage = () => { - const router = useRouter() const { data: project } = useSelectedProjectQuery() const { - parsedSchema: { status: installationStatus }, + schemaComment: { status: installationStatus }, syncState, - isLoading: isLoadingInstallationStatus, } = useStripeSyncStatus({ projectRef: project?.ref, connectionString: project?.connectionString, @@ -33,79 +30,92 @@ export const StripeSyncSettingsPage = () => { const installed = isInstalled(installationStatus) const isSyncing = isSyncRunning(syncState) const uninstalling = isUninstalling(installationStatus) - const tableEditorUrl = `/project/${project?.ref}/editor?schema=stripe` + + if (!installed || uninstalling) { + return ( + + + + + + ) + } return ( - {syncState && installed && !uninstalling && ( - - - - Sync Status - - - - -
    - {isSyncing ? ( - <> -
    - -
    Sync in progress...
    -
    -
    - Started{' '} - {syncState.started_at - ? formatRelative(new Date(syncState.started_at), new Date()) - : 'recently'} -
    - - ) : ( - <> -
    - -
    All up to date
    - -
    -
    - Last synced{' '} - {syncState.closed_at - ? formatRelative(new Date(syncState.closed_at), new Date()) - : 'recently'} -
    - - )} -
    -
    -
    -
    - )} - + - Stripe Schema + Manage Stripe data Access and manage the synced Stripe data in your database. - + + + + {!syncState ? ( + + ) : ( +
    + {isSyncing ? ( + <> +
    + +

    Sync in progress

    +
    + {syncState.started_at && ( +

    + Started{' '} + +

    + )} + + ) : ( + <> +
    + +

    All up to date

    +
    + {syncState.closed_at && ( +

    + Last synced{' '} + +

    + )} + + )} +
    + )} +
    +
    -
    - -
    -
    Open Stripe schema in Table Editor
    -

    - The Stripe Sync Engine stores all synced data in the{' '} - stripe schema. You can - view and query this data directly in the Table Editor. -

    -
    +
    +
    View Stripe data in Table Editor
    +

    + The Stripe Sync Engine stores all synced data in the{' '} + stripe schema. You can + view and query this data directly in the Table Editor. +

    + - ); + ) } const UserDetailsButton = () => { - const [response, setResponse] = useState(null); + const [response, setResponse] = useState(null) const handleClick = async () => { - const response = await client.api.user.$get(); - const data = await response.json(); - const headers = Array.from(response.headers.entries()).reduce< - Record - >((acc, [key, value]) => { - acc[key] = value; - return acc; - }, {}); + const response = await client.api.user.$get() + const data = await response.json() + const headers = Array.from(response.headers.entries()).reduce>( + (acc, [key, value]) => { + acc[key] = value + return acc + }, + {} + ) const fullResponse = { url: response.url, status: response.status, headers, body: data, - }; - setResponse(JSON.stringify(fullResponse, null, 2)); - }; + } + setResponse(JSON.stringify(fullResponse, null, 2)) + } return (
    @@ -107,8 +103,8 @@ const UserDetailsButton = () => { {response &&
    {response}
    }
    - ); -}; + ) +} -const root = document.getElementById('root')!; -render(, root); +const root = document.getElementById('root')! +render(, root) diff --git a/examples/auth/hono-full/src/index.tsx b/examples/auth/hono-full/src/index.tsx index 5acaf4801f8..8560a3bcabf 100644 --- a/examples/auth/hono-full/src/index.tsx +++ b/examples/auth/hono-full/src/index.tsx @@ -1,42 +1,42 @@ -import { Hono } from 'hono'; -import { getSupabase, supabaseMiddleware } from './middleware/auth.middleware'; +import { Hono } from 'hono' +import { getSupabase, supabaseMiddleware } from './middleware/auth.middleware' -const app = new Hono(); -app.use('*', supabaseMiddleware()); +const app = new Hono() +app.use('*', supabaseMiddleware()) const routes = app.get('/api/user', async (c) => { - const supabase = getSupabase(c); - const { data, error } = await supabase.auth.getUser(); + const supabase = getSupabase(c) + const { data, error } = await supabase.auth.getUser() - if (error) console.log('error', error); + if (error) console.log('error', error) if (!data?.user) { return c.json({ message: 'You are not logged in.', - }); + }) } return c.json({ message: 'You are logged in!', userId: data.user, - }); -}); + }) +}) app.get('/signout', async (c) => { - const supabase = getSupabase(c); - await supabase.auth.signOut(); - console.log('Signed out server-side!'); - return c.redirect('/'); -}); + const supabase = getSupabase(c) + await supabase.auth.signOut() + console.log('Signed out server-side!') + return c.redirect('/') +}) app.get('/countries', async (c) => { - const supabase = getSupabase(c); - const { data, error } = await supabase.from('countries').select('*'); - if (error) console.log(error); - return c.json(data); -}); + const supabase = getSupabase(c) + const { data, error } = await supabase.from('countries').select('*') + if (error) console.log(error) + return c.json(data) +}) -export type AppType = typeof routes; +export type AppType = typeof routes app.get('/', (c) => { return c.html( @@ -44,10 +44,7 @@ app.get('/', (c) => { - + {import.meta.env.PROD ? (