diff --git a/api/models/userModel.js b/api/models/userModel.js index 69136a0..f500aaf 100644 --- a/api/models/userModel.js +++ b/api/models/userModel.js @@ -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!' -// } -// } \ No newline at end of file +export default User diff --git a/app/(auth)/sign-up.tsx b/app/(auth)/sign-up.tsx index 6ef0437..80f9f46 100644 --- a/app/(auth)/sign-up.tsx +++ b/app/(auth)/sign-up.tsx @@ -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([]) + 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 ( - - Create an account - - First name - - Last name - - Email - - Password + 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 ( + + Enter the verification code we sent to {email} setCode(code)} /> - { - handleSignUp() - }} - > - Sign Up + + Verify - Already have an account? - { - router.navigate('/sign-in') - }} - > - Sign In - {' '} + {errors.map((error) => ( + + {error.longMessage} + + ))} + ) + } + + return ( + + <> + Create an account + + First name + + Last name + + Email + + Password + + { + onSignUpPress() + }} + > + Sign Up + + Already have an account? + { + router.navigate('/sign-in') + }} + > + Sign In + {' '} + + {errors.map((error) => ( + + {error.longMessage} + + ))} + ) } @@ -92,7 +176,7 @@ const styles = StyleSheet.create({ textCenter: { color: '#ffffff', alignSelf: 'center', - marginBottom: 6 + marginBottom: 12 }, textlabel: { color: '#fff', diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index a00d0d2..0aec797 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -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 ( ( - ) + ), + tabBarItemStyle: { display: user?.publicMetadata.role === 'admin' ? 'flex' : 'none' } }} /> = ({ post, fetchData }) {post.notes} {user?.publicMetadata.role !== 'admin' && ( - {post.status === StatusEnum.Created && Created} - {post.status === StatusEnum.Pending && Pending} - {post.status === StatusEnum.Denied && Denied} - {post.status === StatusEnum.Accepted && Accepted} + {post.status === StatusEnum.Created && ( + Created + )} + {post.status === StatusEnum.Pending && ( + Pending + )} + {post.status === StatusEnum.Denied && Denied} + {post.status === StatusEnum.Approved && ( + Approved + )} )} {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' } }) diff --git a/app/components/SignOutButton.tsx b/app/components/SignOutButton.tsx index 79cdb79..f11aa3d 100644 --- a/app/components/SignOutButton.tsx +++ b/app/components/SignOutButton.tsx @@ -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', diff --git a/app/models/postModel.ts b/app/models/postModel.ts index fb31572..e3052b7 100644 --- a/app/models/postModel.ts +++ b/app/models/postModel.ts @@ -10,5 +10,5 @@ export enum StatusEnum { Created = 'created', Pending = 'pending', Denied = 'denied', - Accepted = 'accepted' + Approved = 'approved' }