diff --git a/backend/src/main/java/com/rit/canteen/sales/config/SecurityConfig.java b/backend/src/main/java/com/rit/canteen/sales/config/SecurityConfig.java index fab3f20c..07d4c602 100644 --- a/backend/src/main/java/com/rit/canteen/sales/config/SecurityConfig.java +++ b/backend/src/main/java/com/rit/canteen/sales/config/SecurityConfig.java @@ -28,6 +28,7 @@ public class SecurityConfig { .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/api/notifications/**").permitAll() .requestMatchers("/api/**").permitAll() .anyRequest().permitAll() ); @@ -46,7 +47,7 @@ public class SecurityConfig { configuration.setAllowedOriginPatterns(List.of("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); - configuration.setAllowCredentials(true); + configuration.setAllowCredentials(false); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; diff --git a/backend/src/main/java/com/rit/canteen/sales/controller/SystemNotificationController.java b/backend/src/main/java/com/rit/canteen/sales/controller/SystemNotificationController.java index 2ebfd4ae..fa7bd7c5 100644 --- a/backend/src/main/java/com/rit/canteen/sales/controller/SystemNotificationController.java +++ b/backend/src/main/java/com/rit/canteen/sales/controller/SystemNotificationController.java @@ -15,14 +15,31 @@ public class SystemNotificationController { @Autowired private SystemNotificationService notificationService; + @GetMapping("/test") + public String testReachability() { + return "Notification API is Reachable and whitelisted!"; + } + @GetMapping public List getUnread() { - return notificationService.getUnreadNotifications(); + try { + return notificationService.getUnreadNotifications(); + } catch (Exception e) { + System.err.println("[CRITICAL] Failed to fetch unread notifications:"); + e.printStackTrace(); + throw e; + } } @GetMapping("/all") public List getAll() { - return notificationService.getAllNotifications(); + try { + return notificationService.getAllNotifications(); + } catch (Exception e) { + System.err.println("[CRITICAL] Failed to fetch all notifications:"); + e.printStackTrace(); + throw e; + } } @PostMapping("/mark-read/{id}") diff --git a/backend/src/main/java/com/rit/canteen/sales/model/SystemNotification.java b/backend/src/main/java/com/rit/canteen/sales/model/SystemNotification.java index 0a85036d..a94302bf 100644 --- a/backend/src/main/java/com/rit/canteen/sales/model/SystemNotification.java +++ b/backend/src/main/java/com/rit/canteen/sales/model/SystemNotification.java @@ -20,8 +20,8 @@ public class SystemNotification { @Column(nullable = false) private String type; // FEEDBACK, PURCHASE, PRODUCT, COUPON - @Column(nullable = false) - private boolean isRead = false; + @Column(name = "is_read", nullable = false) + private Boolean isReadStatus = false; @Column(nullable = true) private String link; // URL to navigate to @@ -57,8 +57,8 @@ public class SystemNotification { public String getType() { return type; } public void setType(String type) { this.type = type; } - public boolean isRead() { return isRead; } - public void setRead(boolean read) { isRead = read; } + public Boolean isReadStatus() { return isReadStatus; } + public void setReadStatus(Boolean readStatus) { isReadStatus = readStatus; } public String getLink() { return link; } public void setLink(String link) { this.link = link; } diff --git a/backend/src/main/java/com/rit/canteen/sales/repository/SystemNotificationRepository.java b/backend/src/main/java/com/rit/canteen/sales/repository/SystemNotificationRepository.java index 1e3f9ae2..90e90d35 100644 --- a/backend/src/main/java/com/rit/canteen/sales/repository/SystemNotificationRepository.java +++ b/backend/src/main/java/com/rit/canteen/sales/repository/SystemNotificationRepository.java @@ -2,11 +2,14 @@ package com.rit.canteen.sales.repository; import com.rit.canteen.sales.model.SystemNotification; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface SystemNotificationRepository extends JpaRepository { - List findByIsReadFalseOrderByCreatedAtDesc(); + @Query(value = "SELECT * FROM system_notifications WHERE is_read = false ORDER BY created_at DESC", nativeQuery = true) + List findByReadStatusFalse(); + List findAllByOrderByCreatedAtDesc(); } diff --git a/backend/src/main/java/com/rit/canteen/sales/service/SystemNotificationService.java b/backend/src/main/java/com/rit/canteen/sales/service/SystemNotificationService.java index 967b9b41..79e65692 100644 --- a/backend/src/main/java/com/rit/canteen/sales/service/SystemNotificationService.java +++ b/backend/src/main/java/com/rit/canteen/sales/service/SystemNotificationService.java @@ -28,7 +28,7 @@ public class SystemNotificationService { } public List getUnreadNotifications() { - return notificationRepository.findByIsReadFalseOrderByCreatedAtDesc(); + return notificationRepository.findByReadStatusFalse(); } public List getAllNotifications() { @@ -38,16 +38,16 @@ public class SystemNotificationService { @Transactional public void markAsRead(Long id) { notificationRepository.findById(id).ifPresent(n -> { - n.setRead(true); + n.setReadStatus(Boolean.TRUE); notificationRepository.save(n); }); } @Transactional public void markAllAsRead() { - List unread = notificationRepository.findByIsReadFalseOrderByCreatedAtDesc(); + List unread = notificationRepository.findByReadStatusFalse(); for (SystemNotification n : unread) { - n.setRead(true); + n.setReadStatus(Boolean.TRUE); } notificationRepository.saveAll(unread); } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8127cf61..2b56bab5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -28,6 +28,7 @@ import RitzCirculation from './pages/RitzCirculation.tsx'; import ManageWallets from './pages/ManageWallets.tsx'; import ManageCoupons from './pages/ManageCoupons.tsx'; import Settings from './pages/Settings.tsx'; +import NotFound from './pages/NotFound.tsx'; const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { @@ -111,6 +112,7 @@ function App() { {/* Settings */} } /> + } /> ); diff --git a/frontend/src/pages/NotFound.tsx b/frontend/src/pages/NotFound.tsx new file mode 100644 index 00000000..e3382b5f --- /dev/null +++ b/frontend/src/pages/NotFound.tsx @@ -0,0 +1,99 @@ +import { useNavigate } from 'react-router-dom'; +import { Home, ArrowLeft, Search, HelpCircle } from 'lucide-react'; + +const NotFound = () => { + const navigate = useNavigate(); + + return ( +
+
+
+ {/* Visual Section */} +
+ {/* Background Decorative Circles */} +
+
+ + {/* 404 Main Text */} +
+

+ 404 +

+ + {/* Detailed Icon Stack */} +
+
+ + + {/* Small Floating Icons */} +
+ +
+ +
+ Lost? +
+
+
+
+
+ + {/* Content Section */} +
+
+ + + + Error Code: 404 +
+ +

+ Well, this is awkward. +

+ +

+ The page you are looking for seems to have vanished into the grid. Either the URL is incorrect, or it's hiding in a different sector. +

+ +
+ + + +
+ + {/* Quick Links Section */} +
+

Common Sectors

+
+ {['Orders', 'Inventory', 'Reports', 'Settings'].map((item) => ( + + ))} +
+
+
+
+
+
+ ); +}; + +export default NotFound; diff --git a/ordering_site/src/App.tsx b/ordering_site/src/App.tsx index 2430c5d6..96a40e41 100644 --- a/ordering_site/src/App.tsx +++ b/ordering_site/src/App.tsx @@ -18,6 +18,7 @@ import StallDetailScreen from './pages/StallDetailScreen'; import WalletScreen from './pages/WalletScreen'; import TopUpScreen from './pages/TopUpScreen'; import PreferencesScreen from './pages/PreferencesScreen'; +import NotFoundScreen from './pages/NotFoundScreen'; import StockAlert from './components/StockAlert'; import './App.css'; @@ -98,6 +99,7 @@ function App() { } /> + } /> diff --git a/ordering_site/src/contexts/AuthContext.tsx b/ordering_site/src/contexts/AuthContext.tsx index a1c29309..a303379b 100644 --- a/ordering_site/src/contexts/AuthContext.tsx +++ b/ordering_site/src/contexts/AuthContext.tsx @@ -10,6 +10,7 @@ interface AuthContextType { logout: () => void; changePin: (currentPin: string, newPin: string) => Promise<{ success: boolean; message: string }>; updateProfile: (name: string, mobileNumber: string) => Promise<{ success: boolean; message: string }>; + refreshUser: () => Promise; } const AuthContext = createContext(undefined); @@ -137,35 +138,46 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children } }; - // Background suspension check + const refreshUser = async () => { + if (!user) return; + try { + const response = await fetch(`${API_BASE_URL}/user/${user.mobileNumber}`, { cache: 'no-store' }); + if (response.ok) { + const data = await response.json(); + const updatedUser: User = { + id: data.id, + name: data.name, + mobileNumber: data.mobileNumber, + isLoggedIn: data.isLoggedIn || data.loggedIn || true, + isSuspended: data.isSuspended || data.suspended, + ritzTokenBalance: data.ritzTokenBalance + }; + + // Update state and localStorage + setUser(updatedUser); + localStorage.setItem('user', JSON.stringify(updatedUser)); + } else if (response.status === 404) { + logout(); + } + } catch (error) { + console.error('Error refreshing user data:', error); + } + }; + + // Background status and balance sync useEffect(() => { if (!user) return; - const checkSuspensionStatus = async () => { - try { - const response = await fetch(`${API_BASE_URL}/user/${user.mobileNumber}`, { cache: 'no-store' }); - if (response.status === 404) { - logout(); // User deleted - return; - } - if (response.ok) { - const data = await response.json(); - if (data.isSuspended || data.suspended) { - console.log("Account suspended. Logging out..."); - logout(); - } - } - } catch (error) { - console.error('Error checking user status:', error); - } + const syncUserStatus = async () => { + await refreshUser(); }; - const intervalId = setInterval(checkSuspensionStatus, 30000); // Check every 30 seconds + const intervalId = setInterval(syncUserStatus, 30000); // Sync every 30 seconds return () => clearInterval(intervalId); }, [user]); return ( - + {children} ); diff --git a/ordering_site/src/pages/HomeScreen.css b/ordering_site/src/pages/HomeScreen.css index 311a3a6e..7381bb72 100644 --- a/ordering_site/src/pages/HomeScreen.css +++ b/ordering_site/src/pages/HomeScreen.css @@ -1,19 +1,58 @@ .welcome-section { - padding: 24px 16px 8px; - background-color: var(--surface); + padding: 24px 20px 16px; +} + +.greeting-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 4px; } .welcome-message { - font-size: 1.8rem; - font-weight: 800; + font-size: 26px; + font-weight: 850; color: var(--text-dark); - line-height: 1.2; + margin: 0; + letter-spacing: -0.02em; +} + +.wallet-badge { + display: flex; + align-items: center; + gap: 8px; + background: white; + padding: 8px 12px; + border-radius: 14px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + border: 1px solid rgba(0, 0, 0, 0.03); + cursor: pointer; + transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); +} + +.wallet-badge:active { + transform: scale(0.95); +} + +.wallet-icon-img { + width: 22px; + height: 22px; + object-fit: contain; +} + +.wallet-balance-text { + font-weight: 800; + font-size: 15px; + color: var(--primary-color); + letter-spacing: -0.01em; } .welcome-subtitle { - font-size: 0.95rem; + font-size: 15px; color: var(--text-mid); - margin-top: 4px; + font-weight: 500; + margin: 0; } .search-bar-container { diff --git a/ordering_site/src/pages/HomeScreen.tsx b/ordering_site/src/pages/HomeScreen.tsx index 84859227..ff9fcb54 100644 --- a/ordering_site/src/pages/HomeScreen.tsx +++ b/ordering_site/src/pages/HomeScreen.tsx @@ -9,12 +9,13 @@ import FeedbackModal from '../components/FeedbackModal'; import { useFood } from '../contexts/FoodContext'; import { useAuth } from '../contexts/AuthContext'; import { motion, AnimatePresence } from 'framer-motion'; +import walletIcon from '../assets/front.png'; import './HomeScreen.css'; const HomeScreen: React.FC = () => { const navigate = useNavigate(); const { categories, stalls, foodItems, isLoading, error, refreshData } = useFood(); - const { user } = useAuth(); + const { user, refreshUser } = useAuth(); const [searchQuery, setSearchQuery] = useState(''); // Feedback state @@ -25,6 +26,7 @@ const HomeScreen: React.FC = () => { useEffect(() => { if (user?.id) { checkForUnratedOrder(); + refreshUser(); } }, [user]); @@ -123,7 +125,15 @@ const HomeScreen: React.FC = () => {
-

Hello {user?.name || 'Guest'}!

+
+

Hello {user?.name || 'Guest'}!

+ {user && ( +
navigate('/wallet')}> + Wallet + R {user.ritzTokenBalance || 0} +
+ )} +

What would you like to eat today?