Multiple user support added
This commit is contained in:
@@ -27,6 +27,7 @@ import Ritz from './pages/Ritz.tsx';
|
||||
import RitzCirculation from './pages/RitzCirculation.tsx';
|
||||
import ManageWallets from './pages/ManageWallets.tsx';
|
||||
import ManageCoupons from './pages/ManageCoupons.tsx';
|
||||
import Settings from './pages/Settings.tsx';
|
||||
|
||||
|
||||
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||
@@ -106,6 +107,9 @@ function App() {
|
||||
<Route path="stores/managers" element={<Managers />} />
|
||||
<Route path="stores/staffs" element={<Staff />} />
|
||||
<Route path="stores/stalls" element={<Stalls />} />
|
||||
|
||||
{/* Settings */}
|
||||
<Route path="settings" element={<Settings />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Bell, Search, Settings, HelpCircle, Calendar } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const Header = () => {
|
||||
const currentDate = new Date().toLocaleDateString('en-US', {
|
||||
@@ -33,9 +34,9 @@ const Header = () => {
|
||||
<span className="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full border-2 border-white"></span>
|
||||
</button>
|
||||
|
||||
<button className="p-2 text-[#64748b] hover:text-[#1e293b] hover:bg-gray-100 rounded-lg transition-all">
|
||||
<Link to="/settings" className="p-2 text-[#64748b] hover:text-[#231651] hover:bg-indigo-50 rounded-lg transition-all">
|
||||
<Settings size={20} />
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
<button className="p-2 text-[#64748b] hover:text-[#1e293b] hover:bg-gray-100 rounded-lg transition-all">
|
||||
<HelpCircle size={20} />
|
||||
|
||||
@@ -164,6 +164,7 @@ const Sidebar = () => {
|
||||
sessionStorage.removeItem('isLoggedIn');
|
||||
sessionStorage.removeItem('userRole');
|
||||
sessionStorage.removeItem('userPermissions');
|
||||
localStorage.removeItem('systemUser');
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
|
||||
@@ -151,7 +151,12 @@ const Dashboard = () => {
|
||||
</button>
|
||||
<h2 className="text-xs font-black text-[#0f4475] uppercase tracking-widest">Dashboard</h2>
|
||||
</div>
|
||||
<h1 className="text-2xl font-black text-slate-800">Good morning</h1>
|
||||
<h1 className="text-2xl font-black text-slate-800">
|
||||
Good morning, {(() => {
|
||||
const saved = localStorage.getItem('systemUser');
|
||||
return saved ? JSON.parse(saved).name : 'Partner';
|
||||
})()}
|
||||
</h1>
|
||||
<p className="text-[10px] text-slate-400 font-medium flex items-center gap-1.5">
|
||||
<span className="p-0.5 bg-slate-200 rounded text-slate-500">i</span>
|
||||
The default time settings for the merchant view are from 12:00 AM to 11:59 PM.
|
||||
@@ -175,14 +180,6 @@ const Dashboard = () => {
|
||||
View Full Report
|
||||
</button>
|
||||
|
||||
<div className="relative group">
|
||||
<select title="Filter by store type or category" className="appearance-none bg-white border border-slate-200 rounded-xl px-4 py-2 pr-10 text-xs font-bold text-slate-600 outline-none focus:border-[#0f4475] transition-all cursor-pointer shadow-sm">
|
||||
<option>Store Type: All</option>
|
||||
</select>
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
|
||||
<ChevronRight size={14} className="rotate-90 text-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ const Login = () => {
|
||||
sessionStorage.removeItem('isLoggedIn');
|
||||
sessionStorage.removeItem('userRole');
|
||||
sessionStorage.removeItem('userPermissions');
|
||||
localStorage.removeItem('systemUser');
|
||||
}, []);
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
@@ -51,6 +52,10 @@ const Login = () => {
|
||||
sessionStorage.setItem('isLoggedIn', 'true');
|
||||
sessionStorage.setItem('userRole', user.role.toLowerCase());
|
||||
sessionStorage.setItem('userPermissions', JSON.stringify(user.permissions || []));
|
||||
|
||||
// Persist user profile for personalized greetings and settings
|
||||
localStorage.setItem('systemUser', JSON.stringify(user));
|
||||
|
||||
navigate('/store-dashboard');
|
||||
} else {
|
||||
const error = await response.text();
|
||||
|
||||
399
frontend/src/pages/Settings.tsx
Normal file
399
frontend/src/pages/Settings.tsx
Normal file
@@ -0,0 +1,399 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
User,
|
||||
Mail,
|
||||
Lock,
|
||||
ShieldCheck,
|
||||
Save,
|
||||
CheckCircle2,
|
||||
AlertCircle,
|
||||
Shield,
|
||||
Key,
|
||||
Users,
|
||||
UserPlus,
|
||||
ArrowRight,
|
||||
ShieldAlert
|
||||
} from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
const Settings = () => {
|
||||
const [activeTab, setActiveTab] = useState<'profile' | 'team'>('profile');
|
||||
const [currentUser, setCurrentUser] = useState(() => {
|
||||
const saved = localStorage.getItem('systemUser');
|
||||
return saved ? JSON.parse(saved) : null;
|
||||
});
|
||||
|
||||
// Profile Form State
|
||||
const [profileData, setProfileData] = useState({
|
||||
name: currentUser?.name || '',
|
||||
email: currentUser?.email || '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
// Team State
|
||||
const [admins, setAdmins] = useState<any[]>([]);
|
||||
const [newAdmin, setNewAdmin] = useState({ name: '', email: '', password: '' });
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
|
||||
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'team') {
|
||||
fetchAdmins();
|
||||
}
|
||||
}, [activeTab]);
|
||||
|
||||
const fetchAdmins = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/system/admins');
|
||||
const data = await response.json();
|
||||
setAdmins(data);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch admins');
|
||||
}
|
||||
};
|
||||
|
||||
const handleProfileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setProfileData(prev => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
|
||||
const handleUpdateProfile = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (profileData.password && profileData.password !== profileData.confirmPassword) {
|
||||
setStatus('error');
|
||||
setMessage('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus('loading');
|
||||
try {
|
||||
const response = await fetch('/api/system/update-master', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
id: currentUser?.id,
|
||||
name: profileData.name,
|
||||
email: profileData.email,
|
||||
password: profileData.password || null
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setStatus('success');
|
||||
setMessage(data.message);
|
||||
const updatedUser = { ...currentUser, name: profileData.name, email: profileData.email };
|
||||
localStorage.setItem('systemUser', JSON.stringify(updatedUser));
|
||||
setCurrentUser(updatedUser);
|
||||
setTimeout(() => setStatus('idle'), 3000);
|
||||
} else {
|
||||
setStatus('error');
|
||||
setMessage(data.message || 'Update failed');
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus('error');
|
||||
setMessage('Connection error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddAdmin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setStatus('loading');
|
||||
try {
|
||||
const response = await fetch('/api/system/admins', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(newAdmin)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setStatus('success');
|
||||
setMessage('New administrator added');
|
||||
setNewAdmin({ name: '', email: '', password: '' });
|
||||
setShowAddModal(false);
|
||||
fetchAdmins();
|
||||
setTimeout(() => setStatus('idle'), 3000);
|
||||
} else {
|
||||
setStatus('error');
|
||||
setMessage('Failed to add administrator');
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus('error');
|
||||
setMessage('Connection error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-6xl mx-auto">
|
||||
<div className="mb-8 flex flex-col md:flex-row md:items-end md:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-black text-[#231651] mb-2">Settings & Security</h1>
|
||||
<p className="text-[#64748b]">Manage your account and system access control</p>
|
||||
</div>
|
||||
|
||||
<div className="flex bg-gray-100 p-1.5 rounded-2xl w-fit">
|
||||
<button
|
||||
onClick={() => setActiveTab('profile')}
|
||||
className={`px-6 py-2.5 rounded-xl font-bold text-sm transition-all flex items-center gap-2 ${activeTab === 'profile' ? 'bg-white text-[#231651] shadow-sm' : 'text-[#64748b] hover:text-[#231651]'}`}
|
||||
>
|
||||
<User size={18} />
|
||||
My Account
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('team')}
|
||||
className={`px-6 py-2.5 rounded-xl font-bold text-sm transition-all flex items-center gap-2 ${activeTab === 'team' ? 'bg-white text-[#231651] shadow-sm' : 'text-[#64748b] hover:text-[#231651]'}`}
|
||||
>
|
||||
<Users size={18} />
|
||||
Team Management
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
{activeTab === 'profile' ? (
|
||||
<motion.div
|
||||
key="profile"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="grid grid-cols-1 lg:grid-cols-3 gap-8"
|
||||
>
|
||||
{/* Left Card: Summary */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="bg-white rounded-3xl p-6 border border-[#e2e8f0] shadow-sm text-center">
|
||||
<div className="w-24 h-24 bg-gradient-to-br from-[#231651] to-[#6366f1] rounded-full mx-auto mb-4 flex items-center justify-center text-white shadow-xl shadow-indigo-100">
|
||||
<User size={40} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-[#1e293b]">{currentUser?.name}</h3>
|
||||
<div className="inline-flex items-center gap-1.5 px-3 py-1 bg-indigo-50 text-indigo-600 rounded-full text-xs font-bold mt-2 uppercase tracking-wider">
|
||||
<Shield size={12} />
|
||||
{currentUser?.role}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-gray-100 text-left">
|
||||
<div className="flex items-center gap-3 text-sm text-[#64748b] mb-4">
|
||||
<Mail size={16} className="text-indigo-500" />
|
||||
<span className="truncate">{currentUser?.email}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm text-[#64748b]">
|
||||
<ShieldCheck size={16} className="text-green-500" />
|
||||
<span>Full System Access</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Card: Form */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-white rounded-3xl p-8 border border-[#e2e8f0] shadow-sm">
|
||||
<form onSubmit={handleUpdateProfile} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-[#64748b] mb-2 uppercase tracking-wide">Display Name</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={profileData.name}
|
||||
onChange={handleProfileChange}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-indigo-500 focus:bg-white rounded-xl py-3 pl-12 pr-4 outline-none transition-all font-medium"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-[#64748b] mb-2 uppercase tracking-wide">Email Address</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={18} />
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={profileData.email}
|
||||
onChange={handleProfileChange}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-indigo-500 focus:bg-white rounded-xl py-3 pl-12 pr-4 outline-none transition-all font-medium"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-px bg-gray-100 my-4" />
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-[#64748b] mb-2 uppercase tracking-wide">New Password</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={18} />
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={profileData.password}
|
||||
onChange={handleProfileChange}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-indigo-500 focus:bg-white rounded-xl py-3 pl-12 pr-4 outline-none transition-all font-medium"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-[#64748b] mb-2 uppercase tracking-wide">Confirm New Password</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={18} />
|
||||
<input
|
||||
type="password"
|
||||
name="confirmPassword"
|
||||
value={profileData.confirmPassword}
|
||||
onChange={handleProfileChange}
|
||||
className="w-full bg-gray-50 border border-transparent focus:border-indigo-500 focus:bg-white rounded-xl py-3 pl-12 pr-4 outline-none transition-all font-medium"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-6 border-t border-gray-50">
|
||||
<div className="flex-1">
|
||||
<AnimatePresence mode="wait">
|
||||
{status === 'success' && (
|
||||
<motion.div initial={{ opacity: 0, x: -10 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0 }} className="flex items-center gap-2 text-green-600 font-bold text-sm">
|
||||
<CheckCircle2 size={16} /> {message}
|
||||
</motion.div>
|
||||
)}
|
||||
{status === 'error' && (
|
||||
<motion.div initial={{ opacity: 0, x: -10 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0 }} className="flex items-center gap-2 text-red-600 font-bold text-sm">
|
||||
<AlertCircle size={16} /> {message}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<button type="submit" disabled={status === 'loading'} className="bg-[#231651] text-white px-8 py-3 rounded-xl font-bold flex items-center gap-2 hover:bg-opacity-90 transition-all disabled:opacity-50">
|
||||
<Save size={18} /> Save Profile
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="team"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="flex justify-between items-center bg-white p-6 rounded-3xl border border-[#e2e8f0] shadow-sm">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-[#231651]">Administrator Team</h3>
|
||||
<p className="text-sm text-[#64748b]">Found {admins.length} active system administrators</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAddModal(true)}
|
||||
className="bg-gradient-to-r from-[#231651] to-[#6366f1] text-white px-6 py-3 rounded-xl font-bold flex items-center gap-2 shadow-lg shadow-indigo-100 hover:scale-[1.02] transition-all"
|
||||
>
|
||||
<UserPlus size={18} />
|
||||
Add New Admin
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{admins.map((admin) => (
|
||||
<div key={admin.id} className="bg-white p-6 rounded-3xl border border-[#e2e8f0] shadow-sm hover:shadow-md transition-all group">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="w-12 h-12 bg-gray-100 rounded-2xl flex items-center justify-center text-[#231651] group-hover:bg-[#231651] group-hover:text-white transition-colors">
|
||||
<User size={24} />
|
||||
</div>
|
||||
{admin.id === currentUser?.id && (
|
||||
<span className="text-[10px] bg-green-100 text-green-700 font-black px-2 py-1 rounded-md uppercase">You</span>
|
||||
)}
|
||||
</div>
|
||||
<h4 className="font-bold text-[#1e293b] truncate border-b border-gray-50 pb-2 mb-3">{admin.name}</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-xs text-[#64748b]">
|
||||
<Mail size={14} className="text-indigo-400" />
|
||||
<span className="truncate">{admin.email}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-[#64748b]">
|
||||
<ShieldCheck size={14} className="text-green-500" />
|
||||
<span>{admin.role} ACCESS</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Add Admin Modal */}
|
||||
<AnimatePresence>
|
||||
{showAddModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} onClick={() => setShowAddModal(false)} className="absolute inset-0 bg-[#231651]/20 backdrop-blur-sm" />
|
||||
<motion.div initial={{ scale: 0.9, opacity: 0 }} animate={{ scale: 1, opacity: 1 }} exit={{ scale: 0.9, opacity: 0 }} className="bg-white w-full max-w-md rounded-[32px] overflow-hidden shadow-2xl relative z-10">
|
||||
<div className="bg-[#231651] p-6 text-white text-center">
|
||||
<div className="w-16 h-16 bg-white/10 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<UserPlus size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold">New Administrator</h3>
|
||||
<p className="text-sm text-indigo-200">Grant full system access to a team member</p>
|
||||
</div>
|
||||
<form onSubmit={handleAddAdmin} className="p-8 space-y-5">
|
||||
<div>
|
||||
<label className="block text-xs font-black text-[#64748b] mb-2 uppercase tracking-widest">Full Name</label>
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
value={newAdmin.name}
|
||||
onChange={(e) => setNewAdmin({...newAdmin, name: e.target.value})}
|
||||
className="w-full bg-gray-50 border border-gray-100 rounded-xl py-3 px-4 outline-none focus:border-indigo-500 transition-all font-medium"
|
||||
placeholder="e.g. John Doe"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-black text-[#64748b] mb-2 uppercase tracking-widest">Login Email</label>
|
||||
<input
|
||||
required
|
||||
type="email"
|
||||
value={newAdmin.email}
|
||||
onChange={(e) => setNewAdmin({...newAdmin, email: e.target.value})}
|
||||
className="w-full bg-gray-50 border border-gray-100 rounded-xl py-3 px-4 outline-none focus:border-indigo-500 transition-all font-medium"
|
||||
placeholder="john@example.com"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-black text-[#64748b] mb-2 uppercase tracking-widest">Initial Password</label>
|
||||
<input
|
||||
required
|
||||
type="password"
|
||||
value={newAdmin.password}
|
||||
onChange={(e) => setNewAdmin({...newAdmin, password: e.target.value})}
|
||||
className="w-full bg-gray-50 border border-gray-100 rounded-xl py-3 px-4 outline-none focus:border-indigo-500 transition-all font-medium"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-100 p-4 rounded-2xl flex gap-3 text-amber-700">
|
||||
<ShieldAlert size={20} className="shrink-0" />
|
||||
<p className="text-[10px] leading-relaxed font-bold">
|
||||
CRITICAL: This user will have full MASTER privileges. They can manage sales, tokens, and other administrators.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-2">
|
||||
<button type="button" onClick={() => setShowAddModal(false)} className="flex-1 py-3 rounded-xl font-bold text-gray-500 hover:bg-gray-50 transition-all">Cancel</button>
|
||||
<button type="submit" disabled={status === 'loading'} className="flex-1 bg-[#231651] text-white py-3 rounded-xl font-bold flex items-center justify-center gap-2 hover:bg-indigo-700 transition-all disabled:opacity-50">
|
||||
{status === 'loading' ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : <>Create User <ArrowRight size={16} /></>}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
@@ -167,39 +167,16 @@ const StoreDashboard = () => {
|
||||
</button>
|
||||
<h2 className="text-xs font-black text-[#0f4475] uppercase tracking-widest">Store Dashboard</h2>
|
||||
</div>
|
||||
<h1 className="text-2xl font-black text-slate-800">Good morning, RIT Canteen</h1>
|
||||
<h1 className="text-2xl font-black text-slate-800">
|
||||
Good morning, {(() => {
|
||||
const saved = localStorage.getItem('systemUser');
|
||||
return saved ? JSON.parse(saved).name : 'Partner';
|
||||
})()}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative group">
|
||||
<select title="Switch established store location" className="appearance-none bg-white border border-slate-200 rounded-xl px-4 py-2 pr-10 text-xs font-bold text-slate-600 outline-none focus:border-[#0f4475] transition-all cursor-pointer shadow-sm">
|
||||
<option>RIT Canteen</option>
|
||||
</select>
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
|
||||
<ChevronRight size={14} className="rotate-90 text-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative group">
|
||||
<select title="Toggle between available display views" className="appearance-none bg-white border border-slate-200 rounded-xl px-4 py-2 pr-10 text-xs font-bold text-slate-600 outline-none focus:border-[#0f4475] transition-all cursor-pointer shadow-sm">
|
||||
<option>All</option>
|
||||
</select>
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
|
||||
<ChevronRight size={14} className="rotate-90 text-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button title="Synchronize latest live data from backend" className="p-2 bg-white border border-slate-200 rounded-xl text-slate-400 hover:text-slate-600 transition-all shadow-sm">
|
||||
<RotateCcw size={18} />
|
||||
</button>
|
||||
<button title="Mark this store dashboard as favorite for quick access" className="p-2 bg-white border border-slate-200 rounded-xl text-amber-500 hover:scale-110 transition-all shadow-sm">
|
||||
<Star size={18} fill="#f59e0b" />
|
||||
</button>
|
||||
</div>
|
||||
<div title="Authenticated User: Abiram" className="w-8 h-8 rounded-full bg-[#0f4475] text-white flex items-center justify-center font-bold text-sm shadow-md cursor-pointer hover:ring-2 ring-[#0f4475]/20 transition-all">
|
||||
A
|
||||
</div>
|
||||
{/* Action buttons or profile placeholder can go here if needed later */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user