404 page added
This commit is contained in:
@@ -28,6 +28,7 @@ public class SecurityConfig {
|
|||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers("/api/auth/**").permitAll()
|
.requestMatchers("/api/auth/**").permitAll()
|
||||||
|
.requestMatchers("/api/notifications/**").permitAll()
|
||||||
.requestMatchers("/api/**").permitAll()
|
.requestMatchers("/api/**").permitAll()
|
||||||
.anyRequest().permitAll()
|
.anyRequest().permitAll()
|
||||||
);
|
);
|
||||||
@@ -46,7 +47,7 @@ public class SecurityConfig {
|
|||||||
configuration.setAllowedOriginPatterns(List.of("*"));
|
configuration.setAllowedOriginPatterns(List.of("*"));
|
||||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
|
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
|
||||||
configuration.setAllowedHeaders(List.of("*"));
|
configuration.setAllowedHeaders(List.of("*"));
|
||||||
configuration.setAllowCredentials(true);
|
configuration.setAllowCredentials(false);
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
source.registerCorsConfiguration("/**", configuration);
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
return source;
|
return source;
|
||||||
|
|||||||
@@ -15,14 +15,31 @@ public class SystemNotificationController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SystemNotificationService notificationService;
|
private SystemNotificationService notificationService;
|
||||||
|
|
||||||
|
@GetMapping("/test")
|
||||||
|
public String testReachability() {
|
||||||
|
return "Notification API is Reachable and whitelisted!";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public List<SystemNotification> getUnread() {
|
public List<SystemNotification> 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")
|
@GetMapping("/all")
|
||||||
public List<SystemNotification> getAll() {
|
public List<SystemNotification> 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}")
|
@PostMapping("/mark-read/{id}")
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ public class SystemNotification {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String type; // FEEDBACK, PURCHASE, PRODUCT, COUPON
|
private String type; // FEEDBACK, PURCHASE, PRODUCT, COUPON
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(name = "is_read", nullable = false)
|
||||||
private boolean isRead = false;
|
private Boolean isReadStatus = false;
|
||||||
|
|
||||||
@Column(nullable = true)
|
@Column(nullable = true)
|
||||||
private String link; // URL to navigate to
|
private String link; // URL to navigate to
|
||||||
@@ -57,8 +57,8 @@ public class SystemNotification {
|
|||||||
public String getType() { return type; }
|
public String getType() { return type; }
|
||||||
public void setType(String type) { this.type = type; }
|
public void setType(String type) { this.type = type; }
|
||||||
|
|
||||||
public boolean isRead() { return isRead; }
|
public Boolean isReadStatus() { return isReadStatus; }
|
||||||
public void setRead(boolean read) { isRead = read; }
|
public void setReadStatus(Boolean readStatus) { isReadStatus = readStatus; }
|
||||||
|
|
||||||
public String getLink() { return link; }
|
public String getLink() { return link; }
|
||||||
public void setLink(String link) { this.link = link; }
|
public void setLink(String link) { this.link = link; }
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ package com.rit.canteen.sales.repository;
|
|||||||
|
|
||||||
import com.rit.canteen.sales.model.SystemNotification;
|
import com.rit.canteen.sales.model.SystemNotification;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface SystemNotificationRepository extends JpaRepository<SystemNotification, Long> {
|
public interface SystemNotificationRepository extends JpaRepository<SystemNotification, Long> {
|
||||||
List<SystemNotification> findByIsReadFalseOrderByCreatedAtDesc();
|
@Query(value = "SELECT * FROM system_notifications WHERE is_read = false ORDER BY created_at DESC", nativeQuery = true)
|
||||||
|
List<SystemNotification> findByReadStatusFalse();
|
||||||
|
|
||||||
List<SystemNotification> findAllByOrderByCreatedAtDesc();
|
List<SystemNotification> findAllByOrderByCreatedAtDesc();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class SystemNotificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<SystemNotification> getUnreadNotifications() {
|
public List<SystemNotification> getUnreadNotifications() {
|
||||||
return notificationRepository.findByIsReadFalseOrderByCreatedAtDesc();
|
return notificationRepository.findByReadStatusFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SystemNotification> getAllNotifications() {
|
public List<SystemNotification> getAllNotifications() {
|
||||||
@@ -38,16 +38,16 @@ public class SystemNotificationService {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public void markAsRead(Long id) {
|
public void markAsRead(Long id) {
|
||||||
notificationRepository.findById(id).ifPresent(n -> {
|
notificationRepository.findById(id).ifPresent(n -> {
|
||||||
n.setRead(true);
|
n.setReadStatus(Boolean.TRUE);
|
||||||
notificationRepository.save(n);
|
notificationRepository.save(n);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void markAllAsRead() {
|
public void markAllAsRead() {
|
||||||
List<SystemNotification> unread = notificationRepository.findByIsReadFalseOrderByCreatedAtDesc();
|
List<SystemNotification> unread = notificationRepository.findByReadStatusFalse();
|
||||||
for (SystemNotification n : unread) {
|
for (SystemNotification n : unread) {
|
||||||
n.setRead(true);
|
n.setReadStatus(Boolean.TRUE);
|
||||||
}
|
}
|
||||||
notificationRepository.saveAll(unread);
|
notificationRepository.saveAll(unread);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import RitzCirculation from './pages/RitzCirculation.tsx';
|
|||||||
import ManageWallets from './pages/ManageWallets.tsx';
|
import ManageWallets from './pages/ManageWallets.tsx';
|
||||||
import ManageCoupons from './pages/ManageCoupons.tsx';
|
import ManageCoupons from './pages/ManageCoupons.tsx';
|
||||||
import Settings from './pages/Settings.tsx';
|
import Settings from './pages/Settings.tsx';
|
||||||
|
import NotFound from './pages/NotFound.tsx';
|
||||||
|
|
||||||
|
|
||||||
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||||
@@ -111,6 +112,7 @@ function App() {
|
|||||||
{/* Settings */}
|
{/* Settings */}
|
||||||
<Route path="settings" element={<Settings />} />
|
<Route path="settings" element={<Settings />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|||||||
99
frontend/src/pages/NotFound.tsx
Normal file
99
frontend/src/pages/NotFound.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Home, ArrowLeft, Search, HelpCircle } from 'lucide-react';
|
||||||
|
|
||||||
|
const NotFound = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#f8fafc] flex items-center justify-center p-6 font-sans selection:bg-[#0f4475]/10">
|
||||||
|
<div className="max-w-4xl w-full">
|
||||||
|
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||||
|
{/* Visual Section */}
|
||||||
|
<div className="relative">
|
||||||
|
{/* Background Decorative Circles */}
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-80 h-80 bg-[#0f4475]/5 rounded-full blur-3xl" />
|
||||||
|
<div className="absolute top-1/4 left-1/4 w-32 h-32 bg-indigo-500/10 rounded-full blur-2xl" />
|
||||||
|
|
||||||
|
{/* 404 Main Text */}
|
||||||
|
<div className="relative">
|
||||||
|
<h1 className="text-[180px] font-black leading-none tracking-tighter text-[#0f4475] opacity-20 select-none">
|
||||||
|
404
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Detailed Icon Stack */}
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center">
|
||||||
|
<div className="p-8 bg-white rounded-[40px] shadow-2xl border border-white/50 backdrop-blur-xl relative z-10">
|
||||||
|
<Search size={80} className="text-[#0f4475]" strokeWidth={1.5} />
|
||||||
|
|
||||||
|
{/* Small Floating Icons */}
|
||||||
|
<div className="absolute -top-4 -right-4 p-3 bg-white rounded-2xl shadow-lg border border-white">
|
||||||
|
<HelpCircle size={24} className="text-indigo-500" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute -bottom-6 -left-6 p-4 bg-[#0f4475] rounded-3xl shadow-xl border-4 border-white text-white">
|
||||||
|
<span className="text-sm font-black tracking-widest uppercase">Lost?</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Section */}
|
||||||
|
<div className="text-center lg:text-left">
|
||||||
|
<div className="inline-flex items-center gap-2 px-3 py-1 bg-red-50 text-red-600 rounded-full text-[10px] font-black uppercase tracking-[0.2em] mb-6 border border-red-100">
|
||||||
|
<span className="relative flex h-2 w-2">
|
||||||
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
|
||||||
|
</span>
|
||||||
|
Error Code: 404
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-4xl lg:text-5xl font-black text-[#1e293b] mb-6 leading-tight">
|
||||||
|
Well, this is <span className="text-transparent bg-clip-text bg-gradient-to-r from-[#0f4475] to-indigo-600">awkward.</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-[#64748b] text-lg mb-10 leading-relaxed font-medium">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row items-center gap-4 justify-center lg:justify-start">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/dashboard')}
|
||||||
|
className="group px-8 py-4 bg-[#0f4475] text-white rounded-2xl font-bold flex items-center gap-3 shadow-xl shadow-[#0f4475]/20 hover:bg-[#1a5a92] transition-all hover:-translate-y-1 active:translate-y-0"
|
||||||
|
>
|
||||||
|
<Home size={18} className="group-hover:scale-110 transition-transform" />
|
||||||
|
Return to Safety
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
className="px-8 py-4 bg-white text-[#64748b] border border-[#e2e8f0] rounded-2xl font-bold flex items-center gap-3 hover:bg-gray-50 transition-all hover:text-[#1e293b]"
|
||||||
|
>
|
||||||
|
<ArrowLeft size={18} />
|
||||||
|
Go Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Links Section */}
|
||||||
|
<div className="mt-16 pt-8 border-t border-[#e2e8f0]">
|
||||||
|
<p className="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-4">Common Sectors</p>
|
||||||
|
<div className="flex flex-wrap gap-x-6 gap-y-3 justify-center lg:justify-start">
|
||||||
|
{['Orders', 'Inventory', 'Reports', 'Settings'].map((item) => (
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
onClick={() => navigate(`/${item.toLowerCase()}`)}
|
||||||
|
className="text-xs font-bold text-[#64748b] hover:text-[#0f4475] transition-colors flex items-center gap-1 group"
|
||||||
|
>
|
||||||
|
<div className="w-1 h-1 rounded-full bg-[#e2e8f0] group-hover:bg-[#0f4475] transition-colors" />
|
||||||
|
{item}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotFound;
|
||||||
@@ -18,6 +18,7 @@ import StallDetailScreen from './pages/StallDetailScreen';
|
|||||||
import WalletScreen from './pages/WalletScreen';
|
import WalletScreen from './pages/WalletScreen';
|
||||||
import TopUpScreen from './pages/TopUpScreen';
|
import TopUpScreen from './pages/TopUpScreen';
|
||||||
import PreferencesScreen from './pages/PreferencesScreen';
|
import PreferencesScreen from './pages/PreferencesScreen';
|
||||||
|
import NotFoundScreen from './pages/NotFoundScreen';
|
||||||
import StockAlert from './components/StockAlert';
|
import StockAlert from './components/StockAlert';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
@@ -98,6 +99,7 @@ function App() {
|
|||||||
<PreferencesScreen />
|
<PreferencesScreen />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
} />
|
} />
|
||||||
|
<Route path="*" element={<NotFoundScreen />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
</CartProvider>
|
</CartProvider>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface AuthContextType {
|
|||||||
logout: () => void;
|
logout: () => void;
|
||||||
changePin: (currentPin: string, newPin: string) => Promise<{ success: boolean; message: string }>;
|
changePin: (currentPin: string, newPin: string) => Promise<{ success: boolean; message: string }>;
|
||||||
updateProfile: (name: string, mobileNumber: string) => Promise<{ success: boolean; message: string }>;
|
updateProfile: (name: string, mobileNumber: string) => Promise<{ success: boolean; message: string }>;
|
||||||
|
refreshUser: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
const AuthContext = createContext<AuthContextType | undefined>(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(() => {
|
useEffect(() => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
const checkSuspensionStatus = async () => {
|
const syncUserStatus = async () => {
|
||||||
try {
|
await refreshUser();
|
||||||
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 intervalId = setInterval(checkSuspensionStatus, 30000); // Check every 30 seconds
|
const intervalId = setInterval(syncUserStatus, 30000); // Sync every 30 seconds
|
||||||
return () => clearInterval(intervalId);
|
return () => clearInterval(intervalId);
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ user, isLoading, checkUserExists, login, register, logout, changePin, updateProfile }}>
|
<AuthContext.Provider value={{ user, isLoading, checkUserExists, login, register, logout, changePin, updateProfile, refreshUser }}>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,19 +1,58 @@
|
|||||||
.welcome-section {
|
.welcome-section {
|
||||||
padding: 24px 16px 8px;
|
padding: 24px 20px 16px;
|
||||||
background-color: var(--surface);
|
}
|
||||||
|
|
||||||
|
.greeting-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome-message {
|
.welcome-message {
|
||||||
font-size: 1.8rem;
|
font-size: 26px;
|
||||||
font-weight: 800;
|
font-weight: 850;
|
||||||
color: var(--text-dark);
|
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 {
|
.welcome-subtitle {
|
||||||
font-size: 0.95rem;
|
font-size: 15px;
|
||||||
color: var(--text-mid);
|
color: var(--text-mid);
|
||||||
margin-top: 4px;
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar-container {
|
.search-bar-container {
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import FeedbackModal from '../components/FeedbackModal';
|
|||||||
import { useFood } from '../contexts/FoodContext';
|
import { useFood } from '../contexts/FoodContext';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import walletIcon from '../assets/front.png';
|
||||||
import './HomeScreen.css';
|
import './HomeScreen.css';
|
||||||
|
|
||||||
const HomeScreen: React.FC = () => {
|
const HomeScreen: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { categories, stalls, foodItems, isLoading, error, refreshData } = useFood();
|
const { categories, stalls, foodItems, isLoading, error, refreshData } = useFood();
|
||||||
const { user } = useAuth();
|
const { user, refreshUser } = useAuth();
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
// Feedback state
|
// Feedback state
|
||||||
@@ -25,6 +26,7 @@ const HomeScreen: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user?.id) {
|
if (user?.id) {
|
||||||
checkForUnratedOrder();
|
checkForUnratedOrder();
|
||||||
|
refreshUser();
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
@@ -123,7 +125,15 @@ const HomeScreen: React.FC = () => {
|
|||||||
|
|
||||||
<main className="safe-area-bottom">
|
<main className="safe-area-bottom">
|
||||||
<div className="welcome-section">
|
<div className="welcome-section">
|
||||||
<h1 className="welcome-message">Hello {user?.name || 'Guest'}!</h1>
|
<div className="greeting-row">
|
||||||
|
<h1 className="welcome-message">Hello {user?.name || 'Guest'}!</h1>
|
||||||
|
{user && (
|
||||||
|
<div className="wallet-badge" onClick={() => navigate('/wallet')}>
|
||||||
|
<img src={walletIcon} alt="Wallet" className="wallet-icon-img" />
|
||||||
|
<span className="wallet-balance-text">R {user.ritzTokenBalance || 0}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<p className="welcome-subtitle">What would you like to eat today?</p>
|
<p className="welcome-subtitle">What would you like to eat today?</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user