diff options
-rw-r--r-- | src/state/models/ui/shell.ts | 5 | ||||
-rw-r--r-- | src/view/com/auth/SplashScreen.web.tsx | 19 | ||||
-rw-r--r-- | src/view/com/auth/create/Step2.tsx | 13 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 4 | ||||
-rw-r--r-- | src/view/com/modals/Modal.web.tsx | 3 | ||||
-rw-r--r-- | src/view/com/modals/Waitlist.tsx | 163 |
6 files changed, 199 insertions, 8 deletions
diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index db6ae2eac..d6fefb850 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -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 {} diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx index a4fc62349..cef376c05 100644 --- a/src/view/com/auth/SplashScreen.web.tsx +++ b/src/view/com/auth/SplashScreen.web.tsx @@ -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> diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx index 1f3162880..f4f399abd 100644 --- a/src/view/com/auth/create/Step2.tsx +++ b/src/view/com/auth/create/Step2.tsx @@ -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> ) : ( diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index d3a02e0da..931e3fbe4 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -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 /> } diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index d8815376c..3439b0c89 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -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 } diff --git a/src/view/com/modals/Waitlist.tsx b/src/view/com/modals/Waitlist.tsx new file mode 100644 index 000000000..f3c301937 --- /dev/null +++ b/src/view/com/modals/Waitlist.tsx @@ -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'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, + }, +}) |