fixing sing up flow now using an email code to verify.. on sign up we create user in backend that has reference to clerkID for filtering posts later

This commit is contained in:
Will Baumbach
2025-08-06 23:18:26 -05:00
parent 1ce2d1583f
commit 482bc915a5
7 changed files with 180 additions and 108 deletions

View File

@@ -1,44 +1,23 @@
import mongoose from 'mongoose';
import validator from 'validator';
import mongoose from 'mongoose'
import validator from 'validator'
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please tell us your name!']
},
email: {
type: String,
required: [true, 'Please provide your email'],
unique: true,
lowercase: true,
validate: [validator.isEmail, 'Please provide a valid email']
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
}
});
name: {
type: String,
required: [true, 'Please tell us your name!']
},
email: {
type: String,
required: [true, 'Please provide your email'],
unique: true,
lowercase: true,
validate: [validator.isEmail, 'Please provide a valid email']
},
clerkUserID: {
type: String
}
})
const User = mongoose.model('User', userSchema);
const User = mongoose.model('User', userSchema)
export default User;
// password: {
// type: String,
// required: [true, 'Please provide a password'],
// minlength: 8,
// select: false
// },
// passwordConfirm: {
// type: String,
// required: [true, 'Please confirm your password'],
// validate: {
// // This only works on CREATE and SAVE!!!
// validator: function(el) {
// return el === this.password;
// },
// message: 'Passwords are not the same!'
// }
// }
export default User

View File

@@ -1,21 +1,26 @@
import { useSignUp } from '@clerk/clerk-expo'
import { isClerkAPIResponseError, useSignUp } from '@clerk/clerk-expo'
import { ClerkAPIError } from '@clerk/types'
import { router } from 'expo-router'
import React from 'react'
import { useState } from 'react'
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
export default function SignUpScreen() {
const { signUp, setActive, isLoaded } = useSignUp()
const [fname, setFname] = React.useState('')
const [lname, setLname] = React.useState('')
const [email, setUsername] = React.useState('')
const [password, setPassword] = React.useState('')
const [pendingVerification, setPendingVerification] = useState(false)
const [code, setCode] = useState('')
const [errors, setErrors] = useState<ClerkAPIError[]>([])
const [fname, setFname] = useState('')
const [lname, setLname] = useState('')
const [email, setUsername] = useState('')
const [password, setPassword] = useState('')
async function handleSignUp() {
async function onSignUpPress() {
if (!isLoaded) return
setErrors([])
try {
// Start Auth
const signUpAttempt = await signUp.create({
await signUp.create({
firstName: fname,
lastName: lname,
emailAddress: email,
@@ -23,58 +28,137 @@ export default function SignUpScreen() {
})
// Set confirmation
await signUp.prepareVerification({
strategy: 'email_link',
redirectUrl: '/'
})
if (signUpAttempt.status === 'complete') {
setActive({ session: signUpAttempt.createdSessionId })
router.replace('/')
} else {
console.log(signUpAttempt)
}
await signUp.prepareEmailAddressVerification()
setPendingVerification(true)
} catch (e) {
if (isClerkAPIResponseError(e)) setErrors(e.errors)
console.log(JSON.stringify(e))
}
}
return (
<View style={styles.container}>
<Text style={styles.text}>Create an account</Text>
<View style={{ width: '80%', marginTop: 20 }}>
<Text style={styles.textlabel}>First name</Text>
<TextInput style={styles.input} value={fname} placeholder='Enter first name' onChangeText={setFname} />
<Text style={styles.textlabel}>Last name</Text>
<TextInput style={styles.input} value={lname} placeholder='Enter last name' onChangeText={setLname} />
<Text style={styles.textlabel}>Email</Text>
<TextInput style={styles.input} value={email} placeholder='Enter email' onChangeText={setUsername} />
<Text style={styles.textlabel}>Password</Text>
async function onVerifyPress() {
if (!isLoaded) return
setErrors([])
try {
// Use the code the user provided to attempt verification
const signUpAttempt = await signUp.attemptEmailAddressVerification({
code
})
// If verification was completed, set the session to active
// and redirect the user
if (signUpAttempt.status === 'complete') {
await createUser(signUpAttempt.createdUserId)
await setActive({ session: signUpAttempt.createdSessionId })
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signUpAttempt, null, 2))
}
} catch (e: any) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(e, null, 2))
setErrors(e.errors)
}
}
async function createUser(clerkUserID: string | null) {
fetch('http://localhost:3000/api/v1/users', {
method: 'POST',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: `${fname} ${lname}`,
email,
clerkUserID
})
})
}
if (pendingVerification) {
return (
<View style={styles.container}>
<Text style={styles.textCenter}>Enter the verification code we sent to {email}</Text>
<TextInput
style={styles.input}
value={password}
placeholder='Enter password'
secureTextEntry
onChangeText={setPassword}
value={code}
placeholder='Enter your verification code'
onChangeText={(code) => setCode(code)}
/>
<TouchableOpacity
style={styles.button}
onPress={() => {
handleSignUp()
}}
>
<Text style={{ color: '#fff', fontWeight: 'bold' }}>Sign Up</Text>
<TouchableOpacity onPress={onVerifyPress} style={styles.button}>
<Text>Verify</Text>
</TouchableOpacity>
<Text style={styles.textCenter}>Already have an account?</Text>
<TouchableOpacity
style={styles.signIn}
onPress={() => {
router.navigate('/sign-in')
}}
>
<Text style={{ color: '#fff' }}>Sign In</Text>
</TouchableOpacity>{' '}
{errors.map((error) => (
<Text key={error.longMessage} style={{ color: 'red' }}>
{error.longMessage}
</Text>
))}
</View>
)
}
return (
<View style={styles.container}>
<>
<Text style={styles.text}>Create an account</Text>
<View style={{ width: '80%', marginTop: 20 }}>
<Text style={styles.textlabel}>First name</Text>
<TextInput
style={styles.input}
value={fname}
placeholder='Enter first name'
onChangeText={setFname}
/>
<Text style={styles.textlabel}>Last name</Text>
<TextInput
style={styles.input}
value={lname}
placeholder='Enter last name'
onChangeText={setLname}
/>
<Text style={styles.textlabel}>Email</Text>
<TextInput
style={styles.input}
value={email}
placeholder='Enter email'
onChangeText={setUsername}
/>
<Text style={styles.textlabel}>Password</Text>
<TextInput
style={styles.input}
value={password}
placeholder='Enter password'
secureTextEntry
onChangeText={setPassword}
/>
<TouchableOpacity
style={styles.button}
onPress={() => {
onSignUpPress()
}}
>
<Text style={{ color: '#fff', fontWeight: 'bold' }}>Sign Up</Text>
</TouchableOpacity>
<Text style={styles.textCenter}>Already have an account?</Text>
<TouchableOpacity
style={styles.signIn}
onPress={() => {
router.navigate('/sign-in')
}}
>
<Text style={{ color: '#fff' }}>Sign In</Text>
</TouchableOpacity>{' '}
</View>
{errors.map((error) => (
<Text key={error.longMessage} style={{ color: 'red' }}>
{error.longMessage}
</Text>
))}
</>
</View>
)
}
@@ -92,7 +176,7 @@ const styles = StyleSheet.create({
textCenter: {
color: '#ffffff',
alignSelf: 'center',
marginBottom: 6
marginBottom: 12
},
textlabel: {
color: '#fff',

View File

@@ -1,8 +1,11 @@
import { useUser } from '@clerk/clerk-react'
import Ionicons from '@expo/vector-icons/Ionicons'
import { Tabs } from 'expo-router'
import React from 'react'
export default function TabLayout() {
const { user } = useUser()
return (
<Tabs
screenOptions={{
@@ -34,7 +37,8 @@ export default function TabLayout() {
headerShown: false,
tabBarIcon: ({ color, focused }) => (
<Ionicons name={focused ? 'rocket-sharp' : 'rocket-outline'} size={24} color={color} />
)
),
tabBarItemStyle: { display: user?.publicMetadata.role === 'admin' ? 'flex' : 'none' }
}}
/>
<Tabs.Screen

View File

@@ -71,7 +71,6 @@ const styles = StyleSheet.create({
},
profileInfoCard: {
backgroundColor: '#373d44ff',
flex: 0.25,
marginTop: 12,
borderColor: '#626e7aff',
borderStyle: 'solid',
@@ -79,25 +78,24 @@ const styles = StyleSheet.create({
borderRadius: 12
},
profilePic: {
flex: 0.4,
marginTop: 12,
display: 'flex',
alignItems: 'center'
},
image: {
height: 75,
width: 75
width: 75,
borderRadius: 37.5
},
nameContainer: { flex: 0.3, display: 'flex', alignItems: 'center' },
nameContainer: { display: 'flex', alignItems: 'center' },
text: {
color: 'white',
fontSize: 16,
marginTop: 6
},
buttonLayout: {
flex: 0.2,
alignItems: 'center',
marginTop: 12
marginVertical: 12
},
userPostsCard: {
flex: 1

View File

@@ -56,10 +56,16 @@ export const PostComponent: React.FC<PostComponentProps> = ({ post, fetchData })
<Text style={{ color: '#fff' }}>{post.notes}</Text>
{user?.publicMetadata.role !== 'admin' && (
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 10 }}>
{post.status === StatusEnum.Created && <Text style={styles.created}>Created</Text>}
{post.status === StatusEnum.Pending && <Text style={styles.pending}>Pending</Text>}
{post.status === StatusEnum.Denied && <Text style={styles.denied}>Denied</Text>}
{post.status === StatusEnum.Accepted && <Text style={styles.accepted}>Accepted</Text>}
{post.status === StatusEnum.Created && (
<Text style={[styles.created, styles.statusTag]}>Created</Text>
)}
{post.status === StatusEnum.Pending && (
<Text style={[styles.pending, styles.statusTag]}>Pending</Text>
)}
{post.status === StatusEnum.Denied && <Text style={[styles.denied, styles.statusTag]}>Denied</Text>}
{post.status === StatusEnum.Approved && (
<Text style={[styles.approved, styles.statusTag]}>Approved</Text>
)}
</View>
)}
{user?.publicMetadata.role === 'admin' && (
@@ -111,8 +117,9 @@ const styles = StyleSheet.create({
padding: 10,
width: '100%'
},
created: { color: 'white' },
pending: { color: 'yellow' },
denied: { color: 'red' },
accepted: { color: 'green' }
statusTag: { paddingVertical: 3, paddingHorizontal: 10, borderRadius: 6 },
created: { backgroundColor: '#0d6efd', color: '#ffffff' },
pending: { backgroundColor: '#ffc107', color: '#000000' },
denied: { backgroundColor: '#dc3545', color: '#ffffff' },
approved: { backgroundColor: '#198754', color: '#ffffff' }
})

View File

@@ -26,7 +26,7 @@ export const SignOutButton = () => {
const styles = StyleSheet.create({
button: {
width: '40%',
height: '100%',
height: 35,
backgroundColor: 'rgba(192, 196, 199, 1)',
borderRadius: 5,
alignItems: 'center',

View File

@@ -10,5 +10,5 @@ export enum StatusEnum {
Created = 'created',
Pending = 'pending',
Denied = 'denied',
Accepted = 'accepted'
Approved = 'approved'
}