
navigate('/')}
/>
diff --git a/ordering_site/src/index.css b/ordering_site/src/index.css
index 3cf17f28..d5b85d70 100644
--- a/ordering_site/src/index.css
+++ b/ordering_site/src/index.css
@@ -93,9 +93,43 @@ a {
}
.loading-spinner {
- padding: 20px;
- color: var(--primary);
- font-weight: 600;
+ display: flex !important;
+ flex-direction: column !important;
+ align-items: center !important;
+ justify-content: center !important;
+ gap: 12px !important;
+ border: none !important;
+ border-radius: 0 !important;
+ animation: none !important;
+ width: auto !important;
+ height: auto !important;
+ padding: 20px !important;
+ color: var(--primary) !important;
+ font-weight: 600 !important;
+}
+
+.loading-spinner::before {
+ content: '' !important;
+ display: block !important;
+ width: 48px !important;
+ height: 48px !important;
+ background-image: url('./assets/tillo_spinner.gif') !important;
+ background-size: contain !important;
+ background-position: center !important;
+ background-repeat: no-repeat !important;
+}
+
+.loading-spinner-small {
+ width: 18px !important;
+ height: 18px !important;
+ border: none !important;
+ border-radius: 0 !important;
+ animation: none !important;
+ background-image: url('./assets/tillo_spinner.gif') !important;
+ background-size: contain !important;
+ background-position: center !important;
+ background-repeat: no-repeat !important;
+ display: inline-block !important;
}
/* Global QR Overlay Styles */
@@ -110,7 +144,8 @@ a {
}
.qr-completed canvas,
-.qr-expired canvas {
+.qr-expired canvas,
+.qr-cancelled canvas {
filter: blur(8px) !important;
opacity: 0.6 !important;
}
@@ -119,6 +154,10 @@ a {
filter: blur(12px) grayscale(100%) !important;
}
+.qr-cancelled canvas {
+ filter: blur(10px) !important;
+}
+
.qr-overlay {
position: absolute;
top: 0;
@@ -150,6 +189,10 @@ a {
background: #374151 !important;
}
+.overlay-text.cancelled {
+ background: #dc2626 !important;
+}
+
.overlay-text.mini {
padding: 4px 8px;
font-size: 0.75rem;
@@ -157,7 +200,8 @@ a {
}
.latest-qr-wrapper.qr-completed canvas,
-.latest-qr-wrapper.qr-expired canvas {
+.latest-qr-wrapper.qr-expired canvas,
+.latest-qr-wrapper.qr-cancelled canvas {
filter: blur(4px) !important;
}
diff --git a/ordering_site/src/pages/LoginScreen.css b/ordering_site/src/pages/LoginScreen.css
index 9ab0a2e1..a3e0f026 100644
--- a/ordering_site/src/pages/LoginScreen.css
+++ b/ordering_site/src/pages/LoginScreen.css
@@ -18,7 +18,7 @@
height: 100%;
z-index: 0;
background-color: #ffffff;
- background-image:
+ background-image:
radial-gradient(at 0% 0%, rgba(35, 22, 81, 0.15) 0px, transparent 50%),
radial-gradient(at 100% 0%, rgba(0, 208, 148, 0.1) 0px, transparent 50%),
radial-gradient(at 100% 100%, rgba(35, 22, 81, 0.1) 0px, transparent 50%),
@@ -44,13 +44,15 @@
border-radius: 32px;
padding: 40px 32px;
border: 1px solid rgba(255, 255, 255, 0.5);
- box-shadow:
+ box-shadow:
0 20px 50px rgba(35, 22, 81, 0.1),
inset 0 0 0 1px rgba(255, 255, 255, 0.5);
}
.login-header-logo {
display: flex;
+ flex-direction: column;
+ align-items: center;
justify-content: center;
margin-bottom: 24px;
}
@@ -157,7 +159,7 @@
box-shadow: 0 0 0 4px rgba(35, 22, 81, 0.05);
}
-.login-form input:focus + .input-icon {
+.login-form input:focus+.input-icon {
color: var(--primary);
}
@@ -269,6 +271,7 @@
opacity: 0;
transform: scale(0.95);
}
+
to {
opacity: 1;
transform: scale(1);
@@ -319,12 +322,27 @@
margin: 0 2px;
}
-.loading-dots span:nth-child(2) { animation-delay: 0.2s; }
-.loading-dots span:nth-child(3) { animation-delay: 0.4s; }
+.loading-dots span:nth-child(2) {
+ animation-delay: 0.2s;
+}
+
+.loading-dots span:nth-child(3) {
+ animation-delay: 0.4s;
+}
@keyframes loadingDots {
- 0%, 80%, 100% { transform: translateY(0); opacity: 0.2; }
- 40% { transform: translateY(-4px); opacity: 1; }
+
+ 0%,
+ 80%,
+ 100% {
+ transform: translateY(0);
+ opacity: 0.2;
+ }
+
+ 40% {
+ transform: translateY(-4px);
+ opacity: 1;
+ }
}
.error-message {
@@ -373,3 +391,54 @@
border-radius: 24px;
}
}
+
+.tillo-logo {
+ height: 80px;
+ width: auto;
+ object-fit: contain;
+ filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1));
+}
+
+.login-merchant-card {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: rgba(255, 255, 255, 0.85);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ border-radius: 20px;
+ padding: 16px 20px;
+ border: 1px solid rgba(255, 255, 255, 0.5);
+ box-shadow:
+ 0 10px 30px rgba(35, 22, 81, 0.05),
+ inset 0 0 0 1px rgba(255, 255, 255, 0.5);
+ gap: 16px;
+}
+
+.merchant-info {
+ display: flex;
+ flex-direction: column;
+ text-align: left;
+}
+
+.merchant-label {
+ font-size: 10px;
+ font-weight: 800;
+ color: var(--text-light);
+ text-transform: uppercase;
+ letter-spacing: 0.8px;
+}
+
+.merchant-name {
+ font-size: 13px;
+ font-weight: 700;
+ color: var(--text-dark);
+ margin-top: 2px;
+}
+
+.merchant-rit-logo {
+ height: 48px;
+ width: auto;
+ object-fit: contain;
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.05));
+}
\ No newline at end of file
diff --git a/ordering_site/src/pages/LoginScreen.tsx b/ordering_site/src/pages/LoginScreen.tsx
index a9f190b1..db7be5e4 100644
--- a/ordering_site/src/pages/LoginScreen.tsx
+++ b/ordering_site/src/pages/LoginScreen.tsx
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Phone, User as UserIcon, Lock, ChevronLeft } from 'lucide-react';
import { useAuth } from '../contexts/AuthContext';
+import tilloLogo from '../assets/Tillo.png';
import ritLogo from '../assets/RIT_Logo.png';
import './LoginScreen.css';
@@ -91,7 +92,7 @@ const LoginScreen: React.FC = () => {
-

+
Till yo tummy is full...
@@ -214,6 +215,13 @@ const LoginScreen: React.FC = () => {
)}
+
+
+ Merchant Partner
+
+

+
+
© 2026 Rajalakshmi Institute of Technology
Tillo Canteen Management System
diff --git a/ordering_site/src/pages/MyOrdersScreen.tsx b/ordering_site/src/pages/MyOrdersScreen.tsx
index c8082db9..2a5689a8 100644
--- a/ordering_site/src/pages/MyOrdersScreen.tsx
+++ b/ordering_site/src/pages/MyOrdersScreen.tsx
@@ -41,6 +41,8 @@ const MyOrdersScreen: React.FC = () => {
const [showStockAlert, setShowStockAlert] = useState(false);
const [unavailableItems, setUnavailableItems] = useState
([]);
const [pendingItems, setPendingItems] = useState([]);
+
+ const [isCancelling, setIsCancelling] = useState(false);
const isOrderExpired = (order: Order) => {
if (order.isArchived) return true;
@@ -126,6 +128,35 @@ const MyOrdersScreen: React.FC = () => {
}
};
+ const handleCancelOrder = async (orderId: number) => {
+ if (!window.confirm('Are you sure you want to cancel this order?')) return;
+
+ setIsCancelling(true);
+ try {
+ const token = localStorage.getItem('token');
+ const response = await fetch(`http://${window.location.hostname}:8080/api/orders/${orderId}/cancel`, {
+ method: 'POST',
+ headers: {
+ ...(token ? { 'Authorization': `Bearer ${token}` } : {})
+ }
+ });
+ const data = await response.json();
+ if (data.success) {
+ fetchOrders();
+ if (selectedOrder?.id === orderId) {
+ setSelectedOrder({ ...selectedOrder, status: 'CANCELLED' });
+ }
+ } else {
+ alert(data.message || 'Failed to cancel order');
+ }
+ } catch (error) {
+ console.error('Error cancelling order:', error);
+ alert('Error cancelling order');
+ } finally {
+ setIsCancelling(false);
+ }
+ };
+
const performRepeat = (items: FoodItem[]) => {
clearCart();
items.forEach(item => addToCart(item));
@@ -170,7 +201,7 @@ const MyOrdersScreen: React.FC = () => {
{latestOrder.status}
-
+
{latestOrder.status.toUpperCase() === 'COMPLETED' && (
@@ -178,6 +209,11 @@ const MyOrdersScreen: React.FC = () => {
COMPLETED
)}
+ {latestOrder.status.toUpperCase() === 'CANCELLED' && (
+
+ CANCELLED
+
+ )}
{isOrderExpired(latestOrder) && (
EXPIRED
@@ -185,7 +221,7 @@ const MyOrdersScreen: React.FC = () => {
)}
- {isOrderExpired(latestOrder) ? 'QR Expired' : (latestOrder.status.toUpperCase() === 'COMPLETED' ? 'Order Fulfilled' : 'Tap to enlarge QR')}
+ {isOrderExpired(latestOrder) ? 'QR Expired' : (latestOrder.status.toUpperCase() === 'COMPLETED' ? 'Order Fulfilled' : latestOrder.status.toUpperCase() === 'CANCELLED' ? 'Order Cancelled' : 'Tap to enlarge QR')}
@@ -200,15 +236,31 @@ const MyOrdersScreen: React.FC = () => {
R{latestOrder.totalAmount.toFixed(2)}
-
+
+
+ {latestOrder.status.toUpperCase() !== 'COMPLETED' && latestOrder.status.toUpperCase() !== 'CANCELLED' && !isOrderExpired(latestOrder) && (
+
+ )}
+
)}
@@ -270,7 +322,7 @@ const MyOrdersScreen: React.FC = () => {
Order #{selectedOrder.displayOrderId}
-
+
{selectedOrder.status.toUpperCase() === 'COMPLETED' && (
@@ -278,6 +330,11 @@ const MyOrdersScreen: React.FC = () => {
COMPLETED
)}
+ {selectedOrder.status.toUpperCase() === 'CANCELLED' && (
+
+ CANCELLED
+
+ )}
{isOrderExpired(selectedOrder) && (
EXPIRED
@@ -289,7 +346,9 @@ const MyOrdersScreen: React.FC = () => {
? 'QR Expired'
: selectedOrder.status.toUpperCase() === 'COMPLETED'
? 'This order has been fulfilled'
- : 'Show this QR code at the counter'}
+ : selectedOrder.status.toUpperCase() === 'CANCELLED'
+ ? 'This order has been cancelled'
+ : 'Show this QR code at the counter'}
@@ -323,9 +382,21 @@ const MyOrdersScreen: React.FC = () => {
-
+
+
+ {selectedOrder.status.toUpperCase() !== 'COMPLETED' && selectedOrder.status.toUpperCase() !== 'CANCELLED' && !isOrderExpired(selectedOrder) && (
+
+ )}
+
)}
diff --git a/ordering_site/src/pages/SuccessScreen.tsx b/ordering_site/src/pages/SuccessScreen.tsx
index 56497fdc..35815834 100644
--- a/ordering_site/src/pages/SuccessScreen.tsx
+++ b/ordering_site/src/pages/SuccessScreen.tsx
@@ -23,7 +23,7 @@ const SuccessScreen: React.FC = () => {
Order Placed!
Order ID #{displayOrderId}
-
+
{
COMPLETED
)}
+ {status === 'CANCELLED' && (
+
+ CANCELLED
+
+ )}
{archived && (
EXPIRED
@@ -48,7 +53,9 @@ const SuccessScreen: React.FC = () => {
? 'This order has expired'
: status === 'COMPLETED'
? 'Order has been fulfilled'
- : 'Scan this QR at the counter to collect your food'}
+ : status === 'CANCELLED'
+ ? 'Order has been cancelled'
+ : 'Scan this QR at the counter to collect your food'}
diff --git a/tillo_spinner.gif b/tillo_spinner.gif
new file mode 100644
index 00000000..df79042d
Binary files /dev/null and b/tillo_spinner.gif differ