Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
chasenlehara committed May 27, 2024
1 parent d987d65 commit a629769
Show file tree
Hide file tree
Showing 43 changed files with 539 additions and 295 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const Settings: React.FC = () => {
return (
<Screen>
<Card>
<Typography variant="heading">Welcome back, {user.name}</Typography>
<Typography variant="heading">Welcome back</Typography>
<Typography variant="body">{user.name || "Unknown name"}</Typography>
</Card>
<Card>
<View style={styles.row}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const RestaurantDetails: React.FC = () => {
<Screen>
<RestaurantHeader restaurant={restaurant} />
<Button onPress={() => console.warn("Place an order")}>
Place My Order!
Place an order
</Button>
</Screen>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe("Screens/RestaurantDetails", () => {
</NavigationContainer>,
)

expect(screen.getByText("Place My Order!")).toBeOnTheScreen()
expect(screen.getByText("Place an order")).toBeOnTheScreen()
})

it("renders loading state", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const RestaurantDetails: React.FC<RestaurantDetailsProps> = ({ route }) => {
<Screen>
<RestaurantHeader restaurant={restaurant} />
<Button onPress={() => console.warn("Place an order")}>
Place My Order!
Place an order
</Button>
</Screen>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const RestaurantDetails: React.FC<RestaurantDetailsProps> = ({ route }) => {
<Screen>
<RestaurantHeader restaurant={restaurant} />
<Button onPress={() => console.warn("Place an order")}>
Place My Order!
Place an order
</Button>
</Screen>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const RestaurantDetails: React.FC<RestaurantDetailsProps> = ({ route }) => {
<Screen>
<RestaurantHeader restaurant={restaurant} />
<Button onPress={() => console.warn("Place an order")}>
Place My Order!
Place an order
</Button>
{/* Exercise: Add Button that links to RestaurantOrder page. */}
</Screen>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const RestaurantDetails: React.FC<RestaurantDetailsProps> = ({ route }) => {
navigation.navigate("RestaurantOrder", { slug: slug })
}}
>
Place My Order!
Place an order
</Button>
</Screen>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
readonly GOOGLE_OAUTH_CLIENT_ID: string
readonly PMO_API: string
readonly PMO_ASSETS: string
readonly GOOGLE_OAUTH_CLIENT_ID: string
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe("Screens/Settings", () => {
</NavigationContainer>
</AuthProvider>,
)
expect(screen.getByText(/Mock Sign in with Google/i)).toBeOnTheScreen()
expect(screen.getByText(/Loading…/i)).not.toBeNull()
})

it("switches to dark mode", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ import Typography from "../../design/Typography"
import { useAuthentication, useUser } from "../../services/auth"

const Settings: FC = () => {
// Exercise: use the hooks from auth service to grab the 'user' state, and 'signIn' and 'signOut' callbacks.
// Exercise: use the hooks from the auth service to grab the 'user' state, and 'signIn' and 'signOut' callbacks.
const { mode, setMode } = useThemeMode()

return (
<Screen>
<Card>
{/* Exercise: If the user is logged in: Render a button using the signOut callback along with a welcome message.
If the user is logged in: Exercise: Render a button using the GoogleSigninButton. */}
{/*
Exercise:
- If the user is logged in: Render a button using the signOut callback along with a welcome message.
- If the user is logged in: Exercise: Render a button using the GoogleSigninButton.
*/}
<Typography variant="heading"></Typography>
</Card>
<Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("Services/Auth/AuthProvider", () => {
<Text>{user?.photo}</Text>
<Text>{user?.givenName}</Text>
<Text>{user?.familyName}</Text>
{isAuthenticated && <Button onPress={signOut}>Sign Out</Button>}
{isAuthenticated && <Button onPress={signOut}>Sign out</Button>}
{isAuthenticated === false && <GoogleSigninButton onPress={signIn} />}
</View>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
const signOut = async () => {}

Check warning on line 23 in exercise-src/react-native/16-security/01-problem/src/services/auth/AuthProvider.tsx

View workflow job for this annotation

GitHub Actions / job

The 'signOut' function makes the dependencies of useMemo Hook (at line 46) change on every render. Move it inside the useMemo callback. Alternatively, wrap the definition of 'signOut' in its own useCallback() Hook

useEffect(() => {
// Exercise: When a sign in is successful, update the user.
async function run() {}
async function run() {
// Exercise: When a sign in is successful, update the user.
}

run()
}, [])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@ import { createContext, useContext } from "react"
export interface AuthContext {
/** Initiate the Google Auth flow. Return boolean success. */
signIn: () => Promise<UserInfo["user"] | false>

/** Log the user out from Google Auth. Return boolean success. */
signOut: () => Promise<boolean>

/** Error if any Google API returns an error. Undefined if no errors in the last API call. */
error?: Error | undefined

/** Boolean if the user is authenticated or not. Undefined if unknown. */
isAuthenticated?: boolean

/** Boolean if a Google API call is being made. */
isPending: boolean

/** Google Auth User object. Undefined if not signed in. */
user?: UserInfo["user"]

/** List of Google Auth scopes. Undefined if not signed in. */
scopes?: UserInfo["scopes"]

/** Google Auth Token. Undefined if not signed in. */
idToken?: UserInfo["idToken"]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GoogleSigninButton } from "@react-native-google-signin/google-signin"
import { StyleSheet, Switch, View } from "react-native"

import Loading from "../../components/Loading"
import Button from "../../design/Button"
import Card from "../../design/Card"
import Screen from "../../design/Screen"
Expand All @@ -9,20 +10,33 @@ import Typography from "../../design/Typography"
import { useAuthentication, useUser } from "../../services/auth"

const Settings: React.FC = () => {
const { signIn, signOut } = useAuthentication()
const { error, isPending, signIn, signOut } = useAuthentication()
const user = useUser()
const { mode, setMode } = useThemeMode()

return (
<Screen>
<Card>
{user ? (
{isPending ? (
<Loading />
) : user ? (
<>
<Typography variant="heading">Welcome back, {user.name}</Typography>
<Button onPress={signOut}>Sign Out</Button>
<Typography variant="heading">Welcome back</Typography>
<Typography variant="body">
{user.name || "Unknown name"}
</Typography>
<Button onPress={signOut}>Sign out</Button>
{error ? (
<Typography variant="body">Error: {error.message}</Typography>
) : null}
</>
) : (
<GoogleSigninButton onPress={signIn} style={{ width: "100%" }} />
<>
<GoogleSigninButton onPress={signIn} style={{ width: "100%" }} />
{error ? (
<Typography variant="body">Error: {error.message}</Typography>
) : null}
</>
)}
</Card>
<Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,72 @@ export interface AuthProviderProps {
}

const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [error, setError] = useState<Error | undefined>()
const [isPending, setIsPending] = useState<boolean>(true)
const [userInfo, setUserInfo] = useState<UserInfo | undefined>()

const signIn = useCallback(async () => {
try {
setError(undefined)
setIsPending(true)

const userInfo = await GoogleSignin.signIn()

setIsPending(false)
setUserInfo(userInfo)

return userInfo.user
} catch (error) {
console.error("Call to GoogleSignin.signIn() failed with error:", error)

setError(error as Error)
setIsPending(false)
setUserInfo(undefined)
console.error("GoogleSignin.signIn() error", error)

return false
}
}, [])

const signOut = useCallback(async () => {
try {
setError(undefined)
setIsPending(true)

await GoogleSignin.signOut()

setIsPending(false)
setUserInfo(undefined)

return true
} catch (error) {
console.error("GoogleSignin.signOut() error", error)
console.error("Call to GoogleSignin.signOut() failed with error:", error)

setError(error as Error)
setIsPending(false)

return false
}
}, [])

useEffect(() => {
async function run() {
const userInfo = await GoogleSignin.getCurrentUser()
setUserInfo(userInfo || undefined)
try {
setError(undefined)
setIsPending(true)

const userInfo = await GoogleSignin.getCurrentUser()

setIsPending(false)
setUserInfo(userInfo || undefined)
} catch (error) {
console.error(
"Call to GoogleSignin.getCurrentUser() failed with error:",
error,
)

setError(error as Error)
setIsPending(false)
}
}

run()
Expand All @@ -55,27 +92,32 @@ const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
() => ({
signIn,
signOut,
error,
isAuthenticated: userInfo
? true
: userInfo === undefined
? false
: undefined,
isPending,
user: userInfo?.user,
scopes: userInfo?.scopes,
idToken: userInfo?.idToken,
}),
[signIn, signOut, userInfo],
[error, isPending, signIn, signOut, userInfo],
)

return <AuthContextProvider value={value}>{children}</AuthContextProvider>
}

export default AuthProvider

export function useAuthentication(): Pick<AuthContext, "signIn" | "signOut"> {
const { signIn, signOut } = useAuthContext()
export function useAuthentication(): Pick<
AuthContext,
"error" | "isPending" | "signIn" | "signOut"
> {
const { error, isPending, signIn, signOut } = useAuthContext()

return { signIn, signOut }
return { error, isPending, signIn, signOut }
}

export function useAuthenticated(): boolean | undefined {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { NavigationContainer } from "@react-navigation/native"
import { fireEvent, render, screen } from "@testing-library/react-native"

import AuthProvider from "../../services/auth/AuthProvider"

import Settings from "./Settings"

const mockSetMode = jest.fn()

jest.mock("../../design/theme", () => ({
...jest.requireActual("../../design/theme"),
useThemeMode: () => ({
mode: "light",
setMode: mockSetMode,
}),
}))

describe("Screens/Settings", () => {
it("renders", async () => {
render(
<AuthProvider>
<NavigationContainer>
<Settings />
</NavigationContainer>
</AuthProvider>,
)
expect(screen.getByText(/Loading…/i)).not.toBeNull()
})

it("switches to dark mode", () => {
render(
<AuthProvider>
<NavigationContainer>
<Settings />
</NavigationContainer>
</AuthProvider>,
)

const switchElement = screen.getByRole("switch")
expect(switchElement.props.value).toBe(false)

fireEvent(switchElement, "onValueChange", true)
expect(mockSetMode).toHaveBeenCalledWith("dark")
})

it("displays the correct connection status", () => {
render(
<AuthProvider>
<NavigationContainer>
<Settings />
</NavigationContainer>
</AuthProvider>,
)

expect(screen.getByText(/Connection status: Online/i)).not.toBeNull()
})
})
Loading

0 comments on commit a629769

Please sign in to comment.