Add waitlist signup

zio/stable
Paul Frazee 2023-03-14 13:30:20 -05:00
parent b838c786e0
commit 617d93fb48
6 changed files with 199 additions and 8 deletions

View File

@ -56,6 +56,10 @@ export interface ChangeHandleModal {
onChanged: () => void
}
export interface WaitlistModal {
name: 'waitlist'
}
export type Modal =
| ConfirmModal
| EditProfileModal
@ -66,6 +70,7 @@ export type Modal =
| DeleteAccountModal
| RepostModal
| ChangeHandleModal
| WaitlistModal
interface LightboxModel {}

View File

@ -1,10 +1,10 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {Text} from 'view/com/util/text/Text'
import {TextLink} from '../util/Link'
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {CenteredView} from '../util/Views'
export const SplashScreen = ({
@ -15,6 +15,12 @@ export const SplashScreen = ({
onPressCreateAccount: () => void
}) => {
const pal = usePalette('default')
const store = useStores()
const onPressWaitlist = React.useCallback(() => {
store.shell.openModal({name: 'waitlist'})
}, [store])
return (
<CenteredView style={[styles.container, pal.view]}>
<View testID="noSessionView" style={[styles.containerInner, pal.border]}>
@ -42,12 +48,11 @@ export const SplashScreen = ({
style={[styles.notice, pal.textLight]}
lineHeight={1.3}>
Bluesky will launch soon.{' '}
<TextLink
type="xl"
text="Join the waitlist"
href="#"
style={pal.link}
/>{' '}
<TouchableOpacity onPress={onPressWaitlist}>
<Text type="xl" style={pal.link}>
Join the waitlist
</Text>
</TouchableOpacity>{' '}
to try the beta before it's publicly available.
</Text>
</ErrorBoundary>

View File

@ -11,9 +11,16 @@ import {usePalette} from 'lib/hooks/usePalette'
import {TextInput} from '../util/TextInput'
import {Policies} from './Policies'
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import {useStores} from 'state/index'
export const Step2 = observer(({model}: {model: CreateAccountModel}) => {
const pal = usePalette('default')
const store = useStores()
const onPressWaitlist = React.useCallback(() => {
store.shell.openModal({name: 'waitlist'})
}, [store])
return (
<View>
<StepHeader step="2" title="Your account" />
@ -36,7 +43,11 @@ export const Step2 = observer(({model}: {model: CreateAccountModel}) => {
{!model.inviteCode && model.isInviteCodeRequired ? (
<Text>
Don't have an invite code?{' '}
<TextLink text="Join the waitlist" href="#" style={pal.link} /> to try
<TouchableOpacity onPress={onPressWaitlist}>
<Text type="xl" style={pal.link}>
Join the waitlist
</Text>
</TouchableOpacity>{' '}
the beta before it's publicly available.
</Text>
) : (

View File

@ -13,6 +13,7 @@ import * as RepostModal from './Repost'
import * as ReportAccountModal from './ReportAccount'
import * as DeleteAccountModal from './DeleteAccount'
import * as ChangeHandleModal from './ChangeHandle'
import * as WaitlistModal from './Waitlist'
import {usePalette} from 'lib/hooks/usePalette'
import {StyleSheet} from 'react-native'
@ -69,6 +70,9 @@ export const ModalsContainer = observer(function ModalsContainer() {
} else if (activeModal?.name === 'change-handle') {
snapPoints = ChangeHandleModal.snapPoints
element = <ChangeHandleModal.Component {...activeModal} />
} else if (activeModal?.name === 'waitlist') {
snapPoints = WaitlistModal.snapPoints
element = <WaitlistModal.Component />
} else {
return <View />
}

View File

@ -14,6 +14,7 @@ import * as DeleteAccountModal from './DeleteAccount'
import * as RepostModal from './Repost'
import * as CropImageModal from './crop-image/CropImage.web'
import * as ChangeHandleModal from './ChangeHandle'
import * as WaitlistModal from './Waitlist'
export const ModalsContainer = observer(function ModalsContainer() {
const store = useStores()
@ -68,6 +69,8 @@ function Modal({modal}: {modal: ModalIface}) {
element = <RepostModal.Component {...modal} />
} else if (modal.name === 'change-handle') {
element = <ChangeHandleModal.Component {...modal} />
} else if (modal.name === 'waitlist') {
element = <WaitlistModal.Component />
} else {
return null
}

View File

@ -0,0 +1,163 @@
import React from 'react'
import {
ActivityIndicator,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import {TextInput} from './util'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import LinearGradient from 'react-native-linear-gradient'
import {Text} from '../util/text/Text'
import {useStores} from 'state/index'
import {s, gradients} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from 'lib/strings/errors'
export const snapPoints = ['60%']
export function Component({}: {}) {
const pal = usePalette('default')
const theme = useTheme()
const store = useStores()
const [email, setEmail] = React.useState<string>('')
const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
const [isProcessing, setIsProcessing] = React.useState<boolean>(false)
const [error, setError] = React.useState<string>('')
const onPressSignup = async () => {
setError('')
setIsProcessing(true)
try {
const res = await fetch('https://bsky.app/api/waitlist', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email}),
})
const resBody = await res.json()
if (resBody.success) {
setIsEmailSent(true)
} else {
setError(
resBody.error ||
'Something went wrong. Check your email and try again.',
)
}
} catch (e: any) {
setError(cleanError(e))
}
setIsProcessing(false)
}
const onCancel = () => {
store.shell.closeModal()
}
return (
<View
style={[styles.container, {backgroundColor: pal.colors.backgroundLight}]}>
<View style={[styles.innerContainer, pal.view]}>
<Text type="title-xl" style={[styles.title, pal.text]}>
Join the waitlist
</Text>
<Text type="lg" style={[styles.description, pal.text]}>
Bluesky will launch soon. Join the waitlist to try the beta before
it's publicly available.
</Text>
<TextInput
style={[styles.textInput, pal.borderDark, pal.text, s.mb10, s.mt10]}
placeholder="Enter your email"
placeholderTextColor={pal.textLight.color}
autoCapitalize="none"
autoCorrect={false}
keyboardAppearance={theme.colorScheme}
value={email}
onChangeText={setEmail}
/>
{error ? (
<View style={s.mt10}>
<ErrorMessage message={error} style={styles.error} />
</View>
) : undefined}
{isProcessing ? (
<View style={[styles.btn, s.mt10]}>
<ActivityIndicator />
</View>
) : isEmailSent ? (
<View style={[styles.btn, s.mt10]}>
<FontAwesomeIcon
icon="check"
style={pal.text as FontAwesomeIconStyle}
/>
<Text style={s.ml10}>
Your email has been saved! We&apos;ll be in touch soon.
</Text>
</View>
) : (
<>
<TouchableOpacity onPress={onPressSignup}>
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text type="button-lg" style={[s.white, s.bold]}>
Join Waitlist
</Text>
</LinearGradient>
</TouchableOpacity>
<TouchableOpacity style={[styles.btn, s.mt10]} onPress={onCancel}>
<Text type="button-lg" style={pal.textLight}>
Cancel
</Text>
</TouchableOpacity>
</>
)}
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
innerContainer: {
paddingBottom: 20,
},
title: {
textAlign: 'center',
marginTop: 12,
marginBottom: 12,
},
description: {
textAlign: 'center',
paddingHorizontal: 22,
marginBottom: 10,
},
textInput: {
borderWidth: 1,
borderRadius: 6,
paddingHorizontal: 16,
paddingVertical: 12,
fontSize: 20,
marginHorizontal: 20,
},
btn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 32,
padding: 14,
marginHorizontal: 20,
},
error: {
borderRadius: 6,
marginHorizontal: 20,
marginBottom: 20,
},
})