diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/routes/index.tsx | 110 | ||||
-rw-r--r-- | src/view/routes/types.ts | 36 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 21 | ||||
-rw-r--r-- | src/view/screens/Login.tsx | 29 | ||||
-rw-r--r-- | src/view/screens/Menu.tsx | 16 | ||||
-rw-r--r-- | src/view/screens/NotFound.tsx | 15 | ||||
-rw-r--r-- | src/view/screens/Notifications.tsx | 14 | ||||
-rw-r--r-- | src/view/screens/Profile.tsx | 16 | ||||
-rw-r--r-- | src/view/screens/Search.tsx | 14 | ||||
-rw-r--r-- | src/view/screens/Signup.tsx | 34 | ||||
-rw-r--r-- | src/view/shell/desktop-web/left-column.tsx | 57 | ||||
-rw-r--r-- | src/view/shell/desktop-web/right-column.tsx | 19 | ||||
-rw-r--r-- | src/view/shell/desktop-web/shell.tsx | 35 | ||||
-rw-r--r-- | src/view/shell/index.tsx | 12 |
14 files changed, 428 insertions, 0 deletions
diff --git a/src/view/routes/index.tsx b/src/view/routes/index.tsx new file mode 100644 index 000000000..6351dea6a --- /dev/null +++ b/src/view/routes/index.tsx @@ -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> + ) +}) diff --git a/src/view/routes/types.ts b/src/view/routes/types.ts new file mode 100644 index 000000000..d92594bbe --- /dev/null +++ b/src/view/routes/types.ts @@ -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> + > +*/ diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx new file mode 100644 index 000000000..5210d9d40 --- /dev/null +++ b/src/view/screens/Home.tsx @@ -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> + ) +} diff --git a/src/view/screens/Login.tsx b/src/view/screens/Login.tsx new file mode 100644 index 000000000..207557369 --- /dev/null +++ b/src/view/screens/Login.tsx @@ -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> + ) +}) diff --git a/src/view/screens/Menu.tsx b/src/view/screens/Menu.tsx new file mode 100644 index 000000000..8cf93676e --- /dev/null +++ b/src/view/screens/Menu.tsx @@ -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> + ) +} diff --git a/src/view/screens/NotFound.tsx b/src/view/screens/NotFound.tsx new file mode 100644 index 000000000..3f6dd7aa0 --- /dev/null +++ b/src/view/screens/NotFound.tsx @@ -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> + ) +} diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx new file mode 100644 index 000000000..5bade68fa --- /dev/null +++ b/src/view/screens/Notifications.tsx @@ -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> + ) +} diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx new file mode 100644 index 000000000..2c93f4bf9 --- /dev/null +++ b/src/view/screens/Profile.tsx @@ -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> + ) +} diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx new file mode 100644 index 000000000..2f111cf72 --- /dev/null +++ b/src/view/screens/Search.tsx @@ -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> + ) +} diff --git a/src/view/screens/Signup.tsx b/src/view/screens/Signup.tsx new file mode 100644 index 000000000..8ca47e3ef --- /dev/null +++ b/src/view/screens/Signup.tsx @@ -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> + ) + }, +) diff --git a/src/view/shell/desktop-web/left-column.tsx b/src/view/shell/desktop-web/left-column.tsx new file mode 100644 index 000000000..082231ec9 --- /dev/null +++ b/src/view/shell/desktop-web/left-column.tsx @@ -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', + }, +}) diff --git a/src/view/shell/desktop-web/right-column.tsx b/src/view/shell/desktop-web/right-column.tsx new file mode 100644 index 000000000..5fe65cac8 --- /dev/null +++ b/src/view/shell/desktop-web/right-column.tsx @@ -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%', + }, +}) diff --git a/src/view/shell/desktop-web/shell.tsx b/src/view/shell/desktop-web/shell.tsx new file mode 100644 index 000000000..13acbbfed --- /dev/null +++ b/src/view/shell/desktop-web/shell.tsx @@ -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%', + }, +}) diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx new file mode 100644 index 000000000..db60ed149 --- /dev/null +++ b/src/view/shell/index.tsx @@ -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> + ) +} |