Get MVP of web app running

zio/stable
Paul Frazee 2023-01-26 12:53:46 -06:00
parent 751dfb20fd
commit a3d2db9645
8 changed files with 158 additions and 46 deletions

View File

@ -1,7 +1,8 @@
import React, {useState, useEffect} from 'react' import React, {useState, useEffect} from 'react'
import {SafeAreaProvider} from 'react-native-safe-area-context'
import * as view from './view/index' import * as view from './view/index'
import {RootStoreModel, setupState, RootStoreProvider} from './state' import {RootStoreModel, setupState, RootStoreProvider} from './state'
import {DesktopWebShell} from './view/shell/desktop-web' import {WebShell} from './view/shell/web'
// import Toast from 'react-native-root-toast' TODO // import Toast from 'react-native-root-toast' TODO
function App() { function App() {
@ -22,7 +23,9 @@ function App() {
return ( return (
<RootStoreProvider value={rootStore}> <RootStoreProvider value={rootStore}>
<DesktopWebShell /> <SafeAreaProvider>
<WebShell />
</SafeAreaProvider>
</RootStoreProvider> </RootStoreProvider>
) )
// <Toast.ToastContainer /> TODO // <Toast.ToastContainer /> TODO

View File

@ -15,7 +15,7 @@ import {
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import {ComAtprotoAccountCreate} from '@atproto/api' import {ComAtprotoAccountCreate} from '@atproto/api'
import * as EmailValidator from 'email-validator' import * as EmailValidator from 'email-validator'
import {useAnalytics} from '@segment/analytics-react-native' // import {useAnalytics} from '@segment/analytics-react-native' TODO
import {LogoTextHero} from './Logo' import {LogoTextHero} from './Logo'
import {Picker} from '../util/Picker' import {Picker} from '../util/Picker'
import {TextLink} from '../util/Link' import {TextLink} from '../util/Link'
@ -32,7 +32,7 @@ import {ServerInputModal} from '../../../state/models/shell-ui'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
const {track} = useAnalytics() // const {track} = useAnalytics() TODO
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
@ -109,7 +109,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
password, password,
inviteCode, inviteCode,
}) })
track('Create Account') // track('Create Account') TODO
} catch (e: any) { } catch (e: any) {
let errMsg = e.toString() let errMsg = e.toString()
if (e instanceof ComAtprotoAccountCreate.InvalidInviteCodeError) { if (e instanceof ComAtprotoAccountCreate.InvalidInviteCodeError) {

View File

@ -14,7 +14,7 @@ import {
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import * as EmailValidator from 'email-validator' import * as EmailValidator from 'email-validator'
import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api' import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api'
import {useAnalytics} from '@segment/analytics-react-native' // import {useAnalytics} from '@segment/analytics-react-native' TODO
import {LogoTextHero} from './Logo' import {LogoTextHero} from './Logo'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
@ -153,7 +153,7 @@ const ChooseAccountForm = ({
onSelectAccount: (account?: AccountData) => void onSelectAccount: (account?: AccountData) => void
onPressBack: () => void onPressBack: () => void
}) => { }) => {
const {track} = useAnalytics() // const {track} = useAnalytics() TODO
const pal = usePalette('default') const pal = usePalette('default')
const [isProcessing, setIsProcessing] = React.useState(false) const [isProcessing, setIsProcessing] = React.useState(false)
@ -161,7 +161,7 @@ const ChooseAccountForm = ({
if (account.accessJwt && account.refreshJwt) { if (account.accessJwt && account.refreshJwt) {
setIsProcessing(true) setIsProcessing(true)
if (await store.session.resumeSession(account)) { if (await store.session.resumeSession(account)) {
track('Sign In', {resumedSession: true}) // track('Sign In', {resumedSession: true}) TODO
setIsProcessing(false) setIsProcessing(false)
return return
} }
@ -261,7 +261,7 @@ const LoginForm = ({
onPressBack: () => void onPressBack: () => void
onPressForgotPassword: () => void onPressForgotPassword: () => void
}) => { }) => {
const {track} = useAnalytics() // const {track} = useAnalytics() TODO
const pal = usePalette('default') const pal = usePalette('default')
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [handle, setHandle] = useState<string>(initialHandle) const [handle, setHandle] = useState<string>(initialHandle)
@ -302,7 +302,7 @@ const LoginForm = ({
handle: fullHandle, handle: fullHandle,
password, password,
}) })
track('Sign In', {resumedSession: false}) // track('Sign In', {resumedSession: false}) TODO
} catch (e: any) { } catch (e: any) {
const errMsg = e.toString() const errMsg = e.toString()
store.log.warn('Failed to login', e) store.log.warn('Failed to login', e)

View File

@ -76,7 +76,11 @@ export function DropdownButton({
onPress={onPress} onPress={onPress}
hitSlop={HITSLOP} hitSlop={HITSLOP}
// Fix an issue where specific references cause runtime error in jest environment // Fix an issue where specific references cause runtime error in jest environment
ref={process.env.JEST_WORKER_ID != null ? null : ref}> ref={
typeof process !== 'undefined' && process.env.JEST_WORKER_ID != null
? null
: ref
}>
{children} {children}
</TouchableOpacity> </TouchableOpacity>
) )

View File

@ -1,35 +0,0 @@
import React from 'react'
import {observer} from 'mobx-react-lite'
import {View, StyleSheet} from 'react-native'
import {DesktopLeftColumn} from './left-column'
import {DesktopRightColumn} from './right-column'
import {useStores} from '../../../state'
export const DesktopWebShell: React.FC = observer(({children}) => {
const store = useStores()
return (
<View style={styles.outerContainer}>
{store.session.hasSession ? (
<>
<DesktopLeftColumn />
<View style={styles.innerContainer}>{children}</View>
<DesktopRightColumn />
</>
) : (
<View style={styles.innerContainer}>{children}</View>
)}
</View>
)
})
const styles = StyleSheet.create({
outerContainer: {
height: '100%',
},
innerContainer: {
marginLeft: 'auto',
marginRight: 'auto',
width: '600px',
height: '100%',
},
})

View File

@ -0,0 +1,140 @@
import React from 'react'
import {observer} from 'mobx-react-lite'
import {View, StyleSheet, Text} from 'react-native'
import {useStores} from '../../../state'
import {match, MatchResult} from '../../routes'
// import {DesktopLeftColumn} from './left-column'
// import {DesktopRightColumn} from './right-column'
import {Login} from '../../screens/Login'
import {ErrorBoundary} from '../../com/util/ErrorBoundary'
import {usePalette} from '../../lib/hooks/usePalette'
import {s} from '../../lib/styles'
export const WebShell: React.FC = observer(() => {
const pal = usePalette('default')
const store = useStores()
const screenRenderDesc = constructScreenRenderDesc(store.nav)
if (!store.session.hasSession) {
return (
<View style={styles.outerContainer}>
<View style={styles.innerContainer}>
<Login />
</View>
</View>
)
}
return (
<View style={[styles.outerContainer, pal.view]}>
<View style={styles.innerContainer}>
{screenRenderDesc.screens.map(({Com, navIdx, params, key, current}) => (
<View
key={key}
style={[s.h100pct, current ? styles.visible : styles.hidden]}>
<ErrorBoundary>
<Com params={params} navIdx={navIdx} visible={current} />
</ErrorBoundary>
</View>
))}
</View>
</View>
)
// TODO
// <Modal />
// <Lightbox />
// <Composer
// active={store.shell.isComposerActive}
// onClose={() => store.shell.closeComposer()}
// winHeight={winDim.height}
// replyTo={store.shell.composerOpts?.replyTo}
// imagesOpen={store.shell.composerOpts?.imagesOpen}
// onPost={store.shell.composerOpts?.onPost}
// />
// return (
// <View style={styles.outerContainer}>
// {store.session.hasSession ? (
// <>
// <DesktopLeftColumn />
// <View style={styles.innerContainer}>
// <Text>Hello, world! (Logged in)</Text>
// {children}
// </View>
// <DesktopRightColumn />
// </>
// ) : (
// <View style={styles.innerContainer}>
// <Text>Hello, world! (Logged out)</Text>
// {children}
// </View>
// )}
// </View>
// )
})
/**
* This method produces the information needed by the shell to
* render the current screens with screen-caching behaviors.
*/
type ScreenRenderDesc = MatchResult & {
key: string
navIdx: string
current: boolean
previous: boolean
isNewTab: boolean
}
function constructScreenRenderDesc(nav: NavigationModel): {
icon: IconProp
hasNewTab: boolean
screens: ScreenRenderDesc[]
} {
let hasNewTab = false
let icon: IconProp = 'magnifying-glass'
let screens: ScreenRenderDesc[] = []
for (const tab of nav.tabs) {
const tabScreens = [
...tab.getBackList(5),
Object.assign({}, tab.current, {index: tab.index}),
]
const parsedTabScreens = tabScreens.map(screen => {
const isCurrent = nav.isCurrentScreen(tab.id, screen.index)
const isPrevious = nav.isCurrentScreen(tab.id, screen.index + 1)
const matchRes = match(screen.url)
if (isCurrent) {
icon = matchRes.icon
}
hasNewTab = hasNewTab || tab.isNewTab
return Object.assign(matchRes, {
key: `t${tab.id}-s${screen.index}`,
navIdx: `${tab.id}-${screen.id}`,
current: isCurrent,
previous: isPrevious,
isNewTab: tab.isNewTab,
}) as ScreenRenderDesc
})
screens = screens.concat(parsedTabScreens)
}
return {
icon,
hasNewTab,
screens,
}
}
const styles = StyleSheet.create({
outerContainer: {
height: '100%',
},
innerContainer: {
marginLeft: 'auto',
marginRight: 'auto',
width: '600px',
height: '100%',
},
visible: {
display: 'flex',
},
hidden: {
display: 'none',
},
})