mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
/login and /signup draw "Continue with wallet" and "Continue with phone" after Google and GitHub, each only where IAM's methods say it is on. Wallet: where IAM signs a wallet in (@hanzo/iam 0.21.37 `wallet`) and the browser injects an EIP-1193 wallet; the press is loginWithWallet (IAM's challenge, personal_sign of its EIP-4361 text, IAM's verify) and ends as a password's sign-in ends. A wallet IAM has not seen is made an account in an org of its own, with no email. Phone: where IAM says it sends codes (`code`) and has answered. The number is read in its country with the SMS consent ticked, then IAM's code flow. IAM's address lookup answers an email address only, so a number is texted as any is and the account that holds it is signed in by the code. Settings -> General -> Sign in another way adds an email or a phone to the signed-in account through IAM's own proof (addAddress, PUT /v1/iam/address), drawn only where IAM's methods say `address`. Same account, org, keys and balance; nothing is created, so nothing earns twice. Gates: wallet.spec (a throwaway secp256k1 key behind an injected provider), phone.spec, address.spec. login.spec expects the phone after the providers.
69 lines
2.7 KiB
TypeScript
69 lines
2.7 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* The terms, agreed once, by an account that has accepted none on record: the
|
|
* one a provider made a moment ago (/onboarding), or an address a code proved
|
|
* that holds no account yet (the sign-in panel's last step). IAM records the
|
|
* acceptance; this page asks for it and says nothing else. It is the focused
|
|
* page (components/shell/focus) like every page of the way in.
|
|
*/
|
|
|
|
import { useId, useState } from 'react'
|
|
import { useI18n } from '@hanzo/i18n/react'
|
|
import { Checkbox, Text, XStack, YStack } from '@hanzo/ui'
|
|
import { TextLink } from '@hanzo/ui/marketing'
|
|
import { Wrong } from '@/components/auth/steps'
|
|
import { Focus } from '@/components/shell/focus'
|
|
import { Pill, TILE } from './look'
|
|
|
|
export function Terms({
|
|
email,
|
|
busy,
|
|
wrong,
|
|
onCreate,
|
|
onOther,
|
|
title,
|
|
say,
|
|
}: {
|
|
/** The verified address the account is for; empty for an account a wallet made. */
|
|
email: string
|
|
busy: boolean
|
|
wrong: string | null
|
|
onCreate: () => void
|
|
/** Another address: the page this one was reached from decides what that means. */
|
|
onOther: () => void
|
|
/** The page's heading, in the reader's language: /login names a new address's lack of an account here. */
|
|
title?: string
|
|
/** A line under it, in place of the address alone. */
|
|
say?: string
|
|
}) {
|
|
const { t } = useI18n()
|
|
const [agree, setAgree] = useState(false)
|
|
const id = useId()
|
|
return (
|
|
<Focus label={t('Terms')} title={title ?? t('Create your account')} line={say ?? (email || undefined)}>
|
|
<YStack gap="$4">
|
|
<XStack gap="$3" items="flex-start" p="$5" {...TILE} rounded={12}>
|
|
<Checkbox id={id} checked={agree} onCheckedChange={(v) => setAgree(v === true)} mt="$1" />
|
|
<Text render="label" htmlFor={id} flex={1} fontSize="$4" lineHeight="1.375rem" color="var(--foreground)">
|
|
{t("I agree to Hanzo's {terms} and {use} and confirm that I am at least 18 years of age.", {
|
|
terms: <TextLink href="/terms">{t('Terms of Service')}</TextLink>,
|
|
use: <TextLink href="/aup">{t('Acceptable Use Policy')}</TextLink>,
|
|
})}
|
|
</Text>
|
|
</XStack>
|
|
<Wrong text={wrong} />
|
|
<Pill fill disabled={!agree || busy} onPress={onCreate}>
|
|
{busy ? t('One moment…') : t('Create account')}
|
|
</Pill>
|
|
<XStack justify="center">
|
|
{/* An account with no address (one a wallet made) is left for another account, not another address. */}
|
|
<TextLink onPress={onOther} disabled={busy} display="inline-flex" items="center" minH={44}>
|
|
{email ? t('Use a different email') : t('Use a different account')}
|
|
</TextLink>
|
|
</XStack>
|
|
</YStack>
|
|
</Focus>
|
|
)
|
|
}
|