Skip to content
13 changes: 13 additions & 0 deletions apps/dialog/src/lib/Referrer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { useQuery } from '@tanstack/react-query'
import * as TrustedHosts from 'porto/trusted-hosts'
import * as React from 'react'

import * as Dialog from '~/lib/Dialog'

export function useTrusted(scopes?: readonly TrustedHosts.Scope[] | undefined) {
const referrer = Dialog.useStore((state) => state.referrer)
const verifyStatus = useVerify()

return React.useMemo(() => {
if (!referrer?.url?.hostname) return false
if (verifyStatus.data?.status === 'whitelisted') return true
return TrustedHosts.includes(referrer?.url?.hostname, scopes)
}, [referrer, verifyStatus.data?.status, scopes])
}

export function useVerify() {
const hostname = Dialog.useStore((state) => state.referrer?.url?.hostname)

Expand Down
2 changes: 1 addition & 1 deletion apps/dialog/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ if (import.meta.env.PROD) {
Sentry.init({
dsn: 'https://457697aad11614a3f667c8e61f6b9e20@o4509056062849024.ingest.us.sentry.io/4509080285741056',
enabled: document.referrer
? TrustedHosts.hostnames.includes(new URL(document.referrer).hostname)
? TrustedHosts.includes(new URL(document.referrer).hostname)
: true,
environment: Env.get(),
integrations: [
Expand Down
38 changes: 33 additions & 5 deletions apps/dialog/src/routes/-components/Email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@ import { Permissions } from '~/routes/-components/Permissions'
import { StringFormatter } from '~/utils'
import LucideHaze from '~icons/lucide/haze'
import IconScanFace from '~icons/porto/scan-face'
import { SkipPreviews } from './SkipPreviews'

export function Email(props: Email.Props) {
const { defaultValue = '', onApprove, permissions, status } = props
const {
allowBlindSigning,
defaultValue = '',
onApprove,
permissions,
status,
} = props

const [actions, setActions] = React.useState<
readonly ('sign-in' | 'sign-up')[]
>(props.actions ?? ['sign-in', 'sign-up'])

const enableBlindSigningRef = React.useRef<HTMLInputElement>(null)

const account = Hooks.useAccount(porto)
const email = Dialog.useStore((state) =>
account?.address
Expand Down Expand Up @@ -46,8 +55,9 @@ export function Email(props: Email.Props) {
event.preventDefault()
const formData = new FormData(event.target as HTMLFormElement)
const email = formData.get('email')?.toString()
const enableBlindSigning = enableBlindSigningRef.current?.checked
setMode('sign-up')
onApprove({ email, signIn: false })
onApprove({ email, enableBlindSigning, signIn: false })
},
[onApprove],
)
Expand Down Expand Up @@ -82,7 +92,16 @@ export function Email(props: Email.Props) {
/>
</Layout.Header>

<Permissions title="Permissions requested" {...permissions} />
{permissions ? (
<Permissions title="Permissions requested" {...permissions} />
) : allowBlindSigning ? (
<>
<div className="px-3">
<SkipPreviews ref={enableBlindSigningRef} />
</div>
<div className="h-4" />
</>
) : null}

<div className="group flex min-h-[48px] w-full flex-col items-center justify-center space-y-3 px-3 pb-3">
{actions.includes('sign-in') && (
Expand All @@ -92,8 +111,9 @@ export function Email(props: Email.Props) {
icon={<IconScanFace className="size-5.25" />}
loading={signingIn && 'Signing in…'}
onClick={() => {
const enableBlindSigning = enableBlindSigningRef.current?.checked
setMode('sign-in')
onApprove({ signIn: true })
onApprove({ enableBlindSigning, signIn: true })
}}
type="button"
variant="primary"
Expand Down Expand Up @@ -173,7 +193,13 @@ export function Email(props: Email.Props) {
<TextButton
color="link"
onClick={() => {
onApprove({ selectAccount: true, signIn: true })
const enableBlindSigning =
enableBlindSigningRef.current?.checked
onApprove({
enableBlindSigning,
selectAccount: true,
signIn: true,
})
}}
>
Switch
Expand All @@ -198,10 +224,12 @@ export function Email(props: Email.Props) {
export namespace Email {
export type Props = {
actions?: readonly ('sign-in' | 'sign-up')[]
allowBlindSigning?: boolean
defaultValue?: string | undefined
onApprove: (p: {
email?: string
selectAccount?: boolean
enableBlindSigning?: boolean
signIn?: boolean
}) => void
permissions?: Permissions.Props
Expand Down
29 changes: 24 additions & 5 deletions apps/dialog/src/routes/-components/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import { porto } from '~/lib/Porto'
import { Layout } from '~/routes/-components/Layout'
import { Permissions } from '~/routes/-components/Permissions'
import LucideLogIn from '~icons/lucide/log-in'
import { SkipPreviews } from './SkipPreviews'

export function SignIn(props: SignIn.Props) {
const { onApprove, permissions, status } = props
const { allowBlindSigning, onApprove, permissions, status } = props

const enableBlindSigningRef = React.useRef<HTMLInputElement>(null)

const account = Hooks.useAccount(porto)
const hostname = Dialog.useStore((state) => state.referrer?.url?.hostname)
Expand Down Expand Up @@ -38,7 +41,16 @@ export function SignIn(props: SignIn.Props) {
/>
</Layout.Header>

<Permissions title="Permissions requested" {...permissions} />
{permissions ? (
<Permissions title="Permissions requested" {...permissions} />
) : allowBlindSigning ? (
<>
<div className="px-3">
<SkipPreviews ref={enableBlindSigningRef} />
</div>
<div className="h-4" />
</>
) : null}

<Layout.Footer>
<Layout.Footer.Actions>
Expand All @@ -47,8 +59,9 @@ export function SignIn(props: SignIn.Props) {
disabled={status === 'loading' || signingIn}
loading={signingUp && 'Signing up…'}
onClick={() => {
const enableBlindSigning = enableBlindSigningRef.current?.checked
setMode('sign-up')
onApprove({ signIn: false })
onApprove({ enableBlindSigning, signIn: false })
}}
width="grow"
>
Expand All @@ -59,8 +72,9 @@ export function SignIn(props: SignIn.Props) {
disabled={status === 'loading' || signingUp}
loading={signingIn && 'Signing in…'}
onClick={() => {
const enableBlindSigning = enableBlindSigningRef.current?.checked
setMode('sign-in')
onApprove({ signIn: true })
onApprove({ enableBlindSigning, signIn: true })
}}
variant="primary"
width="grow"
Expand All @@ -82,7 +96,12 @@ export function SignIn(props: SignIn.Props) {

declare namespace SignIn {
type Props = {
onApprove: (p: { signIn?: boolean; selectAccount?: boolean }) => void
allowBlindSigning?: boolean
onApprove: (p: {
enableBlindSigning?: boolean
signIn?: boolean
selectAccount?: boolean
}) => void
permissions?: Permissions.Props
status?: 'loading' | 'responding' | undefined
}
Expand Down
46 changes: 41 additions & 5 deletions apps/dialog/src/routes/-components/SignUp.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { Button, LightDarkImage, Screen } from '@porto/ui'
import * as React from 'react'
import { useState } from 'react'
import * as Dialog from '~/lib/Dialog'
import { Layout } from '~/routes/-components/Layout'
import { Permissions } from '~/routes/-components/Permissions'
import LucideLogIn from '~icons/lucide/log-in'
import Question from '~icons/mingcute/question-line'
import { SkipPreviews } from './SkipPreviews'

export function SignUp(props: SignUp.Props) {
const { enableSignIn, onApprove, onReject, permissions, status } = props
const {
allowBlindSigning,
enableSignIn,
onApprove,
onReject,
permissions,
status,
} = props

const enableBlindSigningRef = React.useRef<HTMLInputElement>(null)

const [showLearn, setShowLearn] = useState(false)

Expand Down Expand Up @@ -45,15 +56,32 @@ export function SignUp(props: SignUp.Props) {
/>
</Layout.Header>

<Permissions title="Permissions requested" {...permissions} />
{permissions ? (
<Permissions title="Permissions requested" {...permissions} />
) : allowBlindSigning ? (
<>
<div className="px-3">
<SkipPreviews ref={enableBlindSigningRef} />
</div>
<div className="h-4" />
</>
) : null}

<Layout.Footer>
<Layout.Footer.Actions>
{enableSignIn ? (
<Button
data-testid="sign-in"
disabled={status === 'loading'}
onClick={() => onApprove({ selectAccount: true, signIn: true })}
onClick={() => {
const enableBlindSigning =
enableBlindSigningRef.current?.checked
onApprove({
enableBlindSigning,
selectAccount: true,
signIn: true,
})
}}
>
Sign in
</Button>
Expand All @@ -67,7 +95,10 @@ export function SignUp(props: SignUp.Props) {
data-testid="sign-up"
disabled={status === 'loading'}
loading={status === 'responding' && 'Signing up…'}
onClick={() => onApprove({ signIn: false })}
onClick={() => {
const enableBlindSigning = enableBlindSigningRef.current?.checked
onApprove({ enableBlindSigning, signIn: false })
}}
variant="primary"
width="grow"
>
Expand All @@ -81,8 +112,13 @@ export function SignUp(props: SignUp.Props) {

export namespace SignUp {
export type Props = {
allowBlindSigning?: boolean
enableSignIn?: boolean
onApprove: (p: { signIn?: boolean; selectAccount?: boolean }) => void
onApprove: (p: {
enableBlindSigning?: boolean
signIn?: boolean
selectAccount?: boolean
}) => void
onReject: () => void
permissions?: Permissions.Props
status?: 'loading' | 'responding' | undefined
Expand Down
87 changes: 87 additions & 0 deletions apps/dialog/src/routes/-components/SkipPreviews.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Checkbox } from '@ariakit/react'
import type * as React from 'react'

export function SkipPreviews(props: SkipPreviews.Props) {
const { ref } = props
return (
<label htmlFor="skip-previews">
<div className="relative rounded-[10px] border border-gray4 bg-gray3 p-3">
<div className="flex items-center gap-1.5">
<Checkbox id="skip-previews" ref={ref} />
<span className="font-medium text-[15px] text-gray12">
Skip previews
</span>
</div>
<div className="h-1" />
<p className="text-[14px] text-gray10">
Move faster through apps by skipping transaction previews &
confirmations
</p>
<Illustration className="absolute top-[12px] right-0 h-[calc(100%-(24px))]" />
</div>
</label>
)
}

export declare namespace SkipPreviews {
export type Props = {
ref: React.RefObject<HTMLInputElement | null>
}
}

export function Illustration({ className }: { className?: string }) {
return (
<svg
className={className}
fill="none"
viewBox="0 0 61 51"
xmlns="http://www.w3.org/2000/svg"
>
<title>Skip previews illustration</title>
<path
className="fill-gray5 dark:fill-gray2"
d="M6 9C6 4.02944 10.0294 0 15 0H61V51H15C10.0294 51 6 46.9706 6 42V9Z"
/>
<rect
className="fill-white dark:fill-gray4"
height="35.0435"
rx="3"
width="31"
x="22"
y="8.47803"
/>
<rect
className="fill-gray3"
height="17.5217"
rx="2"
width="25.6087"
x="24.6953"
y="16.5654"
/>
<rect
className="fill-gray3"
height="2.69565"
rx="1.34783"
width="13.4783"
x="24.6953"
y="11.1738"
/>
<rect
className="fill-red4"
height="4.04348"
rx="2.02174"
width="12.1304"
x="24.6953"
y="36.7827"
/>
<rect
className="fill-green4"
height="4.04348"
rx="2.02174"
width="12.1304"
x="38.1738"
y="36.7827"
/>
</svg>
)
}
8 changes: 1 addition & 7 deletions apps/dialog/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
useLocation,
} from '@tanstack/react-router'
import { Actions, Hooks } from 'porto/remote'
import { hostnames } from 'porto/trusted-hosts'
import * as React from 'react'
import * as Dialog from '~/lib/Dialog'
import { EnsureVisibility } from '~/lib/IntersectionObserver'
Expand Down Expand Up @@ -49,12 +48,7 @@ function RouteComponent() {
const customTheme = Dialog.useStore((state) => state.customTheme)
const display = Dialog.useStore((state) => state.display)
const verifyStatus = Referrer.useVerify()

const trusted = React.useMemo(() => {
if (!referrer?.url?.hostname) return false
if (verifyStatus.data?.status === 'whitelisted') return true
return hostnames.includes(referrer?.url?.hostname)
}, [referrer, verifyStatus.data?.status])
const trusted = Referrer.useTrusted(['allow-safari-iframe'])

const { domain, subdomain, icon, url } = React.useMemo(() => {
const hostnameParts = referrer?.url?.hostname.split('.').slice(-3)
Expand Down
Loading
Loading