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,44 +1,23 @@
|
|||||||
|
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: {
|
||||||
type: String,
|
type: String,
|
||||||
required: [true, 'Please tell us your name!']
|
required: [true, 'Please tell us your name!']
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: String,
|
type: String,
|
||||||
required: [true, 'Please provide your email'],
|
required: [true, 'Please provide your email'],
|
||||||
unique: true,
|
unique: true,
|
||||||
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,58 +28,137 @@ 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
async function onVerifyPress() {
|
||||||
<View style={styles.container}>
|
if (!isLoaded) return
|
||||||
<Text style={styles.text}>Create an account</Text>
|
setErrors([])
|
||||||
<View style={{ width: '80%', marginTop: 20 }}>
|
|
||||||
<Text style={styles.textlabel}>First name</Text>
|
try {
|
||||||
<TextInput style={styles.input} value={fname} placeholder='Enter first name' onChangeText={setFname} />
|
// Use the code the user provided to attempt verification
|
||||||
<Text style={styles.textlabel}>Last name</Text>
|
const signUpAttempt = await signUp.attemptEmailAddressVerification({
|
||||||
<TextInput style={styles.input} value={lname} placeholder='Enter last name' onChangeText={setLname} />
|
code
|
||||||
<Text style={styles.textlabel}>Email</Text>
|
})
|
||||||
<TextInput style={styles.input} value={email} placeholder='Enter email' onChangeText={setUsername} />
|
|
||||||
<Text style={styles.textlabel}>Password</Text>
|
// 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
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
value={password}
|
value={code}
|
||||||
placeholder='Enter password'
|
placeholder='Enter your verification code'
|
||||||
secureTextEntry
|
onChangeText={(code) => setCode(code)}
|
||||||
onChangeText={setPassword}
|
|
||||||
/>
|
/>
|
||||||
<TouchableOpacity
|
<TouchableOpacity onPress={onVerifyPress} style={styles.button}>
|
||||||
style={styles.button}
|
<Text>Verify</Text>
|
||||||
onPress={() => {
|
|
||||||
handleSignUp()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ color: '#fff', fontWeight: 'bold' }}>Sign Up</Text>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={styles.textCenter}>Already have an account?</Text>
|
{errors.map((error) => (
|
||||||
<TouchableOpacity
|
<Text key={error.longMessage} style={{ color: 'red' }}>
|
||||||
style={styles.signIn}
|
{error.longMessage}
|
||||||
onPress={() => {
|
</Text>
|
||||||
router.navigate('/sign-in')
|
))}
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ color: '#fff' }}>Sign In</Text>
|
|
||||||
</TouchableOpacity>{' '}
|
|
||||||
</View>
|
</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>
|
</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