Skip to content

Commit

Permalink
Add mobile authentication flow
Browse files Browse the repository at this point in the history
  • Loading branch information
czystyl committed Aug 16, 2023
1 parent 370a75e commit 1746f2f
Show file tree
Hide file tree
Showing 16 changed files with 947 additions and 1,347 deletions.
1 change: 0 additions & 1 deletion apps/mobile/index.tsx

This file was deleted.

5 changes: 4 additions & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@bank-brew/mobile",
"version": "0.1.0",
"private": true,
"main": "index.tsx",
"main": "expo-router/entry",
"scripts": {
"clean": "git clean -xdf .expo .turbo node_modules",
"dev": "expo start --ios",
Expand All @@ -15,12 +15,14 @@
"dependencies": {
"@clerk/clerk-expo": "^0.18.17",
"@expo/metro-config": "^0.10.6",
"@react-native-async-storage/async-storage": "1.18.2",
"@shopify/flash-list": "1.4.3",
"@tanstack/react-query": "^4.29.23",
"@trpc/client": "^10.34.0",
"@trpc/react-query": "^10.34.0",
"@trpc/server": "^10.34.0",
"expo": "^49.0.3",
"@bank-brew/env": "^0.1.0",
"expo-auth-session": "~5.0.2",
"expo-constants": "~14.4.2",
"expo-linking": "~5.0.2",
Expand All @@ -44,6 +46,7 @@
"@bank-brew/eslint-config": "^0.2.0",
"@bank-brew/tailwind-config": "^0.1.0",
"@expo/config-plugins": "^7.2.5",
"@total-typescript/ts-reset": "^0.4.2",
"@types/babel__core": "^7.20.1",
"@types/react": "^18.2.15",
"eslint": "^8.45.0",
Expand Down
12 changes: 12 additions & 0 deletions apps/mobile/src/app/(auth)/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Text, View } from "react-native";

import { useAuth } from "~/utils/authProvider";

export default function Onboarding() {
const { completeOnboarding } = useAuth();
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text onPress={completeOnboarding}>ONBOARDING</Text>
</View>
);
}
6 changes: 4 additions & 2 deletions apps/mobile/src/app/(auth)/sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function SignIn() {
redirectUrl: "exp://",
});

const { user } = useAuth();
const { resetOnboarding } = useAuth();

const onGooglePress = useCallback(async () => {
try {
Expand All @@ -26,8 +26,10 @@ export default function SignIn() {

return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>{user?.id}</Text>
<Text onPress={onGooglePress}>Sign In</Text>
<Text onPress={resetOnboarding} className="mt-10 text-xl">
RESET
</Text>
</View>
);
}
File renamed without changes.
16 changes: 15 additions & 1 deletion apps/mobile/src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,28 @@ export default function RootLayout() {
<SafeAreaProvider>
<StatusBar />

<Stack>
<Stack initialRouteName="index">
<Stack.Screen
name="index"
options={{
title: "",
animation: "none",
}}
/>
<Stack.Screen
name="home"
options={{
title: "Home",
animation: "none",
}}
/>
<Stack.Screen
name="(auth)/onboarding"
options={{
title: "Onboarding 🎉",
animation: "none",
}}
/>
<Stack.Screen
name="(auth)/sign-in"
options={{
Expand Down
31 changes: 31 additions & 0 deletions apps/mobile/src/app/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ScrollView, Text, View } from "react-native";

import { api } from "~/utils/api";
import { useAuth } from "../utils/authProvider";

export default function Index() {
const { signOut, user, resetOnboarding, completeOnboarding } = useAuth();

const { data } = api.transaction.allNot.useQuery();

return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>id: {user?.id}!</Text>
<Text>
name: {user?.firstName} {user?.lastName}!
</Text>
<Text onPress={() => signOut()}>Sign Out</Text>
<Text className="mt-10 text-xl" onPress={resetOnboarding}>
RESET
</Text>
<Text className="mt-10 text-xl" onPress={completeOnboarding}>
SET
</Text>
<ScrollView>
{data?.map((transaction) => (
<Text key={transaction.id}>{transaction.id}</Text>
))}
</ScrollView>
</View>
);
}
18 changes: 7 additions & 11 deletions apps/mobile/src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { Text, View } from "react-native";
import { Redirect } from "expo-router";

import { useAuth } from "../utils/authProvider";

export default function Index() {
const { signOut, user } = useAuth();
const { ready } = useAuth();

return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>id: {user?.id}!</Text>
<Text>
name: {user?.firstName} {user?.lastName}!
</Text>
<Text onPress={() => signOut()}>Sign Out</Text>
</View>
);
if (!ready) {
return null;
}

return <Redirect href="/home" />;
}
84 changes: 74 additions & 10 deletions apps/mobile/src/utils/authProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import React, { createContext, useEffect } from "react";
import "@total-typescript/ts-reset";

import React, { createContext, useEffect, useState } from "react";
import { Text } from "react-native";
import {
router,
SplashScreen,
Expand All @@ -7,54 +10,78 @@ import {
} from "expo-router";
import { useAuth as useClerkAuth, useUser } from "@clerk/clerk-expo";
import type { UserResource } from "@clerk/types";
import AsyncStorage from "@react-native-async-storage/async-storage";

SplashScreen.preventAutoHideAsync();

interface AuthContextType {
signOut: () => Promise<void>;
user: UserResource | null;
ready: boolean;
resetOnboarding: () => void;
completeOnboarding: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider(props: { children: React.ReactNode }) {
const segments = useSegments();
const rootNavigationState = useRootNavigationState();
const {
isOnboardingCompleted,
isOnboardingValueChecked,
resetOnboarding,
completeOnboarding,
} = useOnboardingSettings();

const { user } = useUser();
const { isLoaded, signOut } = useClerkAuth();

useEffect(() => {
if (isLoaded) {
if (isLoaded && isOnboardingValueChecked) {
SplashScreen.hideAsync();
}
}, [isLoaded]);
}, [isLoaded, isOnboardingValueChecked]);

useEffect(() => {
if (!rootNavigationState?.key || !isLoaded) {
return;
}

const inAuthGroup = segments[0] === "(auth)";
const isAuthSegment = segments[0] === "(auth)";

if (!user && !inAuthGroup) {
return router.replace("/sign-in");
if (user && isAuthSegment) {
return router.replace("/home");
}

if (user && inAuthGroup) {
return router.replace("/");
if (!user) {
if (!isOnboardingCompleted) {
return router.replace("(auth)/onboarding");
}

return router.replace("(auth)/sign-in");
}
}, [isLoaded, rootNavigationState?.key, segments, user]);
}, [
isLoaded,
rootNavigationState?.key,
segments,
user,
isOnboardingCompleted,
]);

return (
<AuthContext.Provider
value={{
signOut,
user: user ?? null,
ready: isLoaded,
ready: isLoaded && isOnboardingValueChecked,
resetOnboarding,
completeOnboarding,
}}
>
<Text className="text-xl">
Onboarding: {isOnboardingCompleted?.toString()}
</Text>
{props.children}
</AuthContext.Provider>
);
Expand All @@ -70,3 +97,40 @@ export function useAuth() {

return context;
}

function useOnboardingSettings() {
const [value, setValue] = useState<boolean | null | undefined>(undefined);

useEffect(() => {
AsyncStorage.getItem("ONBOARDING_COMPLETE")
.then((value) => {
if (!value) {
return setValue(false);
}

try {
const parsedValue = JSON.parse(value);

setValue(Boolean(parsedValue));
} catch (error) {
setValue(false);
}
})
.catch(() => {
setValue(false);
});
});

return {
isOnboardingCompleted: value,
isOnboardingValueChecked: value !== undefined,
completeOnboarding: () => {
setValue(true);
void AsyncStorage.setItem("ONBOARDING_COMPLETE", JSON.stringify(true));
},
resetOnboarding: () => {
setValue(false);
void AsyncStorage.setItem("ONBOARDING_COMPLETE", JSON.stringify(false));
},
};
}
6 changes: 4 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
"scripts": {
"build": "next build",
"clean": "git clean -xdf .next .turbo node_modules",
"dev": "next dev",
"dev": "pnpm with-env next dev",
"lint": "dotenv -v SKIP_ENV_VALIDATION=1 next lint",
"lint:fix": "pnpm lint --fix",
"start": "next start",
"type-check": "tsc --noEmit"
"type-check": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@bank-brew/api": "*",
"@bank-brew/db": "*",
"@bank-brew/env": "*",
"@clerk/nextjs": "^4.22.1",
"@t3-oss/env-nextjs": "^0.6.0",
"@tanstack/react-query": "^4.29.23",
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { authMiddleware } from "@clerk/nextjs";

import "@bank-brew/env";

// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/nextjs/middleware for more information about configuring your middleware
Expand Down
1 change: 0 additions & 1 deletion packages/db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"drizzle-orm": "^0.28.0"
},
"devDependencies": {
"dotenv-cli": "^7.2.1",
"drizzle-kit": "^0.19.12",
"typescript": "^5.1.6"
}
Expand Down
9 changes: 5 additions & 4 deletions packages/env/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import { createEnv } from "@t3-oss/env-nextjs";
import { config } from "dotenv";
import { z } from "zod";

// Load environment variables from global .env file
config({ path: "../../.env" });

export const env = createEnv({
skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION,
/*
Expand All @@ -20,14 +17,15 @@ export const env = createEnv({
])
.default("development"),
DATABASE_URL: z.string().min(1),
CLERK_SECRET_KEY: z.string().min(1),
},
/*
* Environment variables available on the client (and server).
*
* 💡 You'll get type errors if these are not prefixed with NEXT_PUBLIC_.
*/
client: {
// NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
},
/*
* Due to how Next.js bundles environment variables on Edge and Client,
Expand All @@ -38,5 +36,8 @@ export const env = createEnv({
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
DATABASE_URL: process.env.DATABASE_URL,
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
},
});
3 changes: 0 additions & 3 deletions packages/env/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,5 @@
"dependencies": {
"@t3-oss/env-nextjs": "^0.6.0",
"zod": "^3.21.4"
},
"devDependencies": {
"dotenv-cli": "^7.2.1"
}
}
Loading

0 comments on commit 1746f2f

Please sign in to comment.