fixing image display for posts on profile page, adding success confirmation modal, changing posts display,

This commit is contained in:
Will Baumbach
2025-08-06 13:36:47 -05:00
parent ed4e1089ee
commit 878e18941e
8 changed files with 187 additions and 210 deletions

View File

@@ -36,13 +36,17 @@ export const getPost = async (req, res, next) => {
}
export const getAllPostsByUser = async (req, res, next) => {
const post = await Post.find({ email: req.params.user }).exec()
if (!post) {
console.log(req.params)
const posts = await Post.find({ userID: req.params.user }).exec()
posts.map((el) => {
el.photo = process.env.CLOUDFRONT_URL + el.photo
})
if (!posts) {
return next('No document found with that id', 404)
}
res.status(200).json({
status: 'success',
data: post
data: posts
})
}

View File

@@ -1,12 +1,13 @@
import { CameraView, useCameraPermissions } from 'expo-camera'
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() {
const [permission, requestPermission] = useCameraPermissions()
const [photo, setPhoto] = useState('')
const [address, setAddress] = useState('')
const [notes, setNotes] = useState('')
const [showConfirmation, setShowConfirmation] = useState(false)
const cameraRef = useRef<CameraView>(null)
async function resetState() {
@@ -43,6 +44,7 @@ export default function App() {
}).then((res) => {
console.log(res.status)
if (res.status === 200) {
setShowConfirmation(true)
resetState()
}
})
@@ -109,6 +111,26 @@ export default function App() {
</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>
)
}
@@ -121,6 +143,48 @@ const styles = StyleSheet.create({
paddingHorizontal: 25,
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: {
flex: 1
},

View File

@@ -30,16 +30,14 @@ export default function PostsScreen() {
return (
<View style={styles.wrapper}>
<View style={styles.container}>
<Text style={styles.text}>Posts</Text>
{posts?.length ? (
posts.map((el) => <PostComponent key={el._id} post={el} fetchData={fetchData} />)
) : (
<View style={styles.caughtUpContainer}>
<Text style={styles.caughtUpText}>All caught up!</Text>
</View>
)}
</View>
<Text style={styles.title}>Posts</Text>
{posts?.length ? (
posts.map((el) => <PostComponent key={el._id} post={el} fetchData={fetchData} />)
) : (
<View style={styles.caughtUpContainer}>
<Text style={styles.caughtUpText}>All caught up!</Text>
</View>
)}
</View>
)
}
@@ -47,14 +45,12 @@ export default function PostsScreen() {
const styles = StyleSheet.create({
wrapper: {
flex: 1,
backgroundColor: '#25292e'
},
container: {
flex: 1,
backgroundColor: '#25292e',
padding: 16,
alignItems: 'center',
overflow: 'scroll'
},
text: {
title: {
color: '#fff',
justifyContent: 'center'
},

View File

@@ -1,10 +1,35 @@
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 { PostComponent } from '../components/PostComponent'
import { SignOutButton } from '../components/SignOutButton'
import { Post } from '../models/postModel'
export default function PostsScreen() {
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/6883ddb2640ebaa1a12e3791`)
.then((res) => res.json())
.then((json) => {
console.log(json)
setPosts(json.data)
})
}
return (
<>
@@ -23,7 +48,15 @@ export default function PostsScreen() {
<SignOutButton></SignOutButton>
</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>
</>
)
@@ -33,7 +66,8 @@ const styles = StyleSheet.create({
wrapper: {
flex: 1,
backgroundColor: '#25292e',
padding: 16
padding: 16,
overflow: 'scroll'
},
profileInfoCard: {
backgroundColor: '#373d44ff',
@@ -66,12 +100,14 @@ const styles = StyleSheet.create({
marginTop: 12
},
userPostsCard: {
backgroundColor: '#373d44ff',
flex: 0.75,
marginTop: 12,
borderColor: '#626e7aff',
borderStyle: 'solid',
borderWidth: 1,
borderRadius: 12
flex: 1
},
noPostsContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
noPosts: {
color: '#787b80ff'
}
})

View File

@@ -1,6 +1,7 @@
import { useUser } from '@clerk/clerk-react'
import React from 'react'
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { Post } from '../models/postModel'
import { Post, StatusEnum } from '../models/postModel'
type PostComponentProps = {
post: Post
@@ -8,6 +9,8 @@ type PostComponentProps = {
}
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}`, {
@@ -51,34 +54,44 @@ export const PostComponent: React.FC<PostComponentProps> = ({ post, fetchData })
/>
</View>
<Text style={{ color: '#fff' }}>{post.notes}</Text>
<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>
{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>}
</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>
)
}
@@ -90,9 +103,16 @@ const styles = StyleSheet.create({
},
posts: {
marginTop: 10,
backgroundColor: '#363c43ff',
backgroundColor: '#373d44ff',
borderColor: '#626e7aff',
borderStyle: 'solid',
borderWidth: 1,
borderRadius: 12,
padding: 10,
width: '90%',
borderRadius: 5
}
width: '100%'
},
created: { color: 'white' },
pending: { color: 'yellow' },
denied: { color: 'red' },
accepted: { color: 'green' }
})

View File

@@ -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'
}
})

View File

@@ -7,8 +7,8 @@ export interface Post {
}
export enum StatusEnum {
Created = "created",
Pending = "pending",
Denied = "denied",
Accepted = "accepted"
}
Created = 'created',
Pending = 'pending',
Denied = 'denied',
Accepted = 'accepted'
}

View File

@@ -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]
}