Add a server instance selector and drop env vars
parent
9a6df95ade
commit
3725a2eed1
|
@ -1 +0,0 @@
|
||||||
REACT_APP_BUILD = 'staging'
|
|
|
@ -1 +0,0 @@
|
||||||
REACT_APP_BUILD = 'staging'
|
|
|
@ -1,13 +0,0 @@
|
||||||
// @ts-ignore types not available -prf
|
|
||||||
import {REACT_APP_BUILD} from '@env'
|
|
||||||
|
|
||||||
if (typeof REACT_APP_BUILD !== 'string') {
|
|
||||||
throw new Error('ENV: No env provided')
|
|
||||||
}
|
|
||||||
if (!['dev', 'staging', 'prod'].includes(REACT_APP_BUILD)) {
|
|
||||||
throw new Error(
|
|
||||||
`ENV: Env must be "dev", "staging", or "prod," got "${REACT_APP_BUILD}"`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BUILD = REACT_APP_BUILD
|
|
10
src/env.ts
10
src/env.ts
|
@ -1,10 +0,0 @@
|
||||||
if (typeof process.env.REACT_APP_BUILD !== 'string') {
|
|
||||||
throw new Error('ENV: No env provided')
|
|
||||||
}
|
|
||||||
if (!['dev', 'staging', 'prod'].includes(process.env.REACT_APP_BUILD)) {
|
|
||||||
throw new Error(
|
|
||||||
`ENV: Env must be "dev", "staging", or "prod," got "${process.env.REACT_APP_BUILD}"`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BUILD = process.env.REACT_APP_BUILD
|
|
|
@ -3,14 +3,12 @@ import {sessionClient as AtpApi} from '../third-party/api'
|
||||||
import {RootStoreModel} from './models/root-store'
|
import {RootStoreModel} from './models/root-store'
|
||||||
import * as libapi from './lib/api'
|
import * as libapi from './lib/api'
|
||||||
import * as storage from './lib/storage'
|
import * as storage from './lib/storage'
|
||||||
import {BUILD} from '../env'
|
|
||||||
|
|
||||||
export const DEFAULT_SERVICE =
|
export const IS_PROD_BUILD = true
|
||||||
BUILD === 'prod'
|
export const LOCAL_DEV_SERVICE = 'http://localhost:2583'
|
||||||
? 'http://localhost:2583' // TODO
|
export const STAGING_SERVICE = 'https://pds.staging.bsky.dev'
|
||||||
: BUILD === 'staging'
|
export const PROD_SERVICE = 'https://plc.bsky.social'
|
||||||
? 'https://pds.staging.bsky.dev' // TODO
|
export const DEFAULT_SERVICE = IS_PROD_BUILD ? PROD_SERVICE : LOCAL_DEV_SERVICE
|
||||||
: 'http://localhost:2583'
|
|
||||||
const ROOT_STATE_STORAGE_KEY = 'root'
|
const ROOT_STATE_STORAGE_KEY = 'root'
|
||||||
const STATE_FETCH_INTERVAL = 15e3
|
const STATE_FETCH_INTERVAL = 15e3
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,17 @@ export class InviteToSceneModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ServerInputModel {
|
||||||
|
name = 'server-input'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public initialService: string,
|
||||||
|
public onSelect: (url: string) => void,
|
||||||
|
) {
|
||||||
|
makeAutoObservable(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface ComposerOpts {
|
export interface ComposerOpts {
|
||||||
replyTo?: Post.PostRef
|
replyTo?: Post.PostRef
|
||||||
onPost?: () => void
|
onPost?: () => void
|
||||||
|
@ -79,6 +90,7 @@ export class ShellUiModel {
|
||||||
| SharePostModel
|
| SharePostModel
|
||||||
| EditProfileModel
|
| EditProfileModel
|
||||||
| CreateSceneModel
|
| CreateSceneModel
|
||||||
|
| ServerInputModel
|
||||||
| undefined
|
| undefined
|
||||||
isComposerActive = false
|
isComposerActive = false
|
||||||
composerOpts: ComposerOpts | undefined
|
composerOpts: ComposerOpts | undefined
|
||||||
|
@ -93,7 +105,8 @@ export class ShellUiModel {
|
||||||
| ConfirmModel
|
| ConfirmModel
|
||||||
| SharePostModel
|
| SharePostModel
|
||||||
| EditProfileModel
|
| EditProfileModel
|
||||||
| CreateSceneModel,
|
| CreateSceneModel
|
||||||
|
| ServerInputModel,
|
||||||
) {
|
) {
|
||||||
this.isModalActive = true
|
this.isModalActive = true
|
||||||
this.activeModal = modal
|
this.activeModal = modal
|
||||||
|
|
|
@ -13,6 +13,7 @@ import * as SharePostModal from './SharePost.native'
|
||||||
import * as EditProfileModal from './EditProfile'
|
import * as EditProfileModal from './EditProfile'
|
||||||
import * as CreateSceneModal from './CreateScene'
|
import * as CreateSceneModal from './CreateScene'
|
||||||
import * as InviteToSceneModal from './InviteToScene'
|
import * as InviteToSceneModal from './InviteToScene'
|
||||||
|
import * as ServerInputModal from './ServerInput'
|
||||||
|
|
||||||
const CLOSED_SNAPPOINTS = ['10%']
|
const CLOSED_SNAPPOINTS = ['10%']
|
||||||
|
|
||||||
|
@ -77,6 +78,13 @@ export const Modal = observer(function Modal() {
|
||||||
{...(store.shell.activeModal as models.InviteToSceneModel)}
|
{...(store.shell.activeModal as models.InviteToSceneModel)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
} else if (store.shell.activeModal?.name === 'server-input') {
|
||||||
|
snapPoints = ServerInputModal.snapPoints
|
||||||
|
element = (
|
||||||
|
<ServerInputModal.Component
|
||||||
|
{...(store.shell.activeModal as models.ServerInputModel)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
element = <View />
|
element = <View />
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
import React, {useState} from 'react'
|
||||||
|
import Toast from '../util/Toast'
|
||||||
|
import {StyleSheet, Text, TextInput, TouchableOpacity, View} from 'react-native'
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
|
import LinearGradient from 'react-native-linear-gradient'
|
||||||
|
import {ErrorMessage} from '../util/ErrorMessage'
|
||||||
|
import {useStores} from '../../../state'
|
||||||
|
import {ProfileViewModel} from '../../../state/models/profile-view'
|
||||||
|
import {s, colors, gradients} from '../../lib/styles'
|
||||||
|
import {enforceLen, MAX_DISPLAY_NAME, MAX_DESCRIPTION} from '../../lib/strings'
|
||||||
|
import {
|
||||||
|
IS_PROD_BUILD,
|
||||||
|
LOCAL_DEV_SERVICE,
|
||||||
|
STAGING_SERVICE,
|
||||||
|
PROD_SERVICE,
|
||||||
|
} from '../../../state/index'
|
||||||
|
|
||||||
|
export const snapPoints = ['80%']
|
||||||
|
|
||||||
|
export function Component({
|
||||||
|
initialService,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
initialService: string
|
||||||
|
onSelect: (url: string) => void
|
||||||
|
}) {
|
||||||
|
const store = useStores()
|
||||||
|
const [customUrl, setCustomUrl] = useState<string>('')
|
||||||
|
|
||||||
|
const doSelect = (url: string) => {
|
||||||
|
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||||
|
url = `https://${url}`
|
||||||
|
}
|
||||||
|
store.shell.closeModal()
|
||||||
|
onSelect(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={s.flex1}>
|
||||||
|
<Text style={[s.textCenter, s.bold, s.f18]}>Choose Service</Text>
|
||||||
|
<View style={styles.inner}>
|
||||||
|
<View style={styles.group}>
|
||||||
|
{!IS_PROD_BUILD ? (
|
||||||
|
<>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.btn}
|
||||||
|
onPress={() => doSelect(LOCAL_DEV_SERVICE)}>
|
||||||
|
<Text style={styles.btnText}>Local dev server</Text>
|
||||||
|
<FontAwesomeIcon icon="arrow-right" style={s.white} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.btn}
|
||||||
|
onPress={() => doSelect(STAGING_SERVICE)}>
|
||||||
|
<Text style={styles.btnText}>Staging</Text>
|
||||||
|
<FontAwesomeIcon icon="arrow-right" style={s.white} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</>
|
||||||
|
) : undefined}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.btn}
|
||||||
|
onPress={() => doSelect(PROD_SERVICE)}>
|
||||||
|
<Text style={styles.btnText}>Bluesky.Social</Text>
|
||||||
|
<FontAwesomeIcon icon="arrow-right" style={s.white} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<View style={styles.group}>
|
||||||
|
<Text style={styles.label}>Other service</Text>
|
||||||
|
<View style={{flexDirection: 'row'}}>
|
||||||
|
<TextInput
|
||||||
|
style={styles.textInput}
|
||||||
|
placeholder="e.g. https://bsky.app"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect={false}
|
||||||
|
value={customUrl}
|
||||||
|
onChangeText={setCustomUrl}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.textInputBtn}
|
||||||
|
onPress={() => doSelect(customUrl)}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="check"
|
||||||
|
style={[s.black, {position: 'relative', top: 2}]}
|
||||||
|
size={18}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
inner: {
|
||||||
|
padding: 14,
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
paddingHorizontal: 4,
|
||||||
|
paddingBottom: 4,
|
||||||
|
},
|
||||||
|
textInput: {
|
||||||
|
flex: 1,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.gray3,
|
||||||
|
borderTopLeftRadius: 6,
|
||||||
|
borderBottomLeftRadius: 6,
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
paddingVertical: 12,
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
textInputBtn: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderLeftWidth: 0,
|
||||||
|
borderColor: colors.gray3,
|
||||||
|
borderTopRightRadius: 6,
|
||||||
|
borderBottomRightRadius: 6,
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
paddingVertical: 10,
|
||||||
|
},
|
||||||
|
btn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.blue3,
|
||||||
|
borderRadius: 6,
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
paddingVertical: 10,
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
btnText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '500',
|
||||||
|
color: colors.white,
|
||||||
|
},
|
||||||
|
})
|
|
@ -5,6 +5,7 @@ import {faAngleDown} from '@fortawesome/free-solid-svg-icons/faAngleDown'
|
||||||
import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft'
|
import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft'
|
||||||
import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight'
|
import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight'
|
||||||
import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft'
|
import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft'
|
||||||
|
import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight'
|
||||||
import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons'
|
import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons'
|
||||||
import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket'
|
import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket'
|
||||||
import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare'
|
import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare'
|
||||||
|
@ -35,6 +36,7 @@ import {faLock} from '@fortawesome/free-solid-svg-icons/faLock'
|
||||||
import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass'
|
import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass'
|
||||||
import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage'
|
import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage'
|
||||||
import {faNoteSticky} from '@fortawesome/free-solid-svg-icons/faNoteSticky'
|
import {faNoteSticky} from '@fortawesome/free-solid-svg-icons/faNoteSticky'
|
||||||
|
import {faPen} from '@fortawesome/free-solid-svg-icons/faPen'
|
||||||
import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib'
|
import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib'
|
||||||
import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare'
|
import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare'
|
||||||
import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus'
|
import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus'
|
||||||
|
@ -59,6 +61,7 @@ export function setup() {
|
||||||
faAngleLeft,
|
faAngleLeft,
|
||||||
faAngleRight,
|
faAngleRight,
|
||||||
faArrowLeft,
|
faArrowLeft,
|
||||||
|
faArrowRight,
|
||||||
faArrowRightFromBracket,
|
faArrowRightFromBracket,
|
||||||
faArrowUpFromBracket,
|
faArrowUpFromBracket,
|
||||||
faArrowUpRightFromSquare,
|
faArrowUpRightFromSquare,
|
||||||
|
@ -89,6 +92,7 @@ export function setup() {
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faMessage,
|
faMessage,
|
||||||
faNoteSticky,
|
faNoteSticky,
|
||||||
|
faPen,
|
||||||
faPenNib,
|
faPenNib,
|
||||||
faPenToSquare,
|
faPenToSquare,
|
||||||
faPlus,
|
faPlus,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {AtUri} from '../../third-party/uri'
|
import {AtUri} from '../../third-party/uri'
|
||||||
import {Entity} from '../../third-party/api/src/client/types/app/bsky/feed/post'
|
import {Entity} from '../../third-party/api/src/client/types/app/bsky/feed/post'
|
||||||
|
import {PROD_SERVICE} from '../../state'
|
||||||
|
|
||||||
export const MAX_DISPLAY_NAME = 64
|
export const MAX_DISPLAY_NAME = 64
|
||||||
export const MAX_DESCRIPTION = 256
|
export const MAX_DESCRIPTION = 256
|
||||||
|
@ -106,3 +107,15 @@ export function cleanError(str: string): string {
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toNiceDomain(url: string): string {
|
||||||
|
try {
|
||||||
|
const urlp = new URL(url)
|
||||||
|
if (`https://${urlp.host}` === PROD_SERVICE) {
|
||||||
|
return 'Bluesky.Social'
|
||||||
|
}
|
||||||
|
return urlp.host
|
||||||
|
} catch (e) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {useStores} from '../../state'
|
||||||
import {FeedModel} from '../../state/models/feed-view'
|
import {FeedModel} from '../../state/models/feed-view'
|
||||||
import {ScreenParams} from '../routes'
|
import {ScreenParams} from '../routes'
|
||||||
import {s} from '../lib/styles'
|
import {s} from '../lib/styles'
|
||||||
import {BUILD} from '../../env'
|
|
||||||
|
|
||||||
export const Home = observer(function Home({
|
export const Home = observer(function Home({
|
||||||
visible,
|
visible,
|
||||||
|
@ -57,10 +56,7 @@ export const Home = observer(function Home({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={s.flex1}>
|
<View style={s.flex1}>
|
||||||
<ViewHeader
|
<ViewHeader title="Bluesky" subtitle="Private Beta" />
|
||||||
title="Bluesky"
|
|
||||||
subtitle={`Private Beta${BUILD !== 'prod' ? ` [${BUILD}]` : ''}`}
|
|
||||||
/>
|
|
||||||
<Feed
|
<Feed
|
||||||
key="default"
|
key="default"
|
||||||
feed={defaultFeedView}
|
feed={defaultFeedView}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react'
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
@ -15,10 +16,10 @@ import * as EmailValidator from 'email-validator'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {Picker} from '../com/util/Picker'
|
import {Picker} from '../com/util/Picker'
|
||||||
import {s, colors} from '../lib/styles'
|
import {s, colors} from '../lib/styles'
|
||||||
import {makeValidHandle, createFullHandle} from '../lib/strings'
|
import {makeValidHandle, createFullHandle, toNiceDomain} from '../lib/strings'
|
||||||
import {useStores, DEFAULT_SERVICE} from '../../state'
|
import {useStores, DEFAULT_SERVICE} from '../../state'
|
||||||
import {ServiceDescription} from '../../state/models/session'
|
import {ServiceDescription} from '../../state/models/session'
|
||||||
import {BUILD} from '../../env'
|
import {ServerInputModel} from '../../state/models/shell-ui'
|
||||||
|
|
||||||
enum ScreenState {
|
enum ScreenState {
|
||||||
SigninOrCreateAccount,
|
SigninOrCreateAccount,
|
||||||
|
@ -72,9 +73,7 @@ const SigninOrCreateAccount = ({
|
||||||
<View style={styles.hero}>
|
<View style={styles.hero}>
|
||||||
<Logo />
|
<Logo />
|
||||||
<Text style={styles.title}>Bluesky</Text>
|
<Text style={styles.title}>Bluesky</Text>
|
||||||
<Text style={styles.subtitle}>
|
<Text style={styles.subtitle}>[ private beta ]</Text>
|
||||||
[ private beta {BUILD !== 'prod' ? `- ${BUILD} ` : ''}]
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={s.flex1}>
|
<View style={s.flex1}>
|
||||||
<TouchableOpacity style={styles.btn} onPress={onPressCreateAccount}>
|
<TouchableOpacity style={styles.btn} onPress={onPressCreateAccount}>
|
||||||
|
@ -112,6 +111,7 @@ const SigninOrCreateAccount = ({
|
||||||
const Signin = ({onPressBack}: {onPressBack: () => void}) => {
|
const Signin = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
||||||
|
const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE)
|
||||||
const [serviceDescription, setServiceDescription] = useState<
|
const [serviceDescription, setServiceDescription] = useState<
|
||||||
ServiceDescription | undefined
|
ServiceDescription | undefined
|
||||||
>(undefined)
|
>(undefined)
|
||||||
|
@ -121,10 +121,9 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let aborted = false
|
let aborted = false
|
||||||
if (serviceDescription || error) {
|
setError('')
|
||||||
return
|
console.log('Fetching service description', serviceUrl)
|
||||||
}
|
store.session.describeService(serviceUrl).then(
|
||||||
store.session.describeService(DEFAULT_SERVICE).then(
|
|
||||||
desc => {
|
desc => {
|
||||||
if (aborted) return
|
if (aborted) return
|
||||||
setServiceDescription(desc)
|
setServiceDescription(desc)
|
||||||
|
@ -140,7 +139,11 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
return () => {
|
return () => {
|
||||||
aborted = true
|
aborted = true
|
||||||
}
|
}
|
||||||
}, [])
|
}, [serviceUrl])
|
||||||
|
|
||||||
|
const onPressSelectService = () => {
|
||||||
|
store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl))
|
||||||
|
}
|
||||||
|
|
||||||
const onPressNext = async () => {
|
const onPressNext = async () => {
|
||||||
setError('')
|
setError('')
|
||||||
|
@ -168,7 +171,7 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await store.session.login({
|
await store.session.login({
|
||||||
service: DEFAULT_SERVICE,
|
service: serviceUrl,
|
||||||
handle: fullHandle,
|
handle: fullHandle,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
|
@ -194,9 +197,14 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
<Logo />
|
<Logo />
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.group}>
|
<View style={styles.group}>
|
||||||
<View style={styles.groupTitle}>
|
<TouchableOpacity
|
||||||
<Text style={[s.white, s.f18, s.bold]}>Sign in</Text>
|
style={styles.groupTitle}
|
||||||
</View>
|
onPress={onPressSelectService}>
|
||||||
|
<Text style={[s.white, s.f18, s.bold]} numberOfLines={1}>
|
||||||
|
Sign in to {toNiceDomain(serviceUrl)}
|
||||||
|
</Text>
|
||||||
|
<FontAwesomeIcon icon="pen" size={10} style={styles.groupTitleIcon} />
|
||||||
|
</TouchableOpacity>
|
||||||
{error ? (
|
{error ? (
|
||||||
<View style={styles.error}>
|
<View style={styles.error}>
|
||||||
<View style={styles.errorIcon}>
|
<View style={styles.errorIcon}>
|
||||||
|
@ -256,6 +264,7 @@ const Signin = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
||||||
|
const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE)
|
||||||
const [error, setError] = useState<string>('')
|
const [error, setError] = useState<string>('')
|
||||||
const [serviceDescription, setServiceDescription] = useState<
|
const [serviceDescription, setServiceDescription] = useState<
|
||||||
ServiceDescription | undefined
|
ServiceDescription | undefined
|
||||||
|
@ -268,10 +277,9 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let aborted = false
|
let aborted = false
|
||||||
if (serviceDescription || error) {
|
setError('')
|
||||||
return
|
console.log('Fetching service description', serviceUrl)
|
||||||
}
|
store.session.describeService(serviceUrl).then(
|
||||||
store.session.describeService(DEFAULT_SERVICE).then(
|
|
||||||
desc => {
|
desc => {
|
||||||
if (aborted) return
|
if (aborted) return
|
||||||
setServiceDescription(desc)
|
setServiceDescription(desc)
|
||||||
|
@ -288,7 +296,11 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
return () => {
|
return () => {
|
||||||
aborted = true
|
aborted = true
|
||||||
}
|
}
|
||||||
}, [])
|
}, [serviceUrl])
|
||||||
|
|
||||||
|
const onPressSelectService = () => {
|
||||||
|
store.shell.openModal(new ServerInputModel(serviceUrl, setServiceUrl))
|
||||||
|
}
|
||||||
|
|
||||||
const onPressNext = async () => {
|
const onPressNext = async () => {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
|
@ -307,7 +319,7 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
try {
|
try {
|
||||||
await store.session.createAccount({
|
await store.session.createAccount({
|
||||||
service: DEFAULT_SERVICE,
|
service: serviceUrl,
|
||||||
email,
|
email,
|
||||||
handle: createFullHandle(handle, userDomain),
|
handle: createFullHandle(handle, userDomain),
|
||||||
password,
|
password,
|
||||||
|
@ -346,136 +358,164 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView behavior="padding" style={{flex: 1}}>
|
<ScrollView style={{flex: 1}}>
|
||||||
<View style={styles.logoHero}>
|
<KeyboardAvoidingView behavior="padding" style={{flex: 1}}>
|
||||||
<Logo />
|
<View style={styles.logoHero}>
|
||||||
</View>
|
<Logo />
|
||||||
{serviceDescription ? (
|
</View>
|
||||||
<>
|
{serviceDescription ? (
|
||||||
{error ? (
|
<>
|
||||||
<View style={[styles.error, styles.errorFloating]}>
|
{error ? (
|
||||||
<View style={styles.errorIcon}>
|
<View style={[styles.error, styles.errorFloating]}>
|
||||||
<FontAwesomeIcon icon="exclamation" style={s.white} size={10} />
|
<View style={styles.errorIcon}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="exclamation"
|
||||||
|
style={s.white}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={s.flex1}>
|
||||||
|
<Text style={[s.white, s.bold]}>{error}</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={s.flex1}>
|
) : undefined}
|
||||||
<Text style={[s.white, s.bold]}>{error}</Text>
|
<View style={[styles.group]}>
|
||||||
|
<View style={styles.groupTitle}>
|
||||||
|
<Text style={[s.white, s.f18, s.bold]}>
|
||||||
|
Create a new account
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
<View style={styles.groupContent}>
|
||||||
) : undefined}
|
<FontAwesomeIcon icon="globe" style={styles.groupContentIcon} />
|
||||||
<View style={styles.group}>
|
<TouchableOpacity
|
||||||
<View style={styles.groupTitle}>
|
style={styles.textBtn}
|
||||||
<Text style={[s.white, s.f18, s.bold]}>Create a new account</Text>
|
onPress={onPressSelectService}>
|
||||||
</View>
|
<Text style={styles.textBtnLabel}>
|
||||||
{serviceDescription?.inviteCodeRequired ? (
|
{toNiceDomain(serviceUrl)}
|
||||||
|
</Text>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="pen"
|
||||||
|
size={12}
|
||||||
|
style={styles.textBtnIcon}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
{serviceDescription?.inviteCodeRequired ? (
|
||||||
|
<View style={styles.groupContent}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="ticket"
|
||||||
|
style={styles.groupContentIcon}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.textInput]}
|
||||||
|
placeholder="Invite code"
|
||||||
|
placeholderTextColor={colors.blue0}
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
autoFocus
|
||||||
|
value={inviteCode}
|
||||||
|
onChangeText={setInviteCode}
|
||||||
|
editable={!isProcessing}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : undefined}
|
||||||
<View style={styles.groupContent}>
|
<View style={styles.groupContent}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon="ticket"
|
icon="envelope"
|
||||||
style={styles.groupContentIcon}
|
style={styles.groupContentIcon}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.textInput]}
|
style={[styles.textInput]}
|
||||||
placeholder="Invite code"
|
placeholder="Email address"
|
||||||
placeholderTextColor={colors.blue0}
|
placeholderTextColor={colors.blue0}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
autoFocus
|
value={email}
|
||||||
value={inviteCode}
|
onChangeText={setEmail}
|
||||||
onChangeText={setInviteCode}
|
|
||||||
editable={!isProcessing}
|
editable={!isProcessing}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : undefined}
|
|
||||||
<View style={styles.groupContent}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="envelope"
|
|
||||||
style={styles.groupContentIcon}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
style={[styles.textInput]}
|
|
||||||
placeholder="Email address"
|
|
||||||
placeholderTextColor={colors.blue0}
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect={false}
|
|
||||||
value={email}
|
|
||||||
onChangeText={setEmail}
|
|
||||||
editable={!isProcessing}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.groupContent}>
|
|
||||||
<FontAwesomeIcon icon="lock" style={styles.groupContentIcon} />
|
|
||||||
<TextInput
|
|
||||||
style={[styles.textInput]}
|
|
||||||
placeholder="Choose your password"
|
|
||||||
placeholderTextColor={colors.blue0}
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect={false}
|
|
||||||
secureTextEntry
|
|
||||||
value={password}
|
|
||||||
onChangeText={setPassword}
|
|
||||||
editable={!isProcessing}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={styles.group}>
|
|
||||||
<View style={styles.groupTitle}>
|
|
||||||
<Text style={[s.white, s.f18, s.bold]}>Choose your username</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.groupContent}>
|
|
||||||
<FontAwesomeIcon icon="at" style={styles.groupContentIcon} />
|
|
||||||
<TextInput
|
|
||||||
style={[styles.textInput]}
|
|
||||||
placeholder="eg alice"
|
|
||||||
placeholderTextColor={colors.blue0}
|
|
||||||
autoCapitalize="none"
|
|
||||||
value={handle}
|
|
||||||
onChangeText={v => setHandle(makeValidHandle(v))}
|
|
||||||
editable={!isProcessing}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
{serviceDescription.availableUserDomains.length > 1 && (
|
|
||||||
<View style={styles.groupContent}>
|
<View style={styles.groupContent}>
|
||||||
<FontAwesomeIcon icon="globe" style={styles.groupContentIcon} />
|
<FontAwesomeIcon icon="lock" style={styles.groupContentIcon} />
|
||||||
<Picker
|
<TextInput
|
||||||
style={styles.picker}
|
style={[styles.textInput]}
|
||||||
labelStyle={styles.pickerLabel}
|
placeholder="Choose your password"
|
||||||
iconStyle={styles.pickerIcon}
|
placeholderTextColor={colors.blue0}
|
||||||
value={userDomain}
|
autoCapitalize="none"
|
||||||
items={serviceDescription.availableUserDomains.map(d => ({
|
autoCorrect={false}
|
||||||
label: `.${d}`,
|
secureTextEntry
|
||||||
value: d,
|
value={password}
|
||||||
}))}
|
onChangeText={setPassword}
|
||||||
onChange={itemValue => setUserDomain(itemValue)}
|
editable={!isProcessing}
|
||||||
enabled={!isProcessing}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
|
||||||
<View style={styles.groupContent}>
|
|
||||||
<Text style={[s.white, s.p10]}>
|
|
||||||
Your full username will be{' '}
|
|
||||||
<Text style={s.bold}>
|
|
||||||
@{createFullHandle(handle, userDomain)}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
<View style={styles.group}>
|
||||||
<View style={[s.flexRow, s.pl20, s.pr20]}>
|
<View style={styles.groupTitle}>
|
||||||
<TouchableOpacity onPress={onPressBack}>
|
<Text style={[s.white, s.f18, s.bold]}>
|
||||||
<Text style={[s.white, s.f18, s.pl5]}>Back</Text>
|
Choose your username
|
||||||
</TouchableOpacity>
|
</Text>
|
||||||
<View style={s.flex1} />
|
</View>
|
||||||
<TouchableOpacity onPress={onPressNext}>
|
<View style={styles.groupContent}>
|
||||||
{isProcessing ? (
|
<FontAwesomeIcon icon="at" style={styles.groupContentIcon} />
|
||||||
<ActivityIndicator color="#fff" />
|
<TextInput
|
||||||
) : (
|
style={[styles.textInput]}
|
||||||
<Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text>
|
placeholder="eg alice"
|
||||||
|
placeholderTextColor={colors.blue0}
|
||||||
|
autoCapitalize="none"
|
||||||
|
value={handle}
|
||||||
|
onChangeText={v => setHandle(makeValidHandle(v))}
|
||||||
|
editable={!isProcessing}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{serviceDescription.availableUserDomains.length > 1 && (
|
||||||
|
<View style={styles.groupContent}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="globe"
|
||||||
|
style={styles.groupContentIcon}
|
||||||
|
/>
|
||||||
|
<Picker
|
||||||
|
style={styles.picker}
|
||||||
|
labelStyle={styles.pickerLabel}
|
||||||
|
iconStyle={styles.pickerIcon}
|
||||||
|
value={userDomain}
|
||||||
|
items={serviceDescription.availableUserDomains.map(d => ({
|
||||||
|
label: `.${d}`,
|
||||||
|
value: d,
|
||||||
|
}))}
|
||||||
|
onChange={itemValue => setUserDomain(itemValue)}
|
||||||
|
enabled={!isProcessing}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
<View style={styles.groupContent}>
|
||||||
</View>
|
<Text style={[s.white, s.p10]}>
|
||||||
</>
|
Your full username will be{' '}
|
||||||
) : (
|
<Text style={s.bold}>
|
||||||
<InitialLoadView />
|
@{createFullHandle(handle, userDomain)}
|
||||||
)}
|
</Text>
|
||||||
</KeyboardAvoidingView>
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={[s.flexRow, s.pl20, s.pr20, {paddingBottom: 200}]}>
|
||||||
|
<TouchableOpacity onPress={onPressBack}>
|
||||||
|
<Text style={[s.white, s.f18, s.pl5]}>Back</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={s.flex1} />
|
||||||
|
<TouchableOpacity onPress={onPressNext}>
|
||||||
|
{isProcessing ? (
|
||||||
|
<ActivityIndicator color="#fff" />
|
||||||
|
) : (
|
||||||
|
<Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<InitialLoadView />
|
||||||
|
)}
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</ScrollView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,9 +617,15 @@ const styles = StyleSheet.create({
|
||||||
backgroundColor: colors.blue3,
|
backgroundColor: colors.blue3,
|
||||||
},
|
},
|
||||||
groupTitle: {
|
groupTitle: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
},
|
},
|
||||||
|
groupTitleIcon: {
|
||||||
|
color: colors.white,
|
||||||
|
marginHorizontal: 6,
|
||||||
|
},
|
||||||
groupContent: {
|
groupContent: {
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderTopColor: colors.blue1,
|
borderTopColor: colors.blue1,
|
||||||
|
@ -600,6 +646,22 @@ const styles = StyleSheet.create({
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
},
|
},
|
||||||
|
textBtn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
textBtnLabel: {
|
||||||
|
flex: 1,
|
||||||
|
color: colors.white,
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
fontSize: 18,
|
||||||
|
},
|
||||||
|
textBtnIcon: {
|
||||||
|
color: colors.white,
|
||||||
|
marginHorizontal: 12,
|
||||||
|
},
|
||||||
picker: {
|
picker: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|
|
@ -170,6 +170,7 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
<SafeAreaView style={styles.innerContainer}>
|
<SafeAreaView style={styles.innerContainer}>
|
||||||
<Login />
|
<Login />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
<Modal />
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -294,6 +295,7 @@ function constructScreenRenderDesc(nav: NavigationModel): {
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
outerContainer: {
|
outerContainer: {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
flex: 1,
|
||||||
},
|
},
|
||||||
innerContainer: {
|
innerContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
|
@ -6,6 +6,7 @@ Paul's todo list
|
||||||
- Cursor behaviors on all views
|
- Cursor behaviors on all views
|
||||||
- Update swipe behaviors: edge always goes back, leftmost always goes back, main connects to selector if present
|
- Update swipe behaviors: edge always goes back, leftmost always goes back, main connects to selector if present
|
||||||
- Onboarding flow
|
- Onboarding flow
|
||||||
|
> Invite codes
|
||||||
- Confirm email
|
- Confirm email
|
||||||
- Setup profile?
|
- Setup profile?
|
||||||
- Onboarding
|
- Onboarding
|
||||||
|
@ -23,7 +24,7 @@ Paul's todo list
|
||||||
- View on post
|
- View on post
|
||||||
- Linking
|
- Linking
|
||||||
- Web linking
|
- Web linking
|
||||||
- App linking
|
> App linking
|
||||||
- Pagination
|
- Pagination
|
||||||
- Liked by
|
- Liked by
|
||||||
- Reposted by
|
- Reposted by
|
||||||
|
@ -33,7 +34,4 @@ Paul's todo list
|
||||||
- Bugs
|
- Bugs
|
||||||
- Auth token refresh seems broken
|
- Auth token refresh seems broken
|
||||||
- Check that sub components arent reloading too much
|
- Check that sub components arent reloading too much
|
||||||
- Titles are getting screwed up (possibly swipe related)
|
- Titles are getting screwed up (possibly swipe related)
|
||||||
> Dont suggest self for follows
|
|
||||||
> Double post on post?
|
|
||||||
> Handle no displayname everywhere
|
|
Loading…
Reference in New Issue