From 85dc815b7dd4d807d68c0bfe89bfc310bbb58ff5 Mon Sep 17 00:00:00 2001 From: Julien Genestoux Date: Fri, 7 Jun 2024 16:52:32 -0400 Subject: [PATCH] Checkout oauth simplified (#14019) * wip * cleanup --- .../interface/checkout/Connect/Confirm.tsx | 68 -------- .../checkout/Connect/ConfirmConnect.tsx | 115 +++++++++++++ .../checkout/Connect/connectMachine.ts | 60 ------- .../interface/checkout/Connect/index.tsx | 154 +++++++++--------- .../interface/checkout/Connected.tsx | 2 +- .../interface/checkout/main/ConnectPage.tsx | 1 - 6 files changed, 190 insertions(+), 210 deletions(-) delete mode 100644 unlock-app/src/components/interface/checkout/Connect/Confirm.tsx create mode 100644 unlock-app/src/components/interface/checkout/Connect/ConfirmConnect.tsx delete mode 100644 unlock-app/src/components/interface/checkout/Connect/connectMachine.ts diff --git a/unlock-app/src/components/interface/checkout/Connect/Confirm.tsx b/unlock-app/src/components/interface/checkout/Connect/Confirm.tsx deleted file mode 100644 index 1cccb7d722e..00000000000 --- a/unlock-app/src/components/interface/checkout/Connect/Confirm.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react' -import { OAuthConfig } from '~/unlockTypes' -import { PaywallConfigType } from '@unlock-protocol/core' -import { useAuth } from '~/contexts/AuthenticationContext' -import { useCheckoutCommunication } from '~/hooks/useCheckoutCommunication' -import { useSIWE } from '~/hooks/useSIWE' -import { generateNonce } from 'siwe' -import { ConnectPage } from '../main/ConnectPage' - -interface Props { - paywallConfig?: PaywallConfigType - oauthConfig: OAuthConfig - onClose(params?: Record): void - communication: ReturnType -} - -export function ConfirmConnect({ - oauthConfig, - paywallConfig, - onClose, - communication, -}: Props) { - const { siweSign, signature, message } = useSIWE() - const { account, connected } = useAuth() - - const onSuccess = (signature: string, message: string) => { - const code = Buffer.from( - JSON.stringify({ - d: message, - s: signature, - }) - ).toString('base64') - communication?.emitUserInfo({ - address: account, - message: message, - signedMessage: signature, - }) - onClose({ - code, - state: oauthConfig.state, - }) - } - - const onSignIn = async () => { - if (signature && message) { - onSuccess(signature, message) - } else { - const result = await siweSign( - generateNonce(), - paywallConfig?.messageToSign || '', - { - resources: [new URL('https://' + oauthConfig.clientId).toString()], - } - ) - if (result) { - onSuccess(result.signature, result.message) - } - } - } - - return ( - - ) -} diff --git a/unlock-app/src/components/interface/checkout/Connect/ConfirmConnect.tsx b/unlock-app/src/components/interface/checkout/Connect/ConfirmConnect.tsx new file mode 100644 index 00000000000..413a0222ba4 --- /dev/null +++ b/unlock-app/src/components/interface/checkout/Connect/ConfirmConnect.tsx @@ -0,0 +1,115 @@ +import React, { Fragment } from 'react' +import { OAuthConfig } from '~/unlockTypes' +import { PaywallConfigType } from '@unlock-protocol/core' +import { useAuth } from '~/contexts/AuthenticationContext' +import { useCheckoutCommunication } from '~/hooks/useCheckoutCommunication' +import { useSIWE } from '~/hooks/useSIWE' +import { generateNonce } from 'siwe' +import { PoweredByUnlock } from '../PoweredByUnlock' +import { Button } from '@unlock-protocol/ui' + +interface Props { + className: string + paywallConfig?: PaywallConfigType + oauthConfig: OAuthConfig + onClose(params?: Record): void + communication: ReturnType +} + +export function ConfirmConnect({ + className, + oauthConfig, + paywallConfig, + onClose, + communication, +}: Props) { + const { siweSign, signature, message } = useSIWE() + const { account } = useAuth() + + const onSuccess = (signature: string, message: string) => { + const code = Buffer.from( + JSON.stringify({ + d: message, + s: signature, + }) + ).toString('base64') + communication?.emitUserInfo({ + address: account, + message: message, + signedMessage: signature, + }) + onClose({ + code, + state: oauthConfig.state, + }) + } + + const onConfirm = async () => { + if (signature && message) { + onSuccess(signature, message) + } else { + const result = await siweSign( + generateNonce(), + paywallConfig?.messageToSign || '', + { + resources: [new URL('https://' + oauthConfig.clientId).toString()], + } + ) + if (result) { + onSuccess(result.signature, result.message) + } + } + } + + return ( + +
+
+
+ Are you sure you want to connect to{' '} + + {oauthConfig.clientId.length > 20 + ? oauthConfig.clientId.slice(0, 17) + '...' + : oauthConfig.clientId}{' '} + +
+ +
+

If you approve, this website may:

+
    +
  • ✅ See your wallet balance and activity
  • +
  • ✅ Identify what memberships you own
  • +
+
+
+ {' '} +

But it will not be able to:

+
    +
  • ❌ move your funds
  • +
  • ❌ transfer your memberships
  • +
+
+
+ + +
+
+
+
+ +
+
+ ) +} diff --git a/unlock-app/src/components/interface/checkout/Connect/connectMachine.ts b/unlock-app/src/components/interface/checkout/Connect/connectMachine.ts deleted file mode 100644 index 434651a74aa..00000000000 --- a/unlock-app/src/components/interface/checkout/Connect/connectMachine.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Actor, ActorRefFrom, createMachine } from 'xstate' - -interface UnlockAccountEvent { - type: 'UNLOCK_ACCOUNT' -} -interface ConnectEvent { - type: 'CONNECT' -} - -interface SignInEvent { - type: 'SIGN_IN' -} - -interface DisconnectEvent { - type: 'DISCONNECT' -} - -interface BackEvent { - type: 'BACK' -} - -type ConnectMachineEvents = - | UnlockAccountEvent - | BackEvent - | DisconnectEvent - | ConnectEvent - | SignInEvent - -export const connectMachine = createMachine( - { - id: 'connect', - types: { - typegen: {} as import('./connectMachine.typegen').Typegen0, - events: {} as ConnectMachineEvents, - }, - on: { - DISCONNECT: '.CONNECT', - }, - initial: 'CONNECT', - states: { - CONNECT: { - on: { - UNLOCK_ACCOUNT: { - target: 'SIGN_IN', - }, - }, - }, - SIGN_IN: { - on: { - CONNECT: 'CONNECT', - }, - }, - }, - }, - {} -) - -export type ConnectService = - | Actor - | ActorRefFrom diff --git a/unlock-app/src/components/interface/checkout/Connect/index.tsx b/unlock-app/src/components/interface/checkout/Connect/index.tsx index a0bd649484e..323425561ca 100644 --- a/unlock-app/src/components/interface/checkout/Connect/index.tsx +++ b/unlock-app/src/components/interface/checkout/Connect/index.tsx @@ -1,12 +1,11 @@ -import React, { useCallback, useMemo } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { useCheckoutCommunication } from '~/hooks/useCheckoutCommunication' import type { OAuthConfig } from '~/unlockTypes' -import { connectMachine } from './connectMachine' -import { CheckoutHead, TopNavigation } from '../Shell' -import { useMachine } from '@xstate/react' -import ConnectWalletComponent from '../../connect/ConnectWalletComponent' -import { generateNonce } from 'siwe' -import { useSIWE } from '~/hooks/useSIWE' + +import { ConfirmConnect } from './ConfirmConnect' +import { Step, StepButton, StepTitle } from '../Stepper' +import { ConnectPage } from '../main/ConnectPage' +import { TopNavigation } from '../Shell' import { useAuth } from '~/contexts/AuthenticationContext' import { PaywallConfigType } from '@unlock-protocol/core' @@ -15,17 +14,58 @@ interface Props { paywallConfig: PaywallConfigType } -export function Connect({ paywallConfig, oauthConfig }: Props) { - const communication = useCheckoutCommunication() +interface StepperProps { + state: string +} + +export const Stepper = ({ state }: StepperProps) => { + const steps = ['connect', 'confirm'] + const { deAuthenticate } = useAuth() + + const [currentState, setCurentState] = useState(steps.indexOf(state)) + + useEffect(() => { + setCurentState(steps.indexOf(state)) + }, [state, setCurentState]) - // @ts-expect-error - The types returned by 'resolveState(...)' are incompatible between these types - const [state, send, connectService] = useMachine(connectMachine) + return ( +
+ {steps.map((step: string, idx: number) => { + const isActive = step === steps[currentState] + if (isActive) { + return ( + <> + {idx + 1} + {steps[idx]} + + ) + } else if (currentState > idx) { + return ( + { + setCurentState(idx) + deAuthenticate() + }} + > + {idx + 1} + + ) + } else { + return {idx + 1} + } + })} +
+ ) +} + +export function Connect({ oauthConfig }: Props) { + const communication = useCheckoutCommunication() + const { account } = useAuth() + const [state, setState] = useState('connect') const onClose = useCallback( (params: Record = {}) => { - // Reset the Paywall State! - connectService.send({ type: 'DISCONNECT' }) - if (oauthConfig.redirectUri) { const redirectURI = new URL(oauthConfig.redirectUri) @@ -39,80 +79,34 @@ export function Connect({ paywallConfig, oauthConfig }: Props) { communication.emitCloseModal() } }, - [oauthConfig.redirectUri, communication, connectService] + [oauthConfig.redirectUri, communication] ) - const onBack = useMemo(() => { - const unlockAccount = state.children?.unlockAccount - const canBackInUnlockAccountService = unlockAccount - ?.getSnapshot() - .can({ type: 'BACK' }) - const canBack = state.can({ type: 'BACK' }) - if (canBackInUnlockAccountService) { - return () => unlockAccount.send({ type: 'BACK' }) - } - if (canBack) { - return () => connectService.send({ type: 'BACK' }) - } - return undefined - }, [state, connectService]) - - const { siweSign, signature, message } = useSIWE() - const { account } = useAuth() - - const onSuccess = (signature: string, message: string) => { - const code = Buffer.from( - JSON.stringify({ - d: message, - s: signature, - }) - ).toString('base64') - communication?.emitUserInfo({ - address: account, - message: message, - signedMessage: signature, - }) - onClose({ - code, - state: oauthConfig.state, - }) - } - - const onSignIn = async () => { - if (signature && message) { - onSuccess(signature, message) + useEffect(() => { + if (!account) { + return setState('connect') } else { - const result = await siweSign( - generateNonce(), - paywallConfig?.messageToSign || '', - { - resources: [new URL('https://' + oauthConfig.clientId).toString()], - } - ) - if (result) { - onSuccess(result.signature, result.message) - } + return setState('confirm') } - } + }, [account]) return (
- - -
-

- - {oauthConfig.clientId.length > 20 - ? oauthConfig.clientId.slice(0, 17) + '...' - : oauthConfig.clientId} - {' '} - wants you to sign in -

-
-
-
- + +
+
+ +
+ {!account && } + {account && ( + + )}
) } diff --git a/unlock-app/src/components/interface/checkout/Connected.tsx b/unlock-app/src/components/interface/checkout/Connected.tsx index 487b5891e0c..4e2e8e826a7 100644 --- a/unlock-app/src/components/interface/checkout/Connected.tsx +++ b/unlock-app/src/components/interface/checkout/Connected.tsx @@ -80,7 +80,7 @@ export function Connected({ service }: ConnectedCheckoutProps) { return ( <> - + ) } diff --git a/unlock-app/src/components/interface/checkout/main/ConnectPage.tsx b/unlock-app/src/components/interface/checkout/main/ConnectPage.tsx index 83505877e5d..d387a0ec5b1 100644 --- a/unlock-app/src/components/interface/checkout/main/ConnectPage.tsx +++ b/unlock-app/src/components/interface/checkout/main/ConnectPage.tsx @@ -4,7 +4,6 @@ import ConnectWalletComponent from '../../connect/ConnectWalletComponent' interface ConnectPageProps { style: string - connected: string | undefined onNext?: () => void }