🚀 Fix: Login case-sensitivity & Restore Super Admin AI metrics access
This commit is contained in:
@@ -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'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user