🚀 Fix: Login case-sensitivity & Restore Super Admin AI metrics access

This commit is contained in:
Hamza-Ayed
2026-04-22 22:37:48 +03:00
parent f72c13f29a
commit 944c82730d
6 changed files with 20 additions and 8 deletions

View File

@@ -100,8 +100,9 @@ export class AuthService {
* تسجيل دخول * تسجيل دخول
*/ */
async login(dto: LoginDto) { async login(dto: LoginDto) {
const normalizedEmail = dto.email.trim().toLowerCase();
const user = await this.dataSource.getRepository(User).findOne({ const user = await this.dataSource.getRepository(User).findOne({
where: { email: dto.email, is_active: true }, where: { email: normalizedEmail, is_active: true },
select: ['id', 'email', 'password_hash', 'tenant_id', 'role', 'name'], select: ['id', 'email', 'password_hash', 'tenant_id', 'role', 'name'],
}); });

View File

@@ -21,8 +21,9 @@ export class UsersService {
* إضافة مستخدم لمكتب محاسبة * إضافة مستخدم لمكتب محاسبة
*/ */
async create(tenantId: string, dto: any): Promise<User> { async create(tenantId: string, dto: any): Promise<User> {
const normalizedEmail = dto.email?.trim().toLowerCase();
const existing = await this.userRepository.findOne({ const existing = await this.userRepository.findOne({
where: { email: dto.email, tenant_id: tenantId }, where: { email: normalizedEmail, tenant_id: tenantId },
}); });
if (existing) { if (existing) {
@@ -33,6 +34,7 @@ export class UsersService {
const user = this.userRepository.create({ const user = this.userRepository.create({
...dto, ...dto,
email: normalizedEmail,
password_hash: passwordHash, password_hash: passwordHash,
tenant_id: tenantId, tenant_id: tenantId,
} as Partial<User>); } as Partial<User>);
@@ -85,6 +87,10 @@ export class UsersService {
delete dto.password; delete dto.password;
} }
if (dto.email) {
dto.email = dto.email.trim().toLowerCase();
}
Object.assign(user, dto); Object.assign(user, dto);
try { try {

View File

@@ -17,19 +17,22 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '../../store/authStore';
const getMenuItems = (role: string | undefined) => [ const getMenuItems = (role: string | undefined) => {
const isAdmin = role && ['admin', 'super_admin'].includes(role.toLowerCase());
return [
{ icon: LayoutDashboard, label: 'الرئيسية', path: '/dashboard' }, { icon: LayoutDashboard, label: 'الرئيسية', path: '/dashboard' },
...(role === 'admin' ? [ ...(isAdmin ? [
{ icon: Crown, label: 'المركز الضريبي الموحد', path: '/elite-dashboard' }, { icon: Crown, label: 'المركز الضريبي الموحد', path: '/elite-dashboard' },
{ icon: AlertTriangle, label: 'مراقبة المخاطر', path: '/risk-monitor' } { icon: AlertTriangle, label: 'مراقبة المخاطر', path: '/risk-monitor' }
] : []), ] : []),
{ icon: FileText, label: 'الفواتير', path: '/invoices' }, { icon: FileText, label: 'الفواتير', path: '/invoices' },
{ icon: Building2, label: 'الشركات', path: '/companies' }, { icon: Building2, label: 'الشركات', path: '/companies' },
...(role === 'admin' ? [ ...(isAdmin ? [
{ icon: Users, label: 'الموظفون', path: '/staff' } { icon: Users, label: 'الموظفون', path: '/staff' }
] : []), ] : []),
{ icon: Settings, label: 'الإعدادات', path: '/settings' }, { icon: Settings, label: 'الإعدادات', path: '/settings' },
]; ];
};
export const Sidebar = () => { export const Sidebar = () => {
const navigate = useNavigate(); const navigate = useNavigate();

View File

@@ -94,6 +94,7 @@ const Cpu = ({ className }: { className?: string }) => (
export const MultiEntityDashboard = () => { export const MultiEntityDashboard = () => {
const user = useAuthStore((state) => state.user); const user = useAuthStore((state) => state.user);
const isAdmin = user?.role && ['admin', 'super_admin'].includes(user.role.toLowerCase());
const [companies, setCompanies] = useState<CompanyStats[]>([]); const [companies, setCompanies] = useState<CompanyStats[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -190,7 +191,7 @@ export const MultiEntityDashboard = () => {
className="card-premium p-6 relative overflow-hidden group" className="card-premium p-6 relative overflow-hidden group"
> >
{/* AI Usage Badge */} {/* AI Usage Badge */}
{user?.role === 'admin' && ( {isAdmin && (
<div className="absolute top-4 left-4"> <div className="absolute top-4 left-4">
<div className="flex items-center gap-1.5 bg-indigo-500/10 backdrop-blur-md px-3 py-1.5 rounded-lg text-xs font-black text-indigo-400 border border-indigo-500/20 shadow-lg shadow-indigo-500/10"> <div className="flex items-center gap-1.5 bg-indigo-500/10 backdrop-blur-md px-3 py-1.5 rounded-lg text-xs font-black text-indigo-400 border border-indigo-500/20 shadow-lg shadow-indigo-500/10">
<Cpu className="w-3.5 h-3.5" /> <Cpu className="w-3.5 h-3.5" />
@@ -239,7 +240,7 @@ export const MultiEntityDashboard = () => {
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<p className="text-sm text-slate-600 dark:text-slate-300">{company.totalInvoices} فاتورة</p> <p className="text-sm text-slate-600 dark:text-slate-300">{company.totalInvoices} فاتورة</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{user?.role === 'admin' && company.aiStats?.totalCost > 0 && ( {isAdmin && company.aiStats?.totalCost > 0 && (
<span className="text-[10px] font-black text-indigo-400 bg-indigo-500/10 px-2 py-0.5 rounded border border-indigo-500/20"> <span className="text-[10px] font-black text-indigo-400 bg-indigo-500/10 px-2 py-0.5 rounded border border-indigo-500/20">
${company.aiStats.totalCost.toFixed(3)} ${company.aiStats.totalCost.toFixed(3)}
</span> </span>

View File

@@ -12,6 +12,7 @@ import apiClient from '../../api/client';
export const StaffPage = () => { export const StaffPage = () => {
const user = useAuthStore((state) => state.user); const user = useAuthStore((state) => state.user);
const isAdmin = user?.role && ['admin', 'super_admin'].includes(user.role.toLowerCase());
const [staff, setStaff] = useState<any[]>([]); const [staff, setStaff] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
@@ -79,7 +80,7 @@ export const StaffPage = () => {
<h2 className="text-3xl font-black text-white">إدارة الموظفين</h2> <h2 className="text-3xl font-black text-white">إدارة الموظفين</h2>
<p className="text-slate-300 mt-1">إدارة فريق العمل المالي لمكتب المحاسبة الخاص بك.</p> <p className="text-slate-300 mt-1">إدارة فريق العمل المالي لمكتب المحاسبة الخاص بك.</p>
</div> </div>
{user?.role === 'admin' && ( {isAdmin && (
<button <button
onClick={() => setIsAddModalOpen(true)} onClick={() => setIsAddModalOpen(true)}
className="bg-emerald-500 hover:bg-emerald-600 text-slate-950 font-bold py-3 px-8 rounded-xl flex items-center gap-2 shadow-lg shadow-emerald-500/20 transition-all active:scale-95" className="bg-emerald-500 hover:bg-emerald-600 text-slate-950 font-bold py-3 px-8 rounded-xl flex items-center gap-2 shadow-lg shadow-emerald-500/20 transition-all active:scale-95"

0
old.tsx Normal file
View File