feat: google auth welcome page

This commit is contained in:
2024-11-01 20:34:57 +01:00
parent 530817812f
commit 7c0b759b14
15 changed files with 177 additions and 168 deletions

113
app/(app)/_layout.tsx Normal file
View File

@@ -0,0 +1,113 @@
import { MaterialCommunityIcons } from '@expo/vector-icons'
import {
useFonts,
JetBrainsMono_400Regular,
} from '@expo-google-fonts/jetbrains-mono'
import { NotoSans_400Regular } from '@expo-google-fonts/noto-sans'
import { Redirect, SplashScreen, Stack, useRootNavigationState } from 'expo-router'
import * as SecureStore from 'expo-secure-store'
import React from 'react'
import { useColorScheme } from 'react-native'
import { PaperProvider } from 'react-native-paper'
import { useAuth } from '@/lib/providers/auth'
import { Setting } from '@/lib/types'
import { StackHeader, Themes } from '@/lib/ui'
export {
// Catch any errors thrown by the Layout component.
ErrorBoundary,
} from 'expo-router'
export const unstable_settings = {
// Ensure that reloading on `/modal` keeps a back button present.
initialRouteName: '(root)/login',
}
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync()
const RootLayout = () => {
const { user } = useAuth()
const [loaded, error] = useFonts({
NotoSans_400Regular,
JetBrainsMono_400Regular,
...MaterialCommunityIcons.font,
})
// Get authentication state
const rootNavigationState = useRootNavigationState()
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
React.useEffect(() => {
if (error) throw error
}, [error])
React.useEffect(() => {
if (loaded) {
SplashScreen.hideAsync()
}
}, [loaded])
// Make sure we have the navigation state before showing content
if (!loaded || !rootNavigationState) {
return null
}
if (!user) {
console.log('redirecting inside the root')
return <Redirect href="/login" />
}
return <RootLayoutNav />
}
const RootLayoutNav = () => {
const colorScheme = useColorScheme()
const [settings, setSettings] = React.useState<Setting>({
theme: 'auto',
color: 'default',
})
// Load settings from the device
React.useEffect(() => {
SecureStore.getItemAsync('settings').then((result) => {
if (result === null) {
SecureStore.setItemAsync('settings', JSON.stringify(settings)).then(
(res) => console.log(res),
)
}
setSettings(JSON.parse(result ?? JSON.stringify(settings)))
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<PaperProvider
theme={
Themes[
settings.theme === 'auto' ? (colorScheme ?? 'dark') : settings.theme
][settings.color]
}
>
<Stack
screenOptions={{
animation: 'slide_from_bottom',
header: (props) => (
<StackHeader navProps={props} children={undefined} />
),
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="drawer" options={{ headerShown: false }} />
<Stack.Screen name="search" options={{ title: 'Search' }} />
<Stack.Screen
name="modal"
options={{ title: 'Modal', presentation: 'modal' }}
/>
</Stack>
</PaperProvider>
)
}
export default RootLayout

View File

@@ -1,7 +0,0 @@
import { Text } from "react-native-paper";
export default function Login() {
return (
<Text>Login please</Text>
)
}

View File

@@ -1,42 +0,0 @@
import { ScrollViewStyleReset } from 'expo-router/html'
import React from 'react'
// This file is web-only and used to configure the root HTML for every
// web page during static rendering.
// The contents of this function only run in Node.js environments and
// do not have access to the DOM or browser APIs.
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{/*
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
*/}
<ScrollViewStyleReset />
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
{/* Add any additional <head> elements that you want globally available on web... */}
</head>
<body>{children}</body>
</html>
)
}
const responsiveBackground = `
body {
background-color: #fff;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
}
}`

View File

@@ -1,111 +1,11 @@
import { MaterialCommunityIcons } from '@expo/vector-icons'
import {
useFonts,
JetBrainsMono_400Regular,
} from '@expo-google-fonts/jetbrains-mono'
import { NotoSans_400Regular } from '@expo-google-fonts/noto-sans'
import { Redirect, SplashScreen, Stack, useRootNavigationState, useRouter } from 'expo-router'
import * as SecureStore from 'expo-secure-store'
import React from 'react'
import { Platform, useColorScheme } from 'react-native'
import { PaperProvider } from 'react-native-paper'
import { AuthProvider, useAuth } from '@/lib/providers/auth'
import { Setting } from '@/lib/types'
import { StackHeader, Themes } from '@/lib/ui'
export {
// Catch any errors thrown by the Layout component.
ErrorBoundary,
} from 'expo-router'
export const unstable_settings = {
// Ensure that reloading on `/modal` keeps a back button present.
initialRouteName: '(tabs)',
}
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync()
const RootLayout = () => {
const [loaded, error] = useFonts({
NotoSans_400Regular,
JetBrainsMono_400Regular,
...MaterialCommunityIcons.font,
})
// Get authentication state
const rootNavigationState = useRootNavigationState()
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
React.useEffect(() => {
if (error) throw error
}, [error])
React.useEffect(() => {
if (loaded) {
SplashScreen.hideAsync()
}
}, [loaded])
// Make sure we have the navigation state before showing content
if (!loaded || !rootNavigationState) {
return null
}
return <RootLayoutNav />
}
const RootLayoutNav = () => {
const colorScheme = useColorScheme()
const [settings, setSettings] = React.useState<Setting>({
theme: 'auto',
color: 'default',
})
// Load settings from the device
React.useEffect(() => {
SecureStore.getItemAsync('settings').then((result) => {
if (result === null) {
SecureStore.setItemAsync('settings', JSON.stringify(settings)).then(
(res) => console.log(res),
)
}
setSettings(JSON.parse(result ?? JSON.stringify(settings)))
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
import { Redirect, Slot } from 'expo-router'
export default function Root() {
return (
<AuthProvider>
<PaperProvider
theme={
Themes[
settings.theme === 'auto' ? (colorScheme ?? 'dark') : settings.theme
][settings.color]
}
>
<Stack
screenOptions={{
animation: 'slide_from_bottom',
header: (props) => (
<StackHeader navProps={props} children={undefined} />
),
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="drawer" options={{ headerShown: false }} />
<Stack.Screen name="search" options={{ title: 'Search' }} />
<Stack.Screen
name="modal"
options={{ title: 'Modal', presentation: 'modal' }}
/>
</Stack>
</PaperProvider>
<Slot />
</AuthProvider>
)
}
export default RootLayout

38
app/login.tsx Normal file
View File

@@ -0,0 +1,38 @@
import { useAuth } from '@/lib/providers/auth'
import { useRouter } from 'expo-router'
import { useEffect } from 'react'
import { View } from 'react-native'
import { Button, Text } from 'react-native-paper'
function Login() {
const { signIn, user } = useAuth()
const router = useRouter()
useEffect(() => {
if (user) {
console.log('redicrecting')
router.push('/')
}
}, [user])
useEffect(() => {
console.log('login page')
}, [])
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
padding: 16,
}}
>
<Text variant="headlineMedium">Welcome</Text>
<Button mode="contained" icon="google" onPress={async () => await signIn()}>
Login with Google
</Button>
</View>
)
}
export default Login

View File

@@ -7,6 +7,8 @@ import {
useContext,
useEffect,
useState,
useCallback,
useMemo,
type ReactNode,
} from 'react'
@@ -17,21 +19,21 @@ interface AuthProviderProps {
export function AuthProvider({ children }: AuthProviderProps) {
const [user, setUser] = useState<User | null>(null)
GoogleSignin.configure({
scopes: [
'https://www.googleapis.com/auth/classroom.courses.readonly',
'https://www.googleapis.com/auth/classroom.coursework.me',
'https://www.googleapis.com/auth/classroom.coursework.students',
'https://www.googleapis.com/auth/classroom.coursework.students.readonly',
'https://www.googleapis.com/auth/classroom.coursework.me.readonly',
],
})
useEffect(() => {
GoogleSignin.configure({
scopes: [
'https://www.googleapis.com/auth/classroom.courses.readonly',
'https://www.googleapis.com/auth/classroom.coursework.me',
'https://www.googleapis.com/auth/classroom.coursework.students',
'https://www.googleapis.com/auth/classroom.coursework.students.readonly',
'https://www.googleapis.com/auth/classroom.coursework.me.readonly',
],
})
setUser(GoogleSignin.getCurrentUser())
}, [])
const signIn = async () => {
const signIn = useCallback(async () => {
try {
await GoogleSignin.hasPlayServices()
const userInfo = await GoogleSignin.signIn()
@@ -40,19 +42,24 @@ export function AuthProvider({ children }: AuthProviderProps) {
} catch (error) {
console.error(error)
}
}
}, [])
const signOut = async () => {
const signOut = useCallback(async () => {
try {
await GoogleSignin.signOut()
setUser(null)
} catch (error) {
console.error(error)
}
}
}, [])
const authContextValue = useMemo(
() => ({ user, signIn, signOut }),
[user, signIn, signOut],
)
return (
<AuthContext.Provider value={{ user, signIn, signOut }}>
<AuthContext.Provider value={authContextValue}>
{children}
</AuthContext.Provider>
)

View File

@@ -6,5 +6,5 @@
"@/*": ["./*"]
}
},
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts", "app/(app)/plushtml.old"]
}