Merge pull request #2 from WillBaumbach/feat/post-component
A lot of changes related to posts, profile posts, sign up, sign in, permissions, etc.
This commit is contained in:
@@ -36,13 +36,17 @@ export const getPost = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getAllPostsByUser = async (req, res, next) => {
|
export const getAllPostsByUser = async (req, res, next) => {
|
||||||
const post = await Post.find({ email: req.params.user }).exec()
|
console.log(req.params)
|
||||||
if (!post) {
|
const posts = await Post.find({ clerkUserID: req.params.id }).exec()
|
||||||
|
posts.map((el) => {
|
||||||
|
el.photo = process.env.CLOUDFRONT_URL + el.photo
|
||||||
|
})
|
||||||
|
if (!posts) {
|
||||||
return next('No document found with that id', 404)
|
return next('No document found with that id', 404)
|
||||||
}
|
}
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
data: post
|
data: posts
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import mongoose from 'mongoose'
|
import mongoose from 'mongoose'
|
||||||
|
|
||||||
const postSchema = new mongoose.Schema({
|
const postSchema = new mongoose.Schema({
|
||||||
userID: {
|
clerkUserID: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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!'
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ router.route('/').get(getAllPosts).post(createPost)
|
|||||||
|
|
||||||
router.route('/:id').get(getPost).patch(updatePost).delete(deletePost)
|
router.route('/:id').get(getPost).patch(updatePost).delete(deletePost)
|
||||||
|
|
||||||
router.route('/user/:user').get(getAllPostsByUser)
|
router.route('/user/:id').get(getAllPostsByUser)
|
||||||
|
|
||||||
router.route('/status/:status').get(getAllPostsByStatus)
|
router.route('/status/:status').get(getAllPostsByStatus)
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,63 @@
|
|||||||
import { SignIn } from '@clerk/clerk-react'
|
import { useSignIn } from '@clerk/clerk-expo'
|
||||||
import { dark } from '@clerk/themes'
|
import { router } from 'expo-router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
|
export default function LoginScreen() {
|
||||||
|
const { signIn, setActive, isLoaded } = useSignIn()
|
||||||
|
const [username, setUsername] = React.useState('')
|
||||||
|
const [password, setPassword] = React.useState('')
|
||||||
|
|
||||||
|
async function handleSignIn() {
|
||||||
|
if (!isLoaded) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const signInAttempt = await signIn.create({
|
||||||
|
identifier: username,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
|
||||||
|
if (signInAttempt.status === 'complete') {
|
||||||
|
await setActive({ session: signInAttempt.createdSessionId })
|
||||||
|
router.replace('/')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(JSON.stringify(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function SignInScreen() {
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<SignIn
|
<Text style={styles.text}>Login</Text>
|
||||||
appearance={{
|
<View style={{ width: '80%', marginTop: 20 }}>
|
||||||
theme: dark,
|
<Text style={styles.textlabel}>Username</Text>
|
||||||
variables: {
|
<TextInput style={styles.input} value={username} placeholder='Enter email' onChangeText={setUsername} />
|
||||||
colorPrimary: '#747b83ff',
|
<Text style={styles.textlabel}>Password</Text>
|
||||||
colorBackground: '#25292e',
|
<TextInput
|
||||||
colorInput: '#383e46ff'
|
style={styles.input}
|
||||||
}
|
value={password}
|
||||||
}}
|
placeholder='Enter password'
|
||||||
signUpUrl='/sign-up'
|
secureTextEntry
|
||||||
/>
|
onChangeText={setPassword}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.button}
|
||||||
|
onPress={() => {
|
||||||
|
handleSignIn()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: '#fff', fontWeight: 'bold' }}>Login</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.textCenter}>Don't have an account?</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.signup}
|
||||||
|
onPress={() => {
|
||||||
|
router.navigate('/sign-up')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: '#fff' }}>Sign Up</Text>
|
||||||
|
</TouchableOpacity>{' '}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -25,7 +66,35 @@ const styles = StyleSheet.create({
|
|||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#25292e',
|
backgroundColor: '#25292e',
|
||||||
padding: 5,
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: '#fff'
|
||||||
|
},
|
||||||
|
textCenter: {
|
||||||
|
color: '#ffffff',
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginBottom: 6
|
||||||
|
},
|
||||||
|
textlabel: {
|
||||||
|
color: '#fff',
|
||||||
|
marginBottom: 3
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: 10,
|
||||||
|
marginBottom: 15
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
backgroundColor: '#1e90ff',
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 5,
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 15
|
||||||
|
},
|
||||||
|
signup: {
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,22 +1,164 @@
|
|||||||
import { SignUp } from '@clerk/clerk-expo/web'
|
import { isClerkAPIResponseError, useSignUp } from '@clerk/clerk-expo'
|
||||||
import { dark } from '@clerk/themes'
|
import { ClerkAPIError } from '@clerk/types'
|
||||||
import * as React from 'react'
|
import { router } from 'expo-router'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { useState } from 'react'
|
||||||
|
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
export default function SignUpScreen() {
|
export default function SignUpScreen() {
|
||||||
|
const { signUp, setActive, isLoaded } = useSignUp()
|
||||||
|
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 onSignUpPress() {
|
||||||
|
if (!isLoaded) return
|
||||||
|
setErrors([])
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Start Auth
|
||||||
|
await signUp.create({
|
||||||
|
firstName: fname,
|
||||||
|
lastName: lname,
|
||||||
|
emailAddress: email,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set confirmation
|
||||||
|
await signUp.prepareEmailAddressVerification()
|
||||||
|
setPendingVerification(true)
|
||||||
|
} catch (e) {
|
||||||
|
if (isClerkAPIResponseError(e)) setErrors(e.errors)
|
||||||
|
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 (
|
||||||
|
<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 (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<SignUp
|
<>
|
||||||
appearance={{
|
<Text style={styles.text}>Create an account</Text>
|
||||||
theme: dark,
|
<View style={{ width: '80%', marginTop: 20 }}>
|
||||||
variables: {
|
<Text style={styles.textlabel}>First name</Text>
|
||||||
colorPrimary: '#747b83ff',
|
<TextInput
|
||||||
colorBackground: '#25292e',
|
style={styles.input}
|
||||||
colorInput: '#383e46ff'
|
value={fname}
|
||||||
}
|
placeholder='Enter first name'
|
||||||
}}
|
onChangeText={setFname}
|
||||||
signInUrl='/sign-in'
|
/>
|
||||||
/>
|
<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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -25,7 +167,35 @@ const styles = StyleSheet.create({
|
|||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#25292e',
|
backgroundColor: '#25292e',
|
||||||
padding: 5,
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: '#fff'
|
||||||
|
},
|
||||||
|
textCenter: {
|
||||||
|
color: '#ffffff',
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginBottom: 12
|
||||||
|
},
|
||||||
|
textlabel: {
|
||||||
|
color: '#fff',
|
||||||
|
marginBottom: 3
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: 10,
|
||||||
|
marginBottom: 15
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
backgroundColor: '#1e90ff',
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 5,
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 15
|
||||||
|
},
|
||||||
|
signIn: {
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
|
import { useUser } from '@clerk/clerk-react'
|
||||||
import { CameraView, useCameraPermissions } from 'expo-camera'
|
import { CameraView, useCameraPermissions } from 'expo-camera'
|
||||||
import React, { useRef, useState } from 'react'
|
import React, { useRef, useState } from 'react'
|
||||||
import { Button, Image, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
|
import { Button, Image, Modal, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const { user } = useUser()
|
||||||
const [permission, requestPermission] = useCameraPermissions()
|
const [permission, requestPermission] = useCameraPermissions()
|
||||||
const [photo, setPhoto] = useState('')
|
const [photo, setPhoto] = useState('')
|
||||||
const [address, setAddress] = useState('')
|
const [address, setAddress] = useState('')
|
||||||
const [notes, setNotes] = useState('')
|
const [notes, setNotes] = useState('')
|
||||||
|
const [showConfirmation, setShowConfirmation] = useState(false)
|
||||||
const cameraRef = useRef<CameraView>(null)
|
const cameraRef = useRef<CameraView>(null)
|
||||||
|
|
||||||
async function resetState() {
|
async function resetState() {
|
||||||
@@ -34,7 +37,7 @@ export default function App() {
|
|||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
userID: '6883ddb2640ebaa1a12e3791',
|
clerkUserID: user?.id,
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
photo,
|
photo,
|
||||||
address,
|
address,
|
||||||
@@ -43,6 +46,7 @@ export default function App() {
|
|||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
console.log(res.status)
|
console.log(res.status)
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
setShowConfirmation(true)
|
||||||
resetState()
|
resetState()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -109,6 +113,26 @@ export default function App() {
|
|||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<Modal
|
||||||
|
animationType='fade'
|
||||||
|
transparent={true}
|
||||||
|
visible={showConfirmation}
|
||||||
|
onRequestClose={() => {
|
||||||
|
setShowConfirmation(!showConfirmation)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={styles.modalView}>
|
||||||
|
<Text style={styles.modalText}>
|
||||||
|
Your post has been submitted and will be reviewed within 3 business days. Thanks!
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.modalButton, styles.buttonClose]}
|
||||||
|
onPress={() => setShowConfirmation(!showConfirmation)}
|
||||||
|
>
|
||||||
|
<Text style={styles.textStyle}>Close</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -121,6 +145,48 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: 25,
|
paddingHorizontal: 25,
|
||||||
paddingVertical: 200
|
paddingVertical: 200
|
||||||
},
|
},
|
||||||
|
centeredView: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
modalView: {
|
||||||
|
flex: 1,
|
||||||
|
top: '80%',
|
||||||
|
height: 100,
|
||||||
|
backgroundColor: '#363c43ff',
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 35,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: {
|
||||||
|
width: 0,
|
||||||
|
height: 2
|
||||||
|
},
|
||||||
|
shadowOpacity: 0.25,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 5
|
||||||
|
},
|
||||||
|
modalText: {
|
||||||
|
marginBottom: 15,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'white'
|
||||||
|
},
|
||||||
|
modalButton: {
|
||||||
|
borderRadius: 20,
|
||||||
|
padding: 10,
|
||||||
|
elevation: 2
|
||||||
|
},
|
||||||
|
buttonClose: {
|
||||||
|
backgroundColor: '#96a5b1ff',
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 5,
|
||||||
|
borderRadius: 12
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center'
|
||||||
|
},
|
||||||
cameraContainer: {
|
cameraContainer: {
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useFocusEffect } from 'expo-router'
|
import { useFocusEffect } from 'expo-router'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { PostComponent } from '../components/PostComponent'
|
||||||
import { Post } from '../models/postModel'
|
import { Post } from '../models/postModel'
|
||||||
|
|
||||||
export default function PostsScreen() {
|
export default function PostsScreen() {
|
||||||
@@ -27,95 +28,16 @@ export default function PostsScreen() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function approvePost(postID: string) {
|
|
||||||
// add code to update post to approved status
|
|
||||||
console.log('Approving post ' + postID)
|
|
||||||
await fetch(`http://localhost:3000/api/v1/posts/${postID}`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json, text/plain, */*',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
status: 'approved'
|
|
||||||
})
|
|
||||||
}).then(() => {
|
|
||||||
fetchData()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function denyPost(postID: string) {
|
|
||||||
// add code to update post to remove status
|
|
||||||
console.log('Denying post ' + postID)
|
|
||||||
await fetch(`http://localhost:3000/api/v1/posts/${postID}`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json, text/plain, */*',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
status: 'denied'
|
|
||||||
})
|
|
||||||
}).then(() => {
|
|
||||||
fetchData()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.wrapper}>
|
<View style={styles.wrapper}>
|
||||||
<View style={styles.container}>
|
<Text style={styles.title}>Posts</Text>
|
||||||
<Text style={styles.text}>Posts</Text>
|
{posts?.length ? (
|
||||||
{posts?.length ? (
|
posts.map((el) => <PostComponent key={el._id} post={el} fetchData={fetchData} />)
|
||||||
posts.map((el) => (
|
) : (
|
||||||
<View key={el._id} style={styles.posts}>
|
<View style={styles.caughtUpContainer}>
|
||||||
<Text style={styles.text}>{el._id}</Text>
|
<Text style={styles.caughtUpText}>All caught up!</Text>
|
||||||
<View style={{ alignItems: 'center', marginVertical: 10 }}>
|
</View>
|
||||||
<Image
|
)}
|
||||||
source={{ uri: el.photo }}
|
|
||||||
style={{ width: 200, height: 200, borderRadius: 8 }}
|
|
||||||
resizeMode='cover'
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text style={{ color: '#fff' }}>{el.notes}</Text>
|
|
||||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 10 }}>
|
|
||||||
<TouchableOpacity style={{ flex: 1, marginRight: 5 }} onPress={() => denyPost(el._id)}>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
backgroundColor: '#bf3636ff',
|
|
||||||
color: '#fff',
|
|
||||||
textAlign: 'center',
|
|
||||||
padding: 8,
|
|
||||||
borderRadius: 4
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Deny
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{ flex: 1, marginLeft: 5 }}
|
|
||||||
onPress={() => approvePost(el._id)}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
backgroundColor: '#17be3bff',
|
|
||||||
color: '#fff',
|
|
||||||
textAlign: 'center',
|
|
||||||
padding: 8,
|
|
||||||
borderRadius: 4
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Approve
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<View style={styles.caughtUpContainer}>
|
|
||||||
<Text style={styles.caughtUpText}>All caught up!</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -123,14 +45,12 @@ export default function PostsScreen() {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
wrapper: {
|
wrapper: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#25292e'
|
backgroundColor: '#25292e',
|
||||||
},
|
padding: 16,
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
overflow: 'scroll'
|
overflow: 'scroll'
|
||||||
},
|
},
|
||||||
text: {
|
title: {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,35 @@
|
|||||||
import { useUser } from '@clerk/clerk-react'
|
import { useUser } from '@clerk/clerk-react'
|
||||||
import React from 'react'
|
import { useFocusEffect } from 'expo-router'
|
||||||
|
import React, { useState } from 'react'
|
||||||
import { Image, StyleSheet, Text, View } from 'react-native'
|
import { Image, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { PostComponent } from '../components/PostComponent'
|
||||||
import { SignOutButton } from '../components/SignOutButton'
|
import { SignOutButton } from '../components/SignOutButton'
|
||||||
|
import { Post } from '../models/postModel'
|
||||||
|
|
||||||
export default function PostsScreen() {
|
export default function PostsScreen() {
|
||||||
const { user } = useUser()
|
const { user } = useUser()
|
||||||
|
const [posts, setPosts] = useState<Post[]>()
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
React.useCallback(() => {
|
||||||
|
// Do something when the screen is focused
|
||||||
|
// TODO: add endpoint to get only non approved or denied status posts
|
||||||
|
fetchData()
|
||||||
|
return () => {
|
||||||
|
// Do something when the screen is unfocused
|
||||||
|
// Useful for cleanup functions
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
)
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
fetch(`http://localhost:3000/api/v1/posts/user/${user?.id}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((json) => {
|
||||||
|
console.log(json)
|
||||||
|
setPosts(json.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -23,7 +48,15 @@ export default function PostsScreen() {
|
|||||||
<SignOutButton></SignOutButton>
|
<SignOutButton></SignOutButton>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.userPostsCard}></View>
|
<View style={styles.userPostsCard}>
|
||||||
|
{posts?.length ? (
|
||||||
|
posts.map((el) => <PostComponent key={el._id} post={el} fetchData={fetchData} />)
|
||||||
|
) : (
|
||||||
|
<View style={styles.noPostsContainer}>
|
||||||
|
<Text style={styles.noPosts}>Your posts will show up here!</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -33,11 +66,11 @@ const styles = StyleSheet.create({
|
|||||||
wrapper: {
|
wrapper: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#25292e',
|
backgroundColor: '#25292e',
|
||||||
padding: 16
|
padding: 16,
|
||||||
|
overflow: 'scroll'
|
||||||
},
|
},
|
||||||
profileInfoCard: {
|
profileInfoCard: {
|
||||||
backgroundColor: '#373d44ff',
|
backgroundColor: '#373d44ff',
|
||||||
flex: 0.25,
|
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
borderColor: '#626e7aff',
|
borderColor: '#626e7aff',
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
@@ -45,33 +78,34 @@ 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: {
|
||||||
backgroundColor: '#373d44ff',
|
flex: 1
|
||||||
flex: 0.75,
|
},
|
||||||
marginTop: 12,
|
noPostsContainer: {
|
||||||
borderColor: '#626e7aff',
|
flex: 1,
|
||||||
borderStyle: 'solid',
|
alignItems: 'center',
|
||||||
borderWidth: 1,
|
justifyContent: 'center'
|
||||||
borderRadius: 12
|
},
|
||||||
|
noPosts: {
|
||||||
|
color: '#787b80ff'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export const Post = () => {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
125
app/components/PostComponent.tsx
Normal file
125
app/components/PostComponent.tsx
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import { useUser } from '@clerk/clerk-react'
|
||||||
|
import React from 'react'
|
||||||
|
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
||||||
|
import { Post, StatusEnum } from '../models/postModel'
|
||||||
|
|
||||||
|
type PostComponentProps = {
|
||||||
|
post: Post
|
||||||
|
fetchData: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PostComponent: React.FC<PostComponentProps> = ({ post, fetchData }) => {
|
||||||
|
const { user } = useUser()
|
||||||
|
|
||||||
|
async function approvePost(postID: string) {
|
||||||
|
console.log('Approving post ' + postID)
|
||||||
|
await fetch(`http://localhost:3000/api/v1/posts/${postID}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json, text/plain, */*',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
status: 'approved'
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function denyPost(postID: string) {
|
||||||
|
console.log('Denying post ' + postID)
|
||||||
|
await fetch(`http://localhost:3000/api/v1/posts/${postID}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json, text/plain, */*',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
status: 'denied'
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View key={post._id} style={styles.posts}>
|
||||||
|
<Text style={styles.text}>{post._id}</Text>
|
||||||
|
<View style={{ alignItems: 'center', marginVertical: 10 }}>
|
||||||
|
<Image
|
||||||
|
source={{ uri: post.photo }}
|
||||||
|
style={{ width: 200, height: 200, borderRadius: 8 }}
|
||||||
|
resizeMode='cover'
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<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, 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' && (
|
||||||
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 10 }}>
|
||||||
|
<TouchableOpacity style={{ flex: 1, marginRight: 5 }} onPress={() => denyPost(post._id)}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#bf3636ff',
|
||||||
|
color: '#fff',
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: 8,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Deny
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={{ flex: 1, marginLeft: 5 }} onPress={() => approvePost(post._id)}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#17be3bff',
|
||||||
|
color: '#fff',
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: 8,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Approve
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
color: '#fff',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
posts: {
|
||||||
|
marginTop: 10,
|
||||||
|
backgroundColor: '#373d44ff',
|
||||||
|
borderColor: '#626e7aff',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 10,
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
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' }
|
||||||
|
})
|
||||||
@@ -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',
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
|
|
||||||
|
|
||||||
export default function LoginScreen() {
|
|
||||||
const [username, setUsername] = React.useState('')
|
|
||||||
const [password, setPassword] = React.useState('')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<Text style={styles.text}>Login</Text>
|
|
||||||
<View style={{ width: '80%', marginTop: 20 }}>
|
|
||||||
<Text style={styles.textlabel}>Username</Text>
|
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
value={username}
|
|
||||||
placeholder='Enter username'
|
|
||||||
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={() => {
|
|
||||||
console.log('test')
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ color: '#fff', fontWeight: 'bold' }}>Login</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
{/* <TouchableOpacity
|
|
||||||
style={styles.signup}
|
|
||||||
onPress={() => {
|
|
||||||
router.navigate('/signup')
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ color: '#fff' }}>Sign Up</Text>
|
|
||||||
</TouchableOpacity> */}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#25292e',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
color: '#fff'
|
|
||||||
},
|
|
||||||
textlabel: {
|
|
||||||
color: '#fff',
|
|
||||||
marginBottom: 3
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
borderRadius: 5,
|
|
||||||
padding: 10,
|
|
||||||
marginBottom: 15
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
backgroundColor: '#1e90ff',
|
|
||||||
padding: 12,
|
|
||||||
borderRadius: 5,
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: 12
|
|
||||||
},
|
|
||||||
signup: {
|
|
||||||
alignItems: 'center'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -7,8 +7,8 @@ export interface Post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum StatusEnum {
|
export enum StatusEnum {
|
||||||
Created = "created",
|
Created = 'created',
|
||||||
Pending = "pending",
|
Pending = 'pending',
|
||||||
Denied = "denied",
|
Denied = 'denied',
|
||||||
Accepted = "accepted"
|
Approved = 'approved'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import * as SecureStore from 'expo-secure-store'
|
|
||||||
import { useCallback, useEffect, useReducer } from 'react'
|
|
||||||
import { Platform } from 'react-native'
|
|
||||||
|
|
||||||
type UseStateHook<T> = [[boolean, T | null], (value: T | null) => void]
|
|
||||||
|
|
||||||
function useAsyncState<T>(initialValue: [boolean, T | null] = [true, null]): UseStateHook<T> {
|
|
||||||
return useReducer(
|
|
||||||
(state: [boolean, T | null], action: T | null = null): [boolean, T | null] => [false, action],
|
|
||||||
initialValue
|
|
||||||
) as UseStateHook<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setStorageItemAsync(key: string, value: string | null) {
|
|
||||||
if (Platform.OS === 'web') {
|
|
||||||
try {
|
|
||||||
if (value === null) {
|
|
||||||
localStorage.removeItem(key)
|
|
||||||
} else {
|
|
||||||
localStorage.setItem(key, value)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Local storage is unavailable:', e)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (value == null) {
|
|
||||||
await SecureStore.deleteItemAsync(key)
|
|
||||||
} else {
|
|
||||||
await SecureStore.setItemAsync(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useStorageState(key: string): UseStateHook<string> {
|
|
||||||
// Public
|
|
||||||
const [state, setState] = useAsyncState<string>()
|
|
||||||
|
|
||||||
// Get
|
|
||||||
useEffect(() => {
|
|
||||||
if (Platform.OS === 'web') {
|
|
||||||
try {
|
|
||||||
if (typeof localStorage !== 'undefined') {
|
|
||||||
setState(localStorage.getItem(key))
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Local storage is unavailable:', e)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SecureStore.getItemAsync(key).then((value) => {
|
|
||||||
setState(value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [key, setState])
|
|
||||||
|
|
||||||
// Set
|
|
||||||
const setValue = useCallback(
|
|
||||||
(value: string | null) => {
|
|
||||||
setState(value)
|
|
||||||
setStorageItemAsync(key, value)
|
|
||||||
},
|
|
||||||
[key, setState]
|
|
||||||
)
|
|
||||||
|
|
||||||
return [state, setValue]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user