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:
@@ -1,6 +1,5 @@
|
|||||||
|
import mongoose from 'mongoose'
|
||||||
import mongoose from 'mongoose';
|
import validator from 'validator'
|
||||||
import validator from 'validator';
|
|
||||||
|
|
||||||
const userSchema = new mongoose.Schema({
|
const userSchema = new mongoose.Schema({
|
||||||
name: {
|
name: {
|
||||||
@@ -14,31 +13,11 @@ const userSchema = new mongoose.Schema({
|
|||||||
lowercase: true,
|
lowercase: true,
|
||||||
validate: [validator.isEmail, 'Please provide a valid email']
|
validate: [validator.isEmail, 'Please provide a valid email']
|
||||||
},
|
},
|
||||||
role: {
|
clerkUserID: {
|
||||||
type: String,
|
type: String
|
||||||
enum: ['user', 'admin'],
|
|
||||||
default: 'user'
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
const User = mongoose.model('User', userSchema);
|
const User = mongoose.model('User', userSchema)
|
||||||
|
|
||||||
export default User;
|
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!'
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -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 { router } from 'expo-router'
|
||||||
import React from 'react'
|
import { useState } from 'react'
|
||||||
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
|
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
export default function SignUpScreen() {
|
export default function SignUpScreen() {
|
||||||
const { signUp, setActive, isLoaded } = useSignUp()
|
const { signUp, setActive, isLoaded } = useSignUp()
|
||||||
const [fname, setFname] = React.useState('')
|
const [pendingVerification, setPendingVerification] = useState(false)
|
||||||
const [lname, setLname] = React.useState('')
|
const [code, setCode] = useState('')
|
||||||
const [email, setUsername] = React.useState('')
|
const [errors, setErrors] = useState<ClerkAPIError[]>([])
|
||||||
const [password, setPassword] = React.useState('')
|
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
|
if (!isLoaded) return
|
||||||
|
setErrors([])
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Start Auth
|
// Start Auth
|
||||||
const signUpAttempt = await signUp.create({
|
await signUp.create({
|
||||||
firstName: fname,
|
firstName: fname,
|
||||||
lastName: lname,
|
lastName: lname,
|
||||||
emailAddress: email,
|
emailAddress: email,
|
||||||
@@ -23,32 +28,105 @@ export default function SignUpScreen() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Set confirmation
|
// Set confirmation
|
||||||
await signUp.prepareVerification({
|
await signUp.prepareEmailAddressVerification()
|
||||||
strategy: 'email_link',
|
setPendingVerification(true)
|
||||||
redirectUrl: '/'
|
|
||||||
})
|
|
||||||
|
|
||||||
if (signUpAttempt.status === 'complete') {
|
|
||||||
setActive({ session: signUpAttempt.createdSessionId })
|
|
||||||
router.replace('/')
|
|
||||||
} else {
|
|
||||||
console.log(signUpAttempt)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (isClerkAPIResponseError(e)) setErrors(e.errors)
|
||||||
console.log(JSON.stringify(e))
|
console.log(JSON.stringify(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.textCenter}>Enter the verification code we sent to {email}</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={code}
|
||||||
|
placeholder='Enter your verification code'
|
||||||
|
onChangeText={(code) => setCode(code)}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity onPress={onVerifyPress} style={styles.button}>
|
||||||
|
<Text>Verify</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>
|
<Text style={styles.text}>Create an account</Text>
|
||||||
<View style={{ width: '80%', marginTop: 20 }}>
|
<View style={{ width: '80%', marginTop: 20 }}>
|
||||||
<Text style={styles.textlabel}>First name</Text>
|
<Text style={styles.textlabel}>First name</Text>
|
||||||
<TextInput style={styles.input} value={fname} placeholder='Enter first name' onChangeText={setFname} />
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={fname}
|
||||||
|
placeholder='Enter first name'
|
||||||
|
onChangeText={setFname}
|
||||||
|
/>
|
||||||
<Text style={styles.textlabel}>Last name</Text>
|
<Text style={styles.textlabel}>Last name</Text>
|
||||||
<TextInput style={styles.input} value={lname} placeholder='Enter last name' onChangeText={setLname} />
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={lname}
|
||||||
|
placeholder='Enter last name'
|
||||||
|
onChangeText={setLname}
|
||||||
|
/>
|
||||||
<Text style={styles.textlabel}>Email</Text>
|
<Text style={styles.textlabel}>Email</Text>
|
||||||
<TextInput style={styles.input} value={email} placeholder='Enter email' onChangeText={setUsername} />
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={email}
|
||||||
|
placeholder='Enter email'
|
||||||
|
onChangeText={setUsername}
|
||||||
|
/>
|
||||||
<Text style={styles.textlabel}>Password</Text>
|
<Text style={styles.textlabel}>Password</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
@@ -60,7 +138,7 @@ export default function SignUpScreen() {
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
handleSignUp()
|
onSignUpPress()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ color: '#fff', fontWeight: 'bold' }}>Sign Up</Text>
|
<Text style={{ color: '#fff', fontWeight: 'bold' }}>Sign Up</Text>
|
||||||
@@ -75,6 +153,12 @@ export default function SignUpScreen() {
|
|||||||
<Text style={{ color: '#fff' }}>Sign In</Text>
|
<Text style={{ color: '#fff' }}>Sign In</Text>
|
||||||
</TouchableOpacity>{' '}
|
</TouchableOpacity>{' '}
|
||||||
</View>
|
</View>
|
||||||
|
{errors.map((error) => (
|
||||||
|
<Text key={error.longMessage} style={{ color: 'red' }}>
|
||||||
|
{error.longMessage}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -92,7 +176,7 @@ const styles = StyleSheet.create({
|
|||||||
textCenter: {
|
textCenter: {
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
marginBottom: 6
|
marginBottom: 12
|
||||||
},
|
},
|
||||||
textlabel: {
|
textlabel: {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { useUser } from '@clerk/clerk-react'
|
||||||
import Ionicons from '@expo/vector-icons/Ionicons'
|
import Ionicons from '@expo/vector-icons/Ionicons'
|
||||||
import { Tabs } from 'expo-router'
|
import { Tabs } from 'expo-router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
|
const { user } = useUser()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
@@ -34,7 +37,8 @@ export default function TabLayout() {
|
|||||||
headerShown: false,
|
headerShown: false,
|
||||||
tabBarIcon: ({ color, focused }) => (
|
tabBarIcon: ({ color, focused }) => (
|
||||||
<Ionicons name={focused ? 'rocket-sharp' : 'rocket-outline'} size={24} color={color} />
|
<Ionicons name={focused ? 'rocket-sharp' : 'rocket-outline'} size={24} color={color} />
|
||||||
)
|
),
|
||||||
|
tabBarItemStyle: { display: user?.publicMetadata.role === 'admin' ? 'flex' : 'none' }
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
profileInfoCard: {
|
profileInfoCard: {
|
||||||
backgroundColor: '#373d44ff',
|
backgroundColor: '#373d44ff',
|
||||||
flex: 0.25,
|
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
borderColor: '#626e7aff',
|
borderColor: '#626e7aff',
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
@@ -79,25 +78,24 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 12
|
borderRadius: 12
|
||||||
},
|
},
|
||||||
profilePic: {
|
profilePic: {
|
||||||
flex: 0.4,
|
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
height: 75,
|
height: 75,
|
||||||
width: 75
|
width: 75,
|
||||||
|
borderRadius: 37.5
|
||||||
},
|
},
|
||||||
nameContainer: { flex: 0.3, display: 'flex', alignItems: 'center' },
|
nameContainer: { display: 'flex', alignItems: 'center' },
|
||||||
text: {
|
text: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
marginTop: 6
|
marginTop: 6
|
||||||
},
|
},
|
||||||
buttonLayout: {
|
buttonLayout: {
|
||||||
flex: 0.2,
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: 12
|
marginVertical: 12
|
||||||
},
|
},
|
||||||
userPostsCard: {
|
userPostsCard: {
|
||||||
flex: 1
|
flex: 1
|
||||||
|
|||||||
@@ -56,10 +56,16 @@ export const PostComponent: React.FC<PostComponentProps> = ({ post, fetchData })
|
|||||||
<Text style={{ color: '#fff' }}>{post.notes}</Text>
|
<Text style={{ color: '#fff' }}>{post.notes}</Text>
|
||||||
{user?.publicMetadata.role !== 'admin' && (
|
{user?.publicMetadata.role !== 'admin' && (
|
||||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 10 }}>
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 10 }}>
|
||||||
{post.status === StatusEnum.Created && <Text style={styles.created}>Created</Text>}
|
{post.status === StatusEnum.Created && (
|
||||||
{post.status === StatusEnum.Pending && <Text style={styles.pending}>Pending</Text>}
|
<Text style={[styles.created, styles.statusTag]}>Created</Text>
|
||||||
{post.status === StatusEnum.Denied && <Text style={styles.denied}>Denied</Text>}
|
)}
|
||||||
{post.status === StatusEnum.Accepted && <Text style={styles.accepted}>Accepted</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>
|
</View>
|
||||||
)}
|
)}
|
||||||
{user?.publicMetadata.role === 'admin' && (
|
{user?.publicMetadata.role === 'admin' && (
|
||||||
@@ -111,8 +117,9 @@ const styles = StyleSheet.create({
|
|||||||
padding: 10,
|
padding: 10,
|
||||||
width: '100%'
|
width: '100%'
|
||||||
},
|
},
|
||||||
created: { color: 'white' },
|
statusTag: { paddingVertical: 3, paddingHorizontal: 10, borderRadius: 6 },
|
||||||
pending: { color: 'yellow' },
|
created: { backgroundColor: '#0d6efd', color: '#ffffff' },
|
||||||
denied: { color: 'red' },
|
pending: { backgroundColor: '#ffc107', color: '#000000' },
|
||||||
accepted: { color: 'green' }
|
denied: { backgroundColor: '#dc3545', color: '#ffffff' },
|
||||||
|
approved: { backgroundColor: '#198754', color: '#ffffff' }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const SignOutButton = () => {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
width: '40%',
|
width: '40%',
|
||||||
height: '100%',
|
height: 35,
|
||||||
backgroundColor: 'rgba(192, 196, 199, 1)',
|
backgroundColor: 'rgba(192, 196, 199, 1)',
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ export enum StatusEnum {
|
|||||||
Created = 'created',
|
Created = 'created',
|
||||||
Pending = 'pending',
|
Pending = 'pending',
|
||||||
Denied = 'denied',
|
Denied = 'denied',
|
||||||
Accepted = 'accepted'
|
Approved = 'approved'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user