about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/auth/create/CaptchaWebView.tsx86
-rw-r--r--src/view/com/auth/create/CaptchaWebView.web.tsx61
-rw-r--r--src/view/com/auth/create/Policies.tsx97
-rw-r--r--src/view/com/auth/create/state.ts298
4 files changed, 0 insertions, 542 deletions
diff --git a/src/view/com/auth/create/CaptchaWebView.tsx b/src/view/com/auth/create/CaptchaWebView.tsx
deleted file mode 100644
index 06b605e4d..000000000
--- a/src/view/com/auth/create/CaptchaWebView.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import React from 'react'
-import {WebView, WebViewNavigation} from 'react-native-webview'
-import {ShouldStartLoadRequest} from 'react-native-webview/lib/WebViewTypes'
-import {StyleSheet} from 'react-native'
-import {SignupState} from '#/screens/Signup/state'
-
-const ALLOWED_HOSTS = [
-  'bsky.social',
-  'bsky.app',
-  'staging.bsky.app',
-  'staging.bsky.dev',
-  'js.hcaptcha.com',
-  'newassets.hcaptcha.com',
-  'api2.hcaptcha.com',
-]
-
-export function CaptchaWebView({
-  url,
-  stateParam,
-  state,
-  onSuccess,
-  onError,
-}: {
-  url: string
-  stateParam: string
-  state?: SignupState
-  onSuccess: (code: string) => void
-  onError: () => void
-}) {
-  const redirectHost = React.useMemo(() => {
-    if (!state?.serviceUrl) return 'bsky.app'
-
-    return state?.serviceUrl &&
-      new URL(state?.serviceUrl).host === 'staging.bsky.dev'
-      ? 'staging.bsky.app'
-      : 'bsky.app'
-  }, [state?.serviceUrl])
-
-  const wasSuccessful = React.useRef(false)
-
-  const onShouldStartLoadWithRequest = React.useCallback(
-    (event: ShouldStartLoadRequest) => {
-      const urlp = new URL(event.url)
-      return ALLOWED_HOSTS.includes(urlp.host)
-    },
-    [],
-  )
-
-  const onNavigationStateChange = React.useCallback(
-    (e: WebViewNavigation) => {
-      if (wasSuccessful.current) return
-
-      const urlp = new URL(e.url)
-      if (urlp.host !== redirectHost) return
-
-      const code = urlp.searchParams.get('code')
-      if (urlp.searchParams.get('state') !== stateParam || !code) {
-        onError()
-        return
-      }
-
-      wasSuccessful.current = true
-      onSuccess(code)
-    },
-    [redirectHost, stateParam, onSuccess, onError],
-  )
-
-  return (
-    <WebView
-      source={{uri: url}}
-      javaScriptEnabled
-      style={styles.webview}
-      onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
-      onNavigationStateChange={onNavigationStateChange}
-      scrollEnabled={false}
-    />
-  )
-}
-
-const styles = StyleSheet.create({
-  webview: {
-    flex: 1,
-    backgroundColor: 'transparent',
-    borderRadius: 10,
-  },
-})
diff --git a/src/view/com/auth/create/CaptchaWebView.web.tsx b/src/view/com/auth/create/CaptchaWebView.web.tsx
deleted file mode 100644
index 7791a58dd..000000000
--- a/src/view/com/auth/create/CaptchaWebView.web.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import React from 'react'
-import {StyleSheet} from 'react-native'
-
-// @ts-ignore web only, we will always redirect to the app on web (CORS)
-const REDIRECT_HOST = new URL(window.location.href).host
-
-export function CaptchaWebView({
-  url,
-  stateParam,
-  onSuccess,
-  onError,
-}: {
-  url: string
-  stateParam: string
-  onSuccess: (code: string) => void
-  onError: () => void
-}) {
-  const onLoad = React.useCallback(() => {
-    // @ts-ignore web
-    const frame: HTMLIFrameElement = document.getElementById(
-      'captcha-iframe',
-    ) as HTMLIFrameElement
-
-    try {
-      // @ts-ignore web
-      const href = frame?.contentWindow?.location.href
-      if (!href) return
-      const urlp = new URL(href)
-
-      // This shouldn't happen with CORS protections, but for good measure
-      if (urlp.host !== REDIRECT_HOST) return
-
-      const code = urlp.searchParams.get('code')
-      if (urlp.searchParams.get('state') !== stateParam || !code) {
-        onError()
-        return
-      }
-      onSuccess(code)
-    } catch (e) {
-      // We don't need to handle this
-    }
-  }, [stateParam, onSuccess, onError])
-
-  return (
-    <iframe
-      src={url}
-      style={styles.iframe}
-      id="captcha-iframe"
-      onLoad={onLoad}
-    />
-  )
-}
-
-const styles = StyleSheet.create({
-  iframe: {
-    flex: 1,
-    borderWidth: 0,
-    borderRadius: 10,
-    backgroundColor: 'transparent',
-  },
-})
diff --git a/src/view/com/auth/create/Policies.tsx b/src/view/com/auth/create/Policies.tsx
deleted file mode 100644
index 8a656203f..000000000
--- a/src/view/com/auth/create/Policies.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import React from 'react'
-import {View} from 'react-native'
-import {ComAtprotoServerDescribeServer} from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {atoms as a, useTheme} from '#/alf'
-import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
-import {InlineLink} from '#/components/Link'
-import {Text} from '#/components/Typography'
-
-export const Policies = ({
-  serviceDescription,
-  needsGuardian,
-  under13,
-}: {
-  serviceDescription: ComAtprotoServerDescribeServer.OutputSchema
-  needsGuardian: boolean
-  under13: boolean
-}) => {
-  const t = useTheme()
-  const {_} = useLingui()
-
-  if (!serviceDescription) {
-    return <View />
-  }
-
-  const tos = validWebLink(serviceDescription.links?.termsOfService)
-  const pp = validWebLink(serviceDescription.links?.privacyPolicy)
-
-  if (!tos && !pp) {
-    return (
-      <View style={[a.flex_row, a.align_center, a.gap_xs]}>
-        <CircleInfo size="md" fill={t.atoms.text_contrast_low.color} />
-
-        <Text style={[t.atoms.text_contrast_medium]}>
-          <Trans>
-            This service has not provided terms of service or a privacy policy.
-          </Trans>
-        </Text>
-      </View>
-    )
-  }
-
-  const els = []
-  if (tos) {
-    els.push(
-      <InlineLink key="tos" to={tos}>
-        {_(msg`Terms of Service`)}
-      </InlineLink>,
-    )
-  }
-  if (pp) {
-    els.push(
-      <InlineLink key="pp" to={pp}>
-        {_(msg`Privacy Policy`)}
-      </InlineLink>,
-    )
-  }
-  if (els.length === 2) {
-    els.splice(
-      1,
-      0,
-      <Text key="and" style={[t.atoms.text_contrast_medium]}>
-        {' '}
-        and{' '}
-      </Text>,
-    )
-  }
-
-  return (
-    <View style={[a.gap_sm]}>
-      <Text style={[a.leading_snug, t.atoms.text_contrast_medium]}>
-        <Trans>By creating an account you agree to the {els}.</Trans>
-      </Text>
-
-      {under13 ? (
-        <Text style={[a.font_bold, a.leading_snug, t.atoms.text_contrast_high]}>
-          You must be 13 years of age or older to sign up.
-        </Text>
-      ) : needsGuardian ? (
-        <Text style={[a.font_bold, a.leading_snug, t.atoms.text_contrast_high]}>
-          <Trans>
-            If you are not yet an adult according to the laws of your country,
-            your parent or legal guardian must read these Terms on your behalf.
-          </Trans>
-        </Text>
-      ) : undefined}
-    </View>
-  )
-}
-
-function validWebLink(url?: string): string | undefined {
-  return url && (url.startsWith('http://') || url.startsWith('https://'))
-    ? url
-    : undefined
-}
diff --git a/src/view/com/auth/create/state.ts b/src/view/com/auth/create/state.ts
deleted file mode 100644
index 840084dcb..000000000
--- a/src/view/com/auth/create/state.ts
+++ /dev/null
@@ -1,298 +0,0 @@
-import {useCallback, useReducer} from 'react'
-import {
-  ComAtprotoServerDescribeServer,
-  ComAtprotoServerCreateAccount,
-} from '@atproto/api'
-import {I18nContext, useLingui} from '@lingui/react'
-import {msg} from '@lingui/macro'
-import * as EmailValidator from 'email-validator'
-import {getAge} from 'lib/strings/time'
-import {logger} from '#/logger'
-import {createFullHandle, validateHandle} from '#/lib/strings/handles'
-import {cleanError} from '#/lib/strings/errors'
-import {useOnboardingDispatch} from '#/state/shell/onboarding'
-import {useSessionApi} from '#/state/session'
-import {DEFAULT_SERVICE, IS_TEST_USER} from '#/lib/constants'
-import {
-  DEFAULT_PROD_FEEDS,
-  usePreferencesSetBirthDateMutation,
-  useSetSaveFeedsMutation,
-} from 'state/queries/preferences'
-
-export type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
-const DEFAULT_DATE = new Date(Date.now() - 60e3 * 60 * 24 * 365 * 20) // default to 20 years ago
-
-export type CreateAccountAction =
-  | {type: 'set-step'; value: number}
-  | {type: 'set-error'; value: string | undefined}
-  | {type: 'set-processing'; value: boolean}
-  | {type: 'set-service-url'; value: string}
-  | {type: 'set-service-description'; value: ServiceDescription | undefined}
-  | {type: 'set-user-domain'; value: string}
-  | {type: 'set-invite-code'; value: string}
-  | {type: 'set-email'; value: string}
-  | {type: 'set-password'; value: string}
-  | {type: 'set-handle'; value: string}
-  | {type: 'set-birth-date'; value: Date}
-  | {type: 'next'}
-  | {type: 'back'}
-
-export interface CreateAccountState {
-  // state
-  step: number
-  error: string | undefined
-  isProcessing: boolean
-  serviceUrl: string
-  serviceDescription: ServiceDescription | undefined
-  userDomain: string
-  inviteCode: string
-  email: string
-  password: string
-  handle: string
-  birthDate: Date
-
-  // computed
-  canBack: boolean
-  canNext: boolean
-  isInviteCodeRequired: boolean
-  isCaptchaRequired: boolean
-}
-
-export type CreateAccountDispatch = (action: CreateAccountAction) => void
-
-export function useCreateAccount() {
-  const {_} = useLingui()
-
-  return useReducer(createReducer({_}), {
-    step: 1,
-    error: undefined,
-    isProcessing: false,
-    serviceUrl: DEFAULT_SERVICE,
-    serviceDescription: undefined,
-    userDomain: '',
-    inviteCode: '',
-    email: '',
-    password: '',
-    handle: '',
-    birthDate: DEFAULT_DATE,
-
-    canBack: false,
-    canNext: false,
-    isInviteCodeRequired: false,
-    isCaptchaRequired: false,
-  })
-}
-
-export function useSubmitCreateAccount(
-  uiState: CreateAccountState,
-  uiDispatch: CreateAccountDispatch,
-) {
-  const {_} = useLingui()
-  const {createAccount} = useSessionApi()
-  const {mutate: setBirthDate} = usePreferencesSetBirthDateMutation()
-  const {mutate: setSavedFeeds} = useSetSaveFeedsMutation()
-  const onboardingDispatch = useOnboardingDispatch()
-
-  return useCallback(
-    async (verificationCode?: string) => {
-      if (!uiState.email) {
-        uiDispatch({type: 'set-step', value: 1})
-        console.log('no email?')
-        return uiDispatch({
-          type: 'set-error',
-          value: _(msg`Please enter your email.`),
-        })
-      }
-      if (!EmailValidator.validate(uiState.email)) {
-        uiDispatch({type: 'set-step', value: 1})
-        return uiDispatch({
-          type: 'set-error',
-          value: _(msg`Your email appears to be invalid.`),
-        })
-      }
-      if (!uiState.password) {
-        uiDispatch({type: 'set-step', value: 1})
-        return uiDispatch({
-          type: 'set-error',
-          value: _(msg`Please choose your password.`),
-        })
-      }
-      if (!uiState.handle) {
-        uiDispatch({type: 'set-step', value: 2})
-        return uiDispatch({
-          type: 'set-error',
-          value: _(msg`Please choose your handle.`),
-        })
-      }
-      if (uiState.isCaptchaRequired && !verificationCode) {
-        uiDispatch({type: 'set-step', value: 3})
-        return uiDispatch({
-          type: 'set-error',
-          value: _(msg`Please complete the verification captcha.`),
-        })
-      }
-      uiDispatch({type: 'set-error', value: ''})
-      uiDispatch({type: 'set-processing', value: true})
-
-      try {
-        onboardingDispatch({type: 'start'}) // start now to avoid flashing the wrong view
-        await createAccount({
-          service: uiState.serviceUrl,
-          email: uiState.email,
-          handle: createFullHandle(uiState.handle, uiState.userDomain),
-          password: uiState.password,
-          inviteCode: uiState.inviteCode.trim(),
-          verificationCode: uiState.isCaptchaRequired
-            ? verificationCode
-            : undefined,
-        })
-        setBirthDate({birthDate: uiState.birthDate})
-        if (!IS_TEST_USER(uiState.handle)) {
-          setSavedFeeds(DEFAULT_PROD_FEEDS)
-        }
-      } catch (e: any) {
-        onboardingDispatch({type: 'skip'}) // undo starting the onboard
-        let errMsg = e.toString()
-        if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) {
-          errMsg = _(
-            msg`Invite code not accepted. Check that you input it correctly and try again.`,
-          )
-          uiDispatch({type: 'set-step', value: 1})
-        }
-
-        if ([400, 429].includes(e.status)) {
-          logger.warn('Failed to create account', {message: e})
-        } else {
-          logger.error(`Failed to create account (${e.status} status)`, {
-            message: e,
-          })
-        }
-
-        const error = cleanError(errMsg)
-        const isHandleError = error.toLowerCase().includes('handle')
-
-        uiDispatch({type: 'set-processing', value: false})
-        uiDispatch({type: 'set-error', value: cleanError(errMsg)})
-        uiDispatch({type: 'set-step', value: isHandleError ? 2 : 1})
-      }
-    },
-    [
-      uiState.email,
-      uiState.password,
-      uiState.handle,
-      uiState.isCaptchaRequired,
-      uiState.serviceUrl,
-      uiState.userDomain,
-      uiState.inviteCode,
-      uiState.birthDate,
-      uiDispatch,
-      _,
-      onboardingDispatch,
-      createAccount,
-      setBirthDate,
-      setSavedFeeds,
-    ],
-  )
-}
-
-export function is13(state: CreateAccountState) {
-  return getAge(state.birthDate) >= 13
-}
-
-export function is18(state: CreateAccountState) {
-  return getAge(state.birthDate) >= 18
-}
-
-function createReducer({_}: {_: I18nContext['_']}) {
-  return function reducer(
-    state: CreateAccountState,
-    action: CreateAccountAction,
-  ): CreateAccountState {
-    switch (action.type) {
-      case 'set-step': {
-        return compute({...state, step: action.value})
-      }
-      case 'set-error': {
-        return compute({...state, error: action.value})
-      }
-      case 'set-processing': {
-        return compute({...state, isProcessing: action.value})
-      }
-      case 'set-service-url': {
-        return compute({
-          ...state,
-          serviceUrl: action.value,
-          serviceDescription:
-            state.serviceUrl !== action.value
-              ? undefined
-              : state.serviceDescription,
-        })
-      }
-      case 'set-service-description': {
-        return compute({
-          ...state,
-          serviceDescription: action.value,
-          userDomain: action.value?.availableUserDomains[0] || '',
-        })
-      }
-      case 'set-user-domain': {
-        return compute({...state, userDomain: action.value})
-      }
-      case 'set-invite-code': {
-        return compute({...state, inviteCode: action.value})
-      }
-      case 'set-email': {
-        return compute({...state, email: action.value})
-      }
-      case 'set-password': {
-        return compute({...state, password: action.value})
-      }
-      case 'set-handle': {
-        return compute({...state, handle: action.value})
-      }
-      case 'set-birth-date': {
-        return compute({...state, birthDate: action.value})
-      }
-      case 'next': {
-        if (state.step === 1) {
-          if (!is13(state)) {
-            return compute({
-              ...state,
-              error: _(
-                msg`Unfortunately, you do not meet the requirements to create an account.`,
-              ),
-            })
-          }
-        }
-        return compute({...state, error: '', step: state.step + 1})
-      }
-      case 'back': {
-        return compute({...state, error: '', step: state.step - 1})
-      }
-    }
-  }
-}
-
-function compute(state: CreateAccountState): CreateAccountState {
-  let canNext = true
-  if (state.step === 1) {
-    canNext =
-      !!state.serviceDescription &&
-      (!state.isInviteCodeRequired || !!state.inviteCode) &&
-      !!state.email &&
-      !!state.password
-  } else if (state.step === 2) {
-    canNext =
-      !!state.handle && validateHandle(state.handle, state.userDomain).overall
-  } else if (state.step === 3) {
-    // Step 3 will automatically redirect as soon as the captcha completes
-    canNext = false
-  }
-  return {
-    ...state,
-    canBack: state.step > 1,
-    canNext,
-    isInviteCodeRequired: !!state.serviceDescription?.inviteCodeRequired,
-    isCaptchaRequired: !!state.serviceDescription?.phoneVerificationRequired,
-  }
-}