Bluesky app fork with some witchin' additions 💫

clarify ATmosphere during signup

commit 24a4265670a51f7491cfd4a8c12f4d2eb72bfbe4
Author: Aviva Ruben <aviva@rubenfamily.com>
Date: Thu Apr 17 22:53:32 2025 -0500

clean up

commit cad55cc38fd155174e19ba00c36bdc1da35d89a7
Author: Aviva Ruben <aviva@rubenfamily.com>
Date: Thu Apr 17 22:43:39 2025 -0500

initial atmosphere explainer screen

Aviva Ruben e6fa8d08 d491d514

+157 -13
+108
src/screens/Signup/StepAtmosphere.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {logger} from '#/logger' 7 + import {isWeb} from '#/platform/detection' 8 + import {ScreenTransition} from '#/screens/Login/ScreenTransition' 9 + import {useSignupContext} from '#/screens/Signup/state' 10 + import {atoms as a, useTheme} from '#/alf' 11 + import {Button, ButtonText} from '#/components/Button' 12 + import {InlineLinkText} from '#/components/Link' 13 + import {Text} from '#/components/Typography' 14 + import {BackNextButtons} from './BackNextButtons' 15 + 16 + export function StepAtmosphere({ 17 + onPressBack, 18 + onPressSignIn, 19 + }: { 20 + onPressBack: () => void 21 + onPressSignIn: () => void 22 + }) { 23 + const {_} = useLingui() 24 + const t = useTheme() 25 + const {state, dispatch} = useSignupContext() 26 + 27 + const onNextPress = React.useCallback(async () => { 28 + logger.metric( 29 + 'signup:nextPressed', 30 + { 31 + activeStep: state.activeStep, 32 + }, 33 + {statsig: true}, 34 + ) 35 + dispatch({type: 'next'}) 36 + }, [dispatch, state.activeStep]) 37 + 38 + const onBackPress = React.useCallback(() => { 39 + logger.metric( 40 + 'signup:backPressed', 41 + {activeStep: state.activeStep}, 42 + {statsig: true}, 43 + ) 44 + onPressBack() 45 + }, [state.activeStep, onPressBack]) 46 + 47 + return ( 48 + <ScreenTransition> 49 + <View style={[a.gap_xl]}> 50 + <Text style={[a.gap_md, a.leading_snug]}> 51 + <Trans> 52 + deer.social is part of the{' '} 53 + { 54 + <InlineLinkText 55 + label={_(msg`ATmosphere`)} 56 + to="https://atproto.com/"> 57 + <Trans>ATmosphere</Trans> 58 + </InlineLinkText> 59 + } 60 + —the network of apps, services, and accounts built on the AT 61 + Protocol. For example, if you already have a Bluesky account you are 62 + already part of this ecosystem. That means you can sign in right now 63 + with your existing account. 64 + </Trans> 65 + </Text> 66 + <View style={isWeb && [a.flex_row, a.justify_start]}> 67 + <Button 68 + testID="signInButton" 69 + onPress={onPressSignIn} 70 + label={_(msg`Sign in with ATmosphere`)} 71 + accessibilityHint={_( 72 + msg`Opens flow to sign in to your existing ATmosphere account`, 73 + )} 74 + size="large" 75 + variant="solid" 76 + color="primary"> 77 + <ButtonText> 78 + <Trans>Sign in with ATmosphere</Trans> 79 + </ButtonText> 80 + </Button> 81 + </View> 82 + <Text style={[a.gap_md, t.atoms.text_contrast_medium, a.leading_snug]}> 83 + <Trans> 84 + Don’t have an account in the ATmosphere yet? You can create one on 85 + the next page. Just note that you'll need to choose a Personal Data 86 + Server ( 87 + { 88 + <InlineLinkText 89 + label={_(msg`PDS`)} 90 + to="https://atproto.com/guides/self-hosting"> 91 + <Trans>PDS</Trans> 92 + </InlineLinkText> 93 + } 94 + ) that isn’t hosted by Bluesky. If you want to use a Bluesky-hosted 95 + PDS, you’ll need to sign up through bsky.app first, then return here 96 + to continue. 97 + </Trans> 98 + </Text> 99 + </View> 100 + <BackNextButtons 101 + isLoading={false} 102 + isNextDisabled={false} 103 + onBackPress={onBackPress} 104 + onNextPress={onNextPress} 105 + /> 106 + </ScreenTransition> 107 + ) 108 + }
+20 -2
src/screens/Signup/StepInfo/index.tsx
··· 5 5 import * as EmailValidator from 'email-validator' 6 6 import type tldts from 'tldts' 7 7 8 + import {DEFAULT_SERVICE} from '#/lib/constants' 8 9 import {isEmailMaybeInvalid} from '#/lib/strings/email' 9 10 import {logger} from '#/logger' 10 11 import {ScreenTransition} from '#/screens/Login/ScreenTransition' ··· 78 79 return 79 80 } 80 81 82 + if (state.serviceUrl === 'https://example.com') { 83 + return dispatch({ 84 + type: 'setError', 85 + value: _(msg`Please choose a service host.`), 86 + }) 87 + } 88 + 89 + if (state.serviceUrl === DEFAULT_SERVICE) { 90 + return dispatch({ 91 + type: 'setError', 92 + value: _( 93 + msg`Please choose a 3rd party service host, or sign up on bsky.app.`, 94 + ), 95 + }) 96 + } 97 + 81 98 if (state.serviceDescription?.inviteCodeRequired && !inviteCode) { 82 99 return dispatch({ 83 100 type: 'setError', ··· 147 164 <View style={[a.gap_md]}> 148 165 <FormError error={state.error} /> 149 166 <HostingProvider 150 - minimal 151 167 serviceUrl={state.serviceUrl} 152 168 onSelectServiceUrl={v => dispatch({type: 'setServiceUrl', value: v})} 153 169 /> ··· 280 296 ) : undefined} 281 297 </View> 282 298 <BackNextButtons 283 - hideNext={!is13(state.dateOfBirth)} 299 + hideNext={ 300 + !is13(state.dateOfBirth) || state.serviceUrl === 'https://example.com' 301 + } 284 302 showRetry={isServerError} 285 303 isLoading={state.isLoading} 286 304 onBackPress={onPressBack}
+20 -6
src/screens/Signup/index.tsx
··· 27 27 import {InlineLinkText} from '#/components/Link' 28 28 import {Text} from '#/components/Typography' 29 29 import * as bsky from '#/types/bsky' 30 + import {StepAtmosphere} from './StepAtmosphere' 30 31 31 - export function Signup({onPressBack}: {onPressBack: () => void}) { 32 + export function Signup({ 33 + onPressBack, 34 + onPressSignIn, 35 + }: { 36 + onPressBack: () => void 37 + onPressSignIn: () => void 38 + }) { 32 39 const {_} = useLingui() 33 40 const t = useTheme() 34 41 const [state, dispatch] = useReducer(reducer, initialState) ··· 149 156 Step {state.activeStep + 1} of{' '} 150 157 {state.serviceDescription && 151 158 !state.serviceDescription.phoneVerificationRequired 152 - ? '2' 153 - : '3'} 159 + ? '3' 160 + : '4'} 154 161 </Trans> 155 162 </Text> 156 163 <Text style={[a.text_3xl, a.font_bold]}> 157 - {state.activeStep === SignupStep.INFO ? ( 164 + {state.activeStep == SignupStep.ATMOSPHERE ? ( 165 + <Trans>The ATmosphere ✨</Trans> 166 + ) : state.activeStep === SignupStep.INFO ? ( 158 167 <Trans>Your account</Trans> 159 168 ) : state.activeStep === SignupStep.HANDLE ? ( 160 169 <Trans>Choose your username</Trans> ··· 165 174 </View> 166 175 167 176 <LayoutAnimationConfig skipEntering skipExiting> 168 - {state.activeStep === SignupStep.INFO ? ( 177 + {state.activeStep === SignupStep.ATMOSPHERE ? ( 178 + <StepAtmosphere 179 + onPressBack={onPressBack} 180 + onPressSignIn={onPressSignIn} 181 + /> 182 + ) : state.activeStep === SignupStep.INFO ? ( 169 183 <StepInfo 170 184 onPressBack={onPressBack} 171 185 isLoadingStarterPack={ ··· 193 207 label={_(msg`Contact support`)} 194 208 to={FEEDBACK_FORM_URL({email: state.email})} 195 209 style={[!gtMobile && a.text_md]}> 196 - <Trans>Contact support</Trans> 210 + <Trans>Open a Github Issue</Trans> 197 211 </InlineLinkText> 198 212 </Text> 199 213 </View>
+5 -5
src/screens/Signup/state.ts
··· 8 8 import {useLingui} from '@lingui/react' 9 9 import * as EmailValidator from 'email-validator' 10 10 11 - import {DEFAULT_SERVICE} from '#/lib/constants' 12 11 import {cleanError} from '#/lib/strings/errors' 13 12 import {createFullHandle} from '#/lib/strings/handles' 14 13 import {getAge} from '#/lib/strings/time' ··· 21 20 const DEFAULT_DATE = new Date(Date.now() - 60e3 * 60 * 24 * 365 * 20) // default to 20 years ago 22 21 23 22 export enum SignupStep { 23 + ATMOSPHERE, 24 24 INFO, 25 25 HANDLE, 26 26 CAPTCHA, ··· 83 83 84 84 export const initialState: SignupState = { 85 85 hasPrev: false, 86 - activeStep: SignupStep.INFO, 86 + activeStep: SignupStep.ATMOSPHERE, 87 87 88 - serviceUrl: DEFAULT_SERVICE, 88 + serviceUrl: 'https://example.com', 89 89 serviceDescription: undefined, 90 90 userDomain: '', 91 91 dateOfBirth: DEFAULT_DATE, ··· 125 125 126 126 switch (a.type) { 127 127 case 'prev': { 128 - if (s.activeStep !== SignupStep.INFO) { 128 + if (s.activeStep !== SignupStep.ATMOSPHERE) { 129 129 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) 130 130 next.activeStep-- 131 131 next.error = '' ··· 230 230 } 231 231 } 232 232 233 - next.hasPrev = next.activeStep !== SignupStep.INFO 233 + next.hasPrev = next.activeStep !== SignupStep.ATMOSPHERE 234 234 235 235 logger.debug('signup', next) 236 236
+4
src/view/com/auth/LoggedOut.tsx
··· 115 115 onPressBack={() => 116 116 setScreenState(ScreenState.S_LoginOrCreateAccount) 117 117 } 118 + onPressSignIn={() => { 119 + setScreenState(ScreenState.S_Login) 120 + logEvent('splash:signInPressed', {}) 121 + }} 118 122 /> 119 123 ) : undefined} 120 124 </ErrorBoundary>