Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.env
16 changes: 16 additions & 0 deletions MealPrepAI_App/app/(auth)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useAuth } from '@clerk/expo'
import { Redirect, Stack } from 'expo-router'

export default function AuthRoutesLayout() {
const { isSignedIn, isLoaded } = useAuth()

if (!isLoaded) {
return null
}

if (isSignedIn) {
return <Redirect href={'/'} />
}

return <Stack />
}
254 changes: 254 additions & 0 deletions MealPrepAI_App/app/(auth)/sign-in.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import { ThemedText } from '@/components/themed-text'
import { ThemedView } from '@/components/themed-view'
import { useSignIn } from '@clerk/expo'
import { type Href, Link, useRouter } from 'expo-router'
import React from 'react'
import { Pressable, StyleSheet, TextInput, View } from 'react-native'

export default function Page() {
const { signIn, errors, fetchStatus } = useSignIn()
const router = useRouter()

const [emailAddress, setEmailAddress] = React.useState('')
const [password, setPassword] = React.useState('')
const [code, setCode] = React.useState('')

const handleSubmit = async () => {
const { error } = await signIn.password({
emailAddress,
password,
})
if (error) {
console.error(JSON.stringify(error, null, 2))
return
}

if (signIn.status === 'complete') {
await signIn.finalize({
navigate: ({ session, decorateUrl }) => {
if (session?.currentTask) {
// Handle pending session tasks
// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
console.log(session?.currentTask)
return
}

const url = decorateUrl('/')
if (url.startsWith('http')) {
window.location.href = url
} else {
router.push(url as Href)
}
},
})
} else if (signIn.status === 'needs_second_factor') {
// See https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication
} else if (signIn.status === 'needs_client_trust') {
// For other second factor strategies,
// see https://clerk.com/docs/guides/development/custom-flows/authentication/client-trust
const emailCodeFactor = signIn.supportedSecondFactors.find(
(factor) => factor.strategy === 'email_code',
)

if (emailCodeFactor) {
await signIn.mfa.sendEmailCode()
}
} else {
// Check why the sign-in is not complete
console.error('Sign-in attempt not complete:', signIn)
}
}

const handleVerify = async () => {
await signIn.mfa.verifyEmailCode({ code })

if (signIn.status === 'complete') {
await signIn.finalize({
navigate: ({ session, decorateUrl }) => {
if (session?.currentTask) {
// Handle pending session tasks
// See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
console.log(session?.currentTask)
return
}

const url = decorateUrl('/')
if (url.startsWith('http')) {
window.location.href = url
} else {
router.push(url as Href)
}
},
})
} else {
// Check why the sign-in is not complete
console.error('Sign-in attempt not complete:', signIn)
}
}

if (signIn.status === 'needs_client_trust') {
return (
<ThemedView style={styles.container}>
<ThemedText type="title" style={[styles.title, { fontSize: 24, fontWeight: 'bold' }]}>
Verify your account
</ThemedText>
<TextInput
style={styles.input}
value={code}
placeholder="Enter your verification code"
placeholderTextColor="#666666"
onChangeText={(code) => setCode(code)}
keyboardType="numeric"
/>
{errors.fields.code && (
<ThemedText style={styles.error}>{errors.fields.code.message}</ThemedText>
)}
<Pressable
style={({ pressed }) => [
styles.button,
fetchStatus === 'fetching' && styles.buttonDisabled,
pressed && styles.buttonPressed,
]}
onPress={handleVerify}
disabled={fetchStatus === 'fetching'}
>
<ThemedText style={styles.buttonText}>Verify</ThemedText>
</Pressable>
<Pressable
style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
onPress={() => signIn.mfa.sendEmailCode()}
>
<ThemedText style={styles.secondaryButtonText}>I need a new code</ThemedText>
</Pressable>
<Pressable
style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
onPress={() => signIn.reset()}
>
<ThemedText style={styles.secondaryButtonText}>Start over</ThemedText>
</Pressable>
</ThemedView>
)
}

return (
<ThemedView style={styles.container}>
<ThemedText type="title" style={styles.title}>
Sign in
</ThemedText>

<ThemedText style={styles.label}>Email address</ThemedText>
<TextInput
style={styles.input}
autoCapitalize="none"
value={emailAddress}
placeholder="Enter email"
placeholderTextColor="#666666"
onChangeText={(emailAddress) => setEmailAddress(emailAddress)}
keyboardType="email-address"
/>
{errors.fields.identifier && (
<ThemedText style={styles.error}>{errors.fields.identifier.message}</ThemedText>
)}
<ThemedText style={styles.label}>Password</ThemedText>
<TextInput
style={styles.input}
value={password}
placeholder="Enter password"
placeholderTextColor="#666666"
secureTextEntry={true}
onChangeText={(password) => setPassword(password)}
/>
{errors.fields.password && (
<ThemedText style={styles.error}>{errors.fields.password.message}</ThemedText>
)}
<Pressable
style={({ pressed }) => [
styles.button,
(!emailAddress || !password || fetchStatus === 'fetching') && styles.buttonDisabled,
pressed && styles.buttonPressed,
]}
onPress={handleSubmit}
disabled={!emailAddress || !password || fetchStatus === 'fetching'}
>
<ThemedText style={styles.buttonText}>Continue</ThemedText>
</Pressable>
{/* For your debugging purposes. You can just console.log errors, but we put them in the UI for convenience */}
{errors && <ThemedText style={styles.debug}>{JSON.stringify(errors, null, 2)}</ThemedText>}

<View style={styles.linkContainer}>
<ThemedText>Don't have an account? </ThemedText>
<Link href="/sign-up">
<ThemedText type="link">Sign up</ThemedText>
</Link>
</View>
</ThemedView>
)
}

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
gap: 12,
},
title: {
marginBottom: 8,
},
label: {
fontWeight: '600',
fontSize: 14,
},
input: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
padding: 12,
fontSize: 16,
backgroundColor: '#fff',
},
button: {
backgroundColor: '#0a7ea4',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
alignItems: 'center',
marginTop: 8,
},
buttonPressed: {
opacity: 0.7,
},
buttonDisabled: {
opacity: 0.5,
},
buttonText: {
color: '#fff',
fontWeight: '600',
},
secondaryButton: {
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
alignItems: 'center',
marginTop: 8,
},
secondaryButtonText: {
color: '#0a7ea4',
fontWeight: '600',
},
linkContainer: {
flexDirection: 'row',
gap: 4,
marginTop: 12,
alignItems: 'center',
},
error: {
color: '#d32f2f',
fontSize: 12,
marginTop: -8,
},
debug: {
fontSize: 10,
opacity: 0.5,
marginTop: 8,
},
})
Loading