Skip to content

Commit

Permalink
clerk based sign in
Browse files Browse the repository at this point in the history
  • Loading branch information
DOOduneye committed Mar 18, 2024
1 parent 32afef4 commit 3329cce
Show file tree
Hide file tree
Showing 6 changed files with 526 additions and 101 deletions.
11 changes: 8 additions & 3 deletions frontend/sac-mobile/app/(app)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import React from 'react';
import { Text, View } from 'react-native';

import { Button } from '@/components/button';

Check failure on line 4 in frontend/sac-mobile/app/(app)/index.tsx

View workflow job for this annotation

GitHub Actions / Lint

Replace `Button·}·from·'@/components/button';` with `useAuth·}·from·'@clerk/clerk-expo';⏎`
import { useAuthStore } from '@/hooks/use-auth';
import { useAuth } from '@clerk/clerk-expo';

Check failure on line 5 in frontend/sac-mobile/app/(app)/index.tsx

View workflow job for this annotation

GitHub Actions / Lint

Replace `useAuth·}·from·'@clerk/clerk-expo` with `Button·}·from·'@/components/button`

const Home = () => {
const { logout } = useAuthStore();
const { signOut } = useAuth();

const handleSignOut = async () => {
await signOut();
}

Check failure on line 12 in frontend/sac-mobile/app/(app)/index.tsx

View workflow job for this annotation

GitHub Actions / Lint

Insert `;`

return (
<View className="items-center justify-center flex-1">
<Button onPress={logout}>Logout</Button>
<Button onPress={handleSignOut}>Sign Out</Button>
<Text>Home</Text>
</View>
);
Expand Down
47 changes: 31 additions & 16 deletions frontend/sac-mobile/app/(auth)/_components/login-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import Error from '@/components/error';
import Input from '@/components/input';
import { useAuthStore } from '@/hooks/use-auth';

Check failure on line 11 in frontend/sac-mobile/app/(auth)/_components/login-form.tsx

View workflow job for this annotation

GitHub Actions / Lint

'useAuthStore' is defined but never used
import { loginByEmail } from '@/services/auth';

Check failure on line 12 in frontend/sac-mobile/app/(auth)/_components/login-form.tsx

View workflow job for this annotation

GitHub Actions / Lint

'loginByEmail' is defined but never used

Check failure on line 12 in frontend/sac-mobile/app/(auth)/_components/login-form.tsx

View workflow job for this annotation

GitHub Actions / Lint

Delete `';⏎import·{·useSignIn·}·from·'@clerk/clerk-expo';⏎import·{·useState·}·from·'react';⏎import·Spinner·from·'react-native-loading-spinner-overlay`
import { useSignIn } from '@clerk/clerk-expo';
import { useState } from 'react';
import Spinner from 'react-native-loading-spinner-overlay';

type LoginFormData = {
email: string;
Expand All @@ -29,28 +32,40 @@ const LoginForm = () => {
handleSubmit,
formState: { errors }
} = useForm<LoginFormData>();
const { login } = useAuthStore();

const onSubmit = async (data: LoginFormData) => {
const { signIn, setActive, isLoaded } = useSignIn();
const [loading, setLoading] = useState(false);

const onSignInPress = async (loginData: LoginFormData) => {
if (!isLoaded) {
return;
}

setLoading(true);

try {
loginSchema.parse(data);
const { user, tokens } = await loginByEmail(
data.email.toLowerCase(),
data.password
);
login(tokens, user);
router.push('/(app)/');
} catch (e: unknown) {
if (e instanceof ZodError) {
Alert.alert('Validation Error', e.errors[0].message); // use a better way to display errors
const validData = loginSchema.parse(loginData);

const completeSignIn = await signIn.create({
identifier: validData.email,
password: validData.password
});

await setActive({ session: completeSignIn.createdSessionId });
} catch (err: any) {
if (err instanceof ZodError) {
Alert.alert(err.errors[0].message);
} else {
console.error('An unexpected error occurred:', e);
Alert.alert('An error occurred', err.message);
}
} finally {
setLoading(false);
}
};

return (
<>
{loading && <Spinner visible={loading} />}
<View>
<Controller
control={control}
Expand All @@ -61,7 +76,7 @@ const LoginForm = () => {
placeholder="[email protected]"
onChangeText={onChange}
value={value}
onSubmitEditing={handleSubmit(onSubmit)}
onSubmitEditing={handleSubmit(onSignInPress)}
error={!!errors.email}
/>
)}
Expand All @@ -81,7 +96,7 @@ const LoginForm = () => {
onChangeText={onChange}
value={value}
secureTextEntry={true}
onSubmitEditing={handleSubmit(onSubmit)}
onSubmitEditing={handleSubmit(onSignInPress)}
error={!!errors.password}
/>
)}
Expand All @@ -106,7 +121,7 @@ const LoginForm = () => {
<Button
size="lg"
variant="default"
onPress={handleSubmit(onSubmit)}
onPress={handleSubmit(onSignInPress)}
>
Log in
</Button>
Expand Down
121 changes: 51 additions & 70 deletions frontend/sac-mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,100 +1,81 @@
import { useEffect } from 'react';
import { Text, View } from 'react-native';

import { useFonts } from 'expo-font';
import { Stack, router } from 'expo-router';
import { getItemAsync } from 'expo-secure-store';
import { Slot, Stack, useRouter, useSegments } from 'expo-router';

Check failure on line 4 in frontend/sac-mobile/app/_layout.tsx

View workflow job for this annotation

GitHub Actions / Lint

'Stack' is defined but never used
import * as SplashScreen from 'expo-splash-screen';

import FontAwesome from '@expo/vector-icons/FontAwesome';

import { useAuthStore } from '@/hooks/use-auth';
import { User } from '@/types/user';
import { ClerkProvider, useAuth } from '@clerk/clerk-expo';
import * as SecureStore from 'expo-secure-store';

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: ''
};
const CLERK_PUBLISHABLE_KEY = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY

// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();

export default function RootLayout() {
const [loaded, error] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
...FontAwesome.font
});
const tokenCache = {
async getToken(key: string) {
try {
return SecureStore.getItemAsync(key);
} catch (error) {
console.error('[RootLayoutNav] Error retrieving token:', error);
return null;
}
},
async saveToken(key: string, value: string) {
try {
return SecureStore.setItemAsync(key, value);
} catch (error) {
console.error('[RootLayoutNav] Error setting token:', error);
}
}
};

// Expo Router uses Error Boundaries to catch errors in the navigation tree.
useEffect(() => {
if (error) throw error;
}, [error]);
const InitalLayout = () => {
const { isLoaded, isSignedIn } = useAuth();
const router = useRouter();
const segments = useSegments();

useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
if (!isLoaded) return;

const inApp = segments[0] === "(app)";

if (isSignedIn && !inApp) {
router.push("/(app)/");
} else if (!isSignedIn) {
router.push("/(auth)/login");
}
}, [loaded]);

if (!loaded) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}

return <RootLayoutNav />;
}
console.log({ isSignedIn, inApp });
}, [isSignedIn]);

function RootLayoutNav() {
const { isLoggedIn, login } = useAuthStore();

useEffect(() => {
const checkLoginStatus = async () => {
try {
const accessToken = await getItemAsync('accessToken');
const refreshToken = await getItemAsync('refreshToken');
const savedUser = await getItemAsync('user');

console.log('[root] accessToken:', accessToken);
console.log('[root] refreshToken:', refreshToken);

const user: User = savedUser ? JSON.parse(savedUser) : null;

if (accessToken && refreshToken) {
// Consider adding token validation (e.g., expiration check)
login({ accessToken, refreshToken }, user);
}
} catch (error) {
console.error(
'[RootLayoutNav] Error retrieving tokens:',
error
);
}
};

checkLoginStatus();
}, [login]);
return <Slot />;
}

useEffect(() => {
if (isLoggedIn === null) {
router.push('/(auth)/welcome');
return;
}
const RootLayout = () => {
const [loaded, error] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
...FontAwesome.font
});

useEffect(() => { if (error) throw error }, [error]);
useEffect(() => { if (loaded) SplashScreen.hideAsync() }, [loaded]);

router.push(isLoggedIn ? '/(app)/' : '/(auth)/welcome');
}, [isLoggedIn]);
if (!loaded) return null;

return (
<Stack>
<Stack.Screen name="(app)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
</Stack>
<ClerkProvider publishableKey={CLERK_PUBLISHABLE_KEY!} tokenCache={tokenCache}>
<InitalLayout />
</ClerkProvider>
);
}

export default RootLayout;
44 changes: 44 additions & 0 deletions frontend/sac-mobile/components/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import { View, ActivityIndicator, Text } from 'react-native';

import { VariantProps, cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const spinnerVariants = {
size: {
default: 'w-6 h-6',
large: 'w-8 h-8',
small: 'w-4 h-4',
},
color: {
default: 'text-gray-500',
primary: 'text-blue-500',
secondary: 'text-red-500',
},
};

const spinnerStyles = cva(['items-center', 'justify-center'], {
variants: spinnerVariants,
defaultVariants: {
size: 'default',
color: 'default',
},
});

export interface SpinnerProps
extends VariantProps<typeof spinnerStyles> {
text?: string;
}

const Spinner = ({ size, color, text }: SpinnerProps) => {
return (
<View className={cn(spinnerStyles({ size, color }))}>
<ActivityIndicator />
{text && <Text>{text}</Text>}
</View>
);
};

Spinner.displayName = 'Spinner';

export { Spinner, spinnerVariants };
4 changes: 4 additions & 0 deletions frontend/sac-mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"preset": "jest-expo"
},
"dependencies": {
"@clerk/clerk-expo": "^0.20.10",
"@expo/vector-icons": "^14.0.0",
"@react-navigation/native": "^6.1.16",
"@tanstack/react-query": "^5.28.4",
Expand All @@ -23,6 +24,8 @@
"clsx": "^2.1.0",
"eslint": "^8.56.0",
"expo": "^50.0.13",
"expo-application": "^5.8.3",
"expo-auth-session": "^5.4.0",
"expo-dev-client": "~3.3.10",
"expo-font": "~11.10.2",
"expo-linking": "~6.2.2",
Expand All @@ -39,6 +42,7 @@
"react-native": "0.73.6",
"react-native-cookies": "^3.3.0",
"react-native-element-dropdown": "^2.10.2",
"react-native-loading-spinner-overlay": "^3.0.1",
"react-native-reanimated": "^3.8.1",
"react-native-safe-area-context": "4.9.0",
"react-native-screens": "~3.29.0",
Expand Down
Loading

0 comments on commit 3329cce

Please sign in to comment.