Skip to content

Commit

Permalink
feat: add ListItem and ToastMessage components; update Profile and Si…
Browse files Browse the repository at this point in the history
…gnUp screens to use Toast for notifications
  • Loading branch information
hackerman70000 committed Feb 1, 2025
1 parent 111a599 commit c11ef72
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 176 deletions.
37 changes: 22 additions & 15 deletions frontend/app/(auth)/sign-up.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Link, router } from 'expo-router'
import React, { useEffect, useState } from 'react'
import { ActivityIndicator, Alert, ImageBackground, KeyboardAvoidingView, Platform, ScrollView, Text, View } from 'react-native'
import { ActivityIndicator, ImageBackground, KeyboardAvoidingView, Platform, ScrollView, Text, View } from 'react-native'
import CustomButton from '../../components/CustomButton'
import FormField from '../../components/FormField'
import ToastMessage from '../../components/ToastMessage'
import { useGlobalContext } from '../../context/GlobalProvider'
import { API_URL } from '../_layout'

Expand All @@ -17,6 +18,7 @@ const SignUp = () => {
const [isSubmitting, setisSubmitting] = useState(false)
const [message, setMessage] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [toast, setToast] = useState(null)
const { isLoggedIn } = useGlobalContext()

useEffect(() => {
Expand All @@ -40,13 +42,13 @@ const SignUp = () => {
.then(data => {
setIsLoading(false)
if (data.message == 'Registration successful') {
const message = 'Account created successfully! You can now sign in'
if (isWeb){
window.alert(message)
} else {
Alert.alert(message)
}
router.replace(`/sign-in?username=${form.username}`)
setToast({
message: 'Account created successfully',
type: 'success'
})
setTimeout(() => {
router.replace(`/sign-in?username=${form.username}`)
}, 1500)
} else {
setMessage(`${data.message}! ${data.details}`)
setForm({
Expand All @@ -63,12 +65,10 @@ const SignUp = () => {
email: '',
password: ''
})
const message = 'Internal Server Error. Try again later'
if (isWeb) {
window.alert(message)
} else {
Alert.alert(message)
}
setToast({
message: 'Error creating account. Please try again later',
type: 'error'
})
})
}

Expand All @@ -87,10 +87,17 @@ const SignUp = () => {
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<View className='absolute inset-0 bg-black/60' />
{toast && (
<ToastMessage
message={toast.message}
type={toast.type}
onHide={() => setToast(null)}
/>
)}
<ScrollView>
<View className='w-full justify-center min-h-[85vh] px-6 my-6 max-w-[500px] self-center'>
<View className='bg-black/20 backdrop-blur-sm p-8 rounded-3xl'>
<Text className='text-3xl font-bold text-white mb-8 text-center'>Sing Up to Your Pharmacy</Text>
<Text className='text-3xl font-bold text-white mb-8 text-center'>Sign Up to Your Pharmacy</Text>

{message && (
<View className='bg-red-500/20 backdrop-blur-sm p-4 rounded-xl mb-6'>
Expand Down
127 changes: 104 additions & 23 deletions frontend/app/(tabs)/(products)/home.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,58 @@
import { router } from 'expo-router';
import { Grid, List } from 'lucide-react';
import React, { useEffect, useState } from 'react';
import { ActivityIndicator, Alert, ImageBackground, Platform, ScrollView, Text, View } from 'react-native';
import { ActivityIndicator, ImageBackground, Platform, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Item from '../../../components/Item';
import ListItem from '../../../components/ListItem';
import ToastMessage from '../../../components/ToastMessage';
import { useGlobalContext } from '../../../context/GlobalProvider';
import { API_URL } from '../../_layout';

const Home = () => {
const isWeb = Platform.OS === 'web';
const { isLoggedIn, state, triggerRefreshViews } = useGlobalContext();
const [products, setProducts] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [viewMode, setViewMode] = useState('list');
const [toast, setToast] = useState(null);

const handleAddToCart = async (productId) => {
if (!isLoggedIn) {
setToast({
message: 'Please sign in to add items to cart',
type: 'error'
});
setTimeout(() => {
router.push('/sign-in');
}, 1500);
return;
}

setIsLoading(true);
try {
await fetch(`${API_URL}/cart/add`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${state.token}`
},
body: JSON.stringify({ product_id: productId, quantity: 1 })
});
triggerRefreshViews();
setToast({
message: 'Added to cart',
type: 'success'
});
} catch (err) {
setToast({
message: 'Could not add to cart',
type: 'error'
});
} finally {
setIsLoading(false);
}
};

useEffect(() => {
setIsLoading(true);
Expand All @@ -23,16 +67,18 @@ const Home = () => {
})
.catch(err => {
console.log(err);
const message = 'Internal Server Error. Try again later';
if (isWeb) {
window.alert(message);
} else {
Alert.alert(message);
}
setToast({
message: 'Error loading products',
type: 'error'
});
})
.finally(() => setIsLoading(false));
}, []);

const toggleViewMode = () => {
setViewMode(viewMode === 'grid' ? 'list' : 'grid');
};

return (
<ImageBackground
source={require('../../../assets/images/index-background.jpg')}
Expand All @@ -45,28 +91,63 @@ const Home = () => {
>
<SafeAreaView className="h-full">
<View className="absolute inset-0 bg-black/60" />
{toast && (
<ToastMessage
message={toast.message}
type={toast.type}
onHide={() => setToast(null)}
/>
)}
<ScrollView>
<View className="w-full px-6 pt-8">
<Text className="text-4xl font-bold text-white text-center">Your Pharmacy</Text>
</View>

<View className="w-full justify-center min-h-[85vh] px-6 my-6 max-w-[1200px] self-center">
<View className="bg-black/20 backdrop-blur-sm p-8 rounded-3xl">
<Text className="text-2xl font-medium text-white mb-8">
{products.length > 0 ? 'Available Products' : 'No products available'}
</Text>

<View className="flex-row flex-wrap justify-center md:justify-start gap-4">
{products.map(product => (
<Item
key={product.id}
name={product.name}
price={product.price}
image={product.image_url}
onClick={() => router.push(`product-details?productId=${product.id}`)}
/>
))}
<View className="w-full justify-center min-h-[85vh] px-6 my-6 max-w-[1000px] self-center">
<View className="bg-black/20 backdrop-blur-sm p-6 rounded-3xl">
<View className="flex-row justify-between items-center mb-8">
<Text className="text-2xl font-medium text-white">
{products.length > 0 ? 'Available Products' : 'No products available'}
</Text>
<TouchableOpacity
onPress={toggleViewMode}
activeOpacity={0.7}
>
{viewMode === 'grid' ? (
<List size={24} color="#FFFFFF" />
) : (
<Grid size={24} color="#FFFFFF" />
)}
</TouchableOpacity>
</View>

{viewMode === 'grid' ? (
<View className="flex-row flex-wrap justify-center md:justify-start gap-4">
{products.map(product => (
<Item
key={product.id}
name={product.name}
price={product.price}
image={product.image_url}
onClick={() => router.push(`product-details?productId=${product.id}`)}
/>
))}
</View>
) : (
<View className="space-y-4">
{products.map(product => (
<ListItem
key={product.id}
name={product.name}
price={product.price}
image={product.image_url}
manufacturer={product.manufacturer}
onClick={() => router.push(`product-details?productId=${product.id}`)}
onAddToCart={() => handleAddToCart(product.id)}
/>
))}
</View>
)}
</View>
</View>
</ScrollView>
Expand Down
80 changes: 52 additions & 28 deletions frontend/app/(tabs)/(products)/product-details.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { router, useLocalSearchParams } from 'expo-router';
import { Minus, Plus } from 'lucide-react';
import React, { useEffect, useState } from 'react';
import { ActivityIndicator, Alert, Image, ImageBackground, Platform, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { ActivityIndicator, Image, ImageBackground, Platform, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import ToastMessage from '../../../components/ToastMessage';
import { useGlobalContext } from '../../../context/GlobalProvider';
import { API_URL } from '../../_layout';

Expand All @@ -12,6 +13,7 @@ const ProductDetails = () => {
const { isLoggedIn, state, triggerRefreshViews } = useGlobalContext();
const [isLoading, setIsLoading] = useState(false);
const [quantity, setQuantity] = useState(1);
const [toast, setToast] = useState(null);
const [productInfo, setProductInfo] = useState({
name: '',
price: 0,
Expand All @@ -21,6 +23,8 @@ const ProductDetails = () => {
});

useEffect(() => {
if (!productId) return;

setIsLoading(true);
fetch(`${API_URL}/products/${productId}`)
.then(res => res.json())
Expand All @@ -34,41 +38,54 @@ const ProductDetails = () => {
});
})
.catch(() => {
isWeb ? window.alert('Error loading product') : Alert.alert('Error loading product');
setToast({
message: 'Error loading product',
type: 'error'
});
})
.finally(() => setIsLoading(false));
}, []);
}, [productId]);

const handleAddToCart = () => {
const handleAddToCart = async () => {
if (!isLoggedIn) {
if (isWeb) {
if (window.confirm("Sign in required to add to cart")) router.push('/sign-in');
return;
}
Alert.alert("Sign in required", "", [
{ text: "Cancel" },
{ text: "Sign in", onPress: () => router.push('/sign-in') }
]);
setToast({
message: 'Please sign in to add items to cart',
type: 'error'
});
setTimeout(() => {
router.push('/sign-in');
}, 1500);
return;
}

setIsLoading(true);
fetch(`${API_URL}/cart/add`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${state.token}`
},
body: JSON.stringify({ product_id: productId, quantity })
})
.then(() => {
triggerRefreshViews();
try {
await fetch(`${API_URL}/cart/add`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${state.token}`
},
body: JSON.stringify({ product_id: productId, quantity })
});

triggerRefreshViews();
setToast({
message: 'Added to cart',
type: 'success'
});

setTimeout(() => {
router.push('/cart');
})
.catch(() => {
isWeb ? window.alert('Error adding to cart') : Alert.alert('Error adding to cart');
})
.finally(() => setIsLoading(false));
}, 1500);
} catch (err) {
setToast({
message: 'Could not add to cart',
type: 'error'
});
} finally {
setIsLoading(false);
}
};

return (
Expand All @@ -83,9 +100,16 @@ const ProductDetails = () => {
>
<SafeAreaView className="h-full">
<View className="absolute inset-0 bg-black/60" />
{toast && (
<ToastMessage
message={toast.message}
type={toast.type}
onHide={() => setToast(null)}
/>
)}
<ScrollView>
<View className="w-full max-w-xl mx-auto p-5">
{/* Header with order number and date */}
{/* Header with product info */}
<View className="flex-row justify-between items-center mb-6">
<View>
<Text className="text-2xl text-white font-medium">{productInfo.name}</Text>
Expand Down
Loading

0 comments on commit c11ef72

Please sign in to comment.