Bill Bot 1
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Star, Send, X } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Star } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import './FeedbackModal.css';
|
||||
|
||||
interface OrderItem {
|
||||
|
||||
@@ -5,7 +5,7 @@ interface AuthContextType {
|
||||
user: User | null;
|
||||
isLoading: boolean;
|
||||
checkUserExists: (mobileNumber: string) => Promise<{ success: boolean; userExists: boolean; message: string }>;
|
||||
login: (mobileNumber: string, pin: string) => Promise<{ success: boolean; message: string }>;
|
||||
login: (mobileNumber: string, pin: string) => Promise<{ success: boolean; message: string; isSuspended?: boolean }>;
|
||||
register: (mobileNumber: string, name: string, pin: string) => Promise<{ success: boolean; message: string }>;
|
||||
logout: () => void;
|
||||
changePin: (currentPin: string, newPin: string) => Promise<{ success: boolean; message: string }>;
|
||||
@@ -53,9 +53,12 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
if (data.success && data.user) {
|
||||
setUser(data.user);
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
if (data.token) {
|
||||
localStorage.setItem('token', data.token);
|
||||
}
|
||||
return { success: true, message: data.message };
|
||||
}
|
||||
return { success: false, message: data.message || 'Login failed' };
|
||||
return { success: false, message: data.message || 'Login failed', isSuspended: data.suspended || data.isSuspended };
|
||||
} catch (error) {
|
||||
return { success: false, message: 'Network error. Please try again.' };
|
||||
}
|
||||
@@ -72,6 +75,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
if (data.success && data.user) {
|
||||
setUser(data.user);
|
||||
localStorage.setItem('user', JSON.stringify(data.user));
|
||||
if (data.token) {
|
||||
localStorage.setItem('token', data.token);
|
||||
}
|
||||
return { success: true, message: data.message };
|
||||
}
|
||||
return { success: false, message: data.message || 'Registration failed' };
|
||||
@@ -82,22 +88,31 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
|
||||
const logout = () => {
|
||||
if (user) {
|
||||
const token = localStorage.getItem('token');
|
||||
fetch(`${API_BASE_URL}/logout`, { cache: 'no-store',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
},
|
||||
body: JSON.stringify({ mobileNumber: user.mobileNumber }),
|
||||
});
|
||||
}
|
||||
setUser(null);
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('token');
|
||||
};
|
||||
|
||||
const changePin = async (currentPin: string, newPin: string) => {
|
||||
if (!user) return { success: false, message: 'Not logged in' };
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${API_BASE_URL}/change-pin`, { cache: 'no-store',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
},
|
||||
body: JSON.stringify({ mobileNumber: user.mobileNumber, currentPin, newPin }),
|
||||
});
|
||||
const data = await response.json();
|
||||
@@ -110,9 +125,13 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const updateProfile = async (name: string, mobileNumber: string) => {
|
||||
if (!user) return { success: false, message: 'Not logged in' };
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${API_BASE_URL}/users/${user.id}`, { cache: 'no-store',
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
},
|
||||
body: JSON.stringify({ name, mobileNumber }),
|
||||
});
|
||||
|
||||
@@ -141,7 +160,13 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const refreshUser = async () => {
|
||||
if (!user) return;
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/user/${user.mobileNumber}`, { cache: 'no-store' });
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`${API_BASE_URL}/user/${user.mobileNumber}`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
}
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const updatedUser: User = {
|
||||
|
||||
@@ -7,7 +7,7 @@ interface FoodContextType {
|
||||
stalls: Stall[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
refreshData: () => Promise<void>;
|
||||
refreshData: (silent?: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
const FoodContext = createContext<FoodContextType | undefined>(undefined);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
CircleDollarSign,
|
||||
Wallet,
|
||||
ChevronRight,
|
||||
AlertCircle,
|
||||
@@ -24,7 +23,6 @@ const CheckoutScreen: React.FC = () => {
|
||||
const { refreshData } = useFood();
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [currentBalance, setCurrentBalance] = useState<number>(user?.ritzTokenBalance || 0);
|
||||
const [isLoadingBalance, setIsLoadingBalance] = useState(true);
|
||||
|
||||
// Conflict state
|
||||
const [stockConflicts, setStockConflicts] = useState<any[]>([]);
|
||||
@@ -37,14 +35,16 @@ const CheckoutScreen: React.FC = () => {
|
||||
const fetchBalance = async () => {
|
||||
if (!user) return;
|
||||
try {
|
||||
setIsLoadingBalance(true);
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/wallet/balance/${user.id}`);
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/wallet/balance/${user.id}`, {
|
||||
headers: {
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
setCurrentBalance(data.balance || 0);
|
||||
} catch (error) {
|
||||
console.error('Error fetching balance:', error);
|
||||
} finally {
|
||||
setIsLoadingBalance(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,9 +76,13 @@ const CheckoutScreen: React.FC = () => {
|
||||
};
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/orders`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
},
|
||||
body: JSON.stringify(orderData),
|
||||
});
|
||||
|
||||
|
||||
@@ -35,7 +35,12 @@ const HomeScreen: React.FC = () => {
|
||||
if (sessionStorage.getItem('feedback_dismissed')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/feedback/latest-unrated/${user?.id}`);
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/feedback/latest-unrated/${user?.id}`, {
|
||||
headers: {
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
}
|
||||
});
|
||||
if (response.status === 200) {
|
||||
const order = await response.json();
|
||||
console.log('Unrated order found:', order);
|
||||
@@ -53,8 +58,12 @@ const HomeScreen: React.FC = () => {
|
||||
if (!unratedOrder) return;
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
await fetch(`http://${window.location.hostname}:8080/api/feedback/skip/${unratedOrder.id}`, {
|
||||
method: 'POST'
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
}
|
||||
});
|
||||
setShowFeedbackModal(false);
|
||||
setShowFeedbackSnackbar(false);
|
||||
@@ -70,9 +79,13 @@ const HomeScreen: React.FC = () => {
|
||||
|
||||
const handleFeedbackSubmit = async (feedbackData: any) => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/feedback/submit`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
},
|
||||
body: JSON.stringify(feedbackData)
|
||||
});
|
||||
|
||||
@@ -112,7 +125,7 @@ const HomeScreen: React.FC = () => {
|
||||
<div className="container" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 24, textAlign: 'center' }}>
|
||||
<h2 style={{ marginBottom: 12 }}>Oops! Something went wrong</h2>
|
||||
<p style={{ color: 'var(--text-mid)', marginBottom: 24 }}>{error}</p>
|
||||
<button className="primary-button" onClick={refreshData} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<button className="primary-button" onClick={() => refreshData()} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<RefreshCcw size={18} /> Try Again
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import type { FoodItem } from '../types';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import Header from '../components/Header';
|
||||
@@ -56,21 +57,18 @@ const ItemDetailScreen: React.FC = () => {
|
||||
fetchItem();
|
||||
}
|
||||
}, [itemId, contextItem]);
|
||||
const quantity = item ? getItemQuantity(item.id) : 0;
|
||||
const isLimitReached = item && item.stock !== undefined && quantity >= item.stock && item.stock > 0;
|
||||
|
||||
if ((isGlobalLoading && foodItems.length === 0) || isFetching) {
|
||||
return (
|
||||
<div className="container item-loading-wrapper">
|
||||
<div className="loading-spinner-wrapper">
|
||||
<div className="loading-spinner"></div>
|
||||
<p>Loading Delights...</p>
|
||||
if (isGlobalLoading || isFetching || !item) {
|
||||
if (isGlobalLoading || isFetching) {
|
||||
return (
|
||||
<div className="container item-loading-wrapper">
|
||||
<div className="loading-spinner-wrapper">
|
||||
<div className="loading-spinner"></div>
|
||||
<p>Loading Delights...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!item && !isGlobalLoading && !isFetching) {
|
||||
return (
|
||||
<div className="container item-not-found-wrapper">
|
||||
<Header />
|
||||
@@ -84,6 +82,9 @@ const ItemDetailScreen: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const quantity = getItemQuantity(item.id);
|
||||
const isLimitReached = item.stock !== undefined && quantity >= item.stock && item.stock > 0;
|
||||
|
||||
return (
|
||||
<div className={`container item-detail-page ${item.stock === 0 ? 'out-of-stock' : ''}`}>
|
||||
<Header title="" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Clock, ShoppingBag as ShoppingBagIcon, ChevronRight as ChevronRightIcon, X, RefreshCcw, AlertCircle, ShoppingCart } from 'lucide-react';
|
||||
import { Clock, ShoppingBag as ShoppingBagIcon, ChevronRight as ChevronRightIcon, X, RefreshCcw, AlertCircle } from 'lucide-react';
|
||||
import { QRCodeCanvas } from 'qrcode.react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import Header from '../components/Header';
|
||||
@@ -58,7 +58,7 @@ const MyOrdersScreen: React.FC = () => {
|
||||
|
||||
// Polling for live updates (Sync when Dashboard regenerates QR or changes status)
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
let interval: ReturnType<typeof setInterval>;
|
||||
|
||||
// Only poll if there are active (non-finalized) orders
|
||||
const hasActiveOrders = orders.some(o =>
|
||||
@@ -79,7 +79,12 @@ const MyOrdersScreen: React.FC = () => {
|
||||
|
||||
const fetchOrders = async () => {
|
||||
try {
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/orders/user/${user?.id}`);
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/orders/user/${user?.id}`, {
|
||||
headers: {
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
setOrders(data);
|
||||
} catch (error) {
|
||||
@@ -132,10 +137,14 @@ const MyOrdersScreen: React.FC = () => {
|
||||
if (!selectedOrder) return;
|
||||
setIsRegenerating(true);
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const newOrderNumber = `ORD-${Math.random().toString(36).substring(2, 10).toUpperCase()}`;
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/orders/${selectedOrder.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...selectedOrder,
|
||||
orderNumber: newOrderNumber
|
||||
|
||||
@@ -71,9 +71,9 @@ const StallDetailScreen: React.FC = () => {
|
||||
if (!prevStall) return prevStall;
|
||||
return {
|
||||
...prevStall,
|
||||
products: prevStall.products.map(p =>
|
||||
products: prevStall.products ? prevStall.products.map(p =>
|
||||
p.id === update.productId.toString() ? { ...p, stock: update.stock } : p
|
||||
)
|
||||
) : []
|
||||
};
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user