Add mock API and reorg code for clarity

This commit is contained in:
Paul Frazee 2022-07-18 15:24:37 -05:00
parent de87ec17d1
commit 1d00f3b984
29 changed files with 356 additions and 168 deletions

110
src/view/routes/index.tsx Normal file
View file

@ -0,0 +1,110 @@
import React, {useEffect} from 'react'
import {Text, Linking} from 'react-native'
import {
NavigationContainer,
LinkingOptions,
RouteProp,
ParamListBase,
} from '@react-navigation/native'
import {createNativeStackNavigator} from '@react-navigation/native-stack'
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'
import {observer} from 'mobx-react-lite'
import type {RootTabsParamList} from './types'
import {useStores} from '../../state'
import * as platform from '../../platform/detection'
import {Home} from '../screens/Home'
import {Search} from '../screens/Search'
import {Notifications} from '../screens/Notifications'
import {Menu} from '../screens/Menu'
import {Profile} from '../screens/Profile'
import {Login} from '../screens/Login'
import {Signup} from '../screens/Signup'
import {NotFound} from '../screens/NotFound'
const linking: LinkingOptions<RootTabsParamList> = {
prefixes: [
'http://localhost:3000', // local dev
'https://pubsq.pfrazee.com', // test server (universal links only)
'pubsqapp://', // custom protocol (ios)
'pubsq://app', // custom protocol (android)
],
config: {
screens: {
Home: '',
Profile: 'profile/:name',
Search: 'search',
Notifications: 'notifications',
Menu: 'menu',
Login: 'login',
Signup: 'signup',
NotFound: '*',
},
},
}
export const RootTabs = createBottomTabNavigator()
export const PrimaryStack = createNativeStackNavigator()
const tabBarScreenOptions = ({
route,
}: {
route: RouteProp<ParamListBase, string>
}) => ({
headerShown: false,
tabBarIcon: (_state: {focused: boolean; color: string; size: number}) => {
// TODO: icons
return <Text>{route.name?.[0] || ''}</Text>
},
})
const HIDE_TAB = {tabBarButton: () => null}
export const Root = observer(() => {
const store = useStores()
useEffect(() => {
console.log('Initial link setup')
Linking.getInitialURL().then((url: string | null) => {
console.log('Initial url', url)
})
Linking.addEventListener('url', ({url}) => {
console.log('Deep link opened with', url)
})
}, [])
// hide the tabbar on desktop web
const tabBar = platform.isDesktopWeb ? () => null : undefined
return (
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
<RootTabs.Navigator
initialRouteName={store.session.isAuthed ? 'Home' : 'Login'}
screenOptions={tabBarScreenOptions}
tabBar={tabBar}>
{store.session.isAuthed ? (
<>
<RootTabs.Screen name="Home" component={Home} />
<RootTabs.Screen name="Search" component={Search} />
<RootTabs.Screen name="Notifications" component={Notifications} />
<RootTabs.Screen name="Menu" component={Menu} />
<RootTabs.Screen
name="Profile"
component={Profile}
options={HIDE_TAB}
/>
</>
) : (
<>
<RootTabs.Screen name="Login" component={Login} />
<RootTabs.Screen name="Signup" component={Signup} />
</>
)}
<RootTabs.Screen
name="NotFound"
component={NotFound}
options={HIDE_TAB}
/>
</RootTabs.Navigator>
</NavigationContainer>
)
})

36
src/view/routes/types.ts Normal file
View file

@ -0,0 +1,36 @@
import type {StackScreenProps} from '@react-navigation/stack'
export type RootTabsParamList = {
Home: undefined
Search: undefined
Notifications: undefined
Menu: undefined
Profile: {name: string}
Login: undefined
Signup: undefined
NotFound: undefined
}
export type RootTabsScreenProps<T extends keyof RootTabsParamList> =
StackScreenProps<RootTabsParamList, T>
/*
NOTE
this is leftover from a nested nav implementation
keeping it around for future reference
-prf
import type {NavigatorScreenParams} from '@react-navigation/native'
import type {CompositeScreenProps} from '@react-navigation/native'
import type {BottomTabScreenProps} from '@react-navigation/bottom-tabs'
Container: NavigatorScreenParams<PrimaryStacksParamList>
export type PrimaryStacksParamList = {
Home: undefined
Profile: {name: string}
}
export type PrimaryStacksScreenProps<T extends keyof PrimaryStacksParamList> =
CompositeScreenProps<
BottomTabScreenProps<PrimaryStacksParamList, T>,
RootTabsScreenProps<keyof RootTabsParamList>
>
*/

21
src/view/screens/Home.tsx Normal file
View file

@ -0,0 +1,21 @@
import React from 'react'
import {Text, Button, View} from 'react-native'
import {Shell} from '../shell'
import type {RootTabsScreenProps} from '../routes/types'
import {useStores} from '../../state'
export function Home({navigation}: RootTabsScreenProps<'Home'>) {
const store = useStores()
return (
<Shell>
<View style={{alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Home</Text>
<Button
title="Go to Jane's profile"
onPress={() => navigation.navigate('Profile', {name: 'Jane'})}
/>
<Button title="Logout" onPress={() => store.session.logout()} />
</View>
</Shell>
)
}

View file

@ -0,0 +1,29 @@
import React from 'react'
import {Text, Button, View, ActivityIndicator} from 'react-native'
import {observer} from 'mobx-react-lite'
import {Shell} from '../shell'
import type {RootTabsScreenProps} from '../routes/types'
import {useStores} from '../../state'
export const Login = observer(({navigation}: RootTabsScreenProps<'Login'>) => {
const store = useStores()
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Sign In</Text>
{store.session.uiError ?? <Text>{store.session.uiError}</Text>}
{!store.session.uiIsProcessing ? (
<>
<Button title="Login" onPress={() => store.session.login()} />
<Button
title="Sign Up"
onPress={() => navigation.navigate('Signup')}
/>
</>
) : (
<ActivityIndicator />
)}
</View>
</Shell>
)
})

16
src/view/screens/Menu.tsx Normal file
View file

@ -0,0 +1,16 @@
import React from 'react'
import {Shell} from '../shell'
import {ScrollView, Text, View} from 'react-native'
import type {RootTabsScreenProps} from '../routes/types'
export const Menu = (_props: RootTabsScreenProps<'Menu'>) => {
return (
<Shell>
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Menu</Text>
</View>
</ScrollView>
</Shell>
)
}

View file

@ -0,0 +1,15 @@
import React from 'react'
import {Shell} from '../shell'
import {Text, Button, View} from 'react-native'
import type {RootTabsScreenProps} from '../routes/types'
export const NotFound = ({navigation}: RootTabsScreenProps<'NotFound'>) => {
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Page not found</Text>
<Button title="Home" onPress={() => navigation.navigate('Home')} />
</View>
</Shell>
)
}

View file

@ -0,0 +1,14 @@
import React from 'react'
import {Shell} from '../shell'
import {Text, View} from 'react-native'
import type {RootTabsScreenProps} from '../routes/types'
export const Notifications = (_props: RootTabsScreenProps<'Notifications'>) => {
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Notifications</Text>
</View>
</Shell>
)
}

View file

@ -0,0 +1,16 @@
import React from 'react'
import {Shell} from '../shell'
import {View, Text} from 'react-native'
import type {RootTabsScreenProps} from '../routes/types'
export const Profile = ({route}: RootTabsScreenProps<'Profile'>) => {
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>
{route.params?.name}'s profile
</Text>
</View>
</Shell>
)
}

View file

@ -0,0 +1,14 @@
import React from 'react'
import {Shell} from '../shell'
import {Text, View} from 'react-native'
import type {RootTabsScreenProps} from '../routes/types'
export const Search = (_props: RootTabsScreenProps<'Search'>) => {
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Search</Text>
</View>
</Shell>
)
}

View file

@ -0,0 +1,34 @@
import React from 'react'
import {Text, Button, View, ActivityIndicator} from 'react-native'
import {observer} from 'mobx-react-lite'
import {Shell} from '../shell'
import type {RootTabsScreenProps} from '../routes/types'
import {useStores} from '../../state'
export const Signup = observer(
({navigation}: RootTabsScreenProps<'Signup'>) => {
const store = useStores()
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Create Account</Text>
{store.session.uiError ?? <Text>{store.session.uiError}</Text>}
{!store.session.uiIsProcessing ? (
<>
<Button
title="Create new account"
onPress={() => store.session.login()}
/>
<Button
title="Log in to an existing account"
onPress={() => navigation.navigate('Login')}
/>
</>
) : (
<ActivityIndicator />
)}
</View>
</Shell>
)
},
)

View file

@ -0,0 +1,57 @@
import React from 'react'
import {Pressable, View, StyleSheet} from 'react-native'
import {Link} from '@react-navigation/native'
import {useRoute} from '@react-navigation/native'
export const NavItem: React.FC<{label: string; screen: string}> = ({
label,
screen,
}) => {
const route = useRoute()
return (
<View>
<Pressable
style={state => [
// @ts-ignore it does exist! (react-native-web) -prf
state.hovered && styles.navItemHovered,
]}>
<Link
style={[
styles.navItemLink,
route.name === screen && styles.navItemLinkSelected,
]}
to={{screen, params: {}}}>
{label}
</Link>
</Pressable>
</View>
)
}
export const DesktopLeftColumn: React.FC = () => {
return (
<View style={styles.container}>
<NavItem screen="Home" label="Home" />
<NavItem screen="Search" label="Search" />
<NavItem screen="Notifications" label="Notifications" />
</View>
)
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
left: 'calc(50vw - 500px)',
width: '200px',
height: '100%',
},
navItemHovered: {
backgroundColor: 'gray',
},
navItemLink: {
padding: '1rem',
},
navItemLinkSelected: {
color: 'blue',
},
})

View file

@ -0,0 +1,19 @@
import React from 'react'
import {Text, View, StyleSheet} from 'react-native'
export const DesktopRightColumn: React.FC = () => {
return (
<View style={styles.container}>
<Text>Right Column</Text>
</View>
)
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
right: 'calc(50vw - 500px)',
width: '200px',
height: '100%',
},
})

View file

@ -0,0 +1,35 @@
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.isAuthed ? (
<>
<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%',
},
})

12
src/view/shell/index.tsx Normal file
View file

@ -0,0 +1,12 @@
import React from 'react'
import {SafeAreaView} from 'react-native'
import {isDesktopWeb} from '../../platform/detection'
import {DesktopWebShell} from './desktop-web/shell'
export const Shell: React.FC = ({children}) => {
return isDesktopWeb ? (
<DesktopWebShell>{children}</DesktopWebShell>
) : (
<SafeAreaView>{children}</SafeAreaView>
)
}