Add JoFotara linking modal and fix company limit
This commit is contained in:
@@ -27,7 +27,7 @@ export class AuthService {
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
private dataSource: DataSource,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/**
|
||||
* تسجيل مستخدم جديد (مدير مكتب)
|
||||
@@ -60,7 +60,7 @@ export class AuthService {
|
||||
const subscription = queryRunner.manager.create(Subscription, {
|
||||
tenant_id: savedTenant.id,
|
||||
plan: SubscriptionPlan.BASIC,
|
||||
max_companies: 1,
|
||||
max_companies: 3, // -1 means unlimited
|
||||
max_invoices_per_month: 200,
|
||||
price_jod: 15, // Basic price
|
||||
status: SubscriptionStatus.ACTIVE,
|
||||
@@ -109,10 +109,10 @@ export class AuthService {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
|
||||
const payload = {
|
||||
sub: user.id,
|
||||
tenantId: user.tenant_id,
|
||||
role: user.role
|
||||
const payload = {
|
||||
sub: user.id,
|
||||
tenantId: user.tenant_id,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
const accessToken = await this.jwtService.signAsync(payload);
|
||||
@@ -152,10 +152,10 @@ export class AuthService {
|
||||
throw new UnauthorizedException('Access Denied');
|
||||
}
|
||||
|
||||
const payload = {
|
||||
sub: user.id,
|
||||
tenantId: user.tenant_id,
|
||||
role: user.role
|
||||
const payload = {
|
||||
sub: user.id,
|
||||
tenantId: user.tenant_id,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
const accessToken = await this.jwtService.signAsync(payload);
|
||||
|
||||
12
fix-limit.js
Normal file
12
fix-limit.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { Pool } = require('pg');
|
||||
const pool = new Pool({
|
||||
user: 'postgres',
|
||||
host: '127.0.0.1',
|
||||
database: 'musadaq',
|
||||
password: 'postgres_password',
|
||||
port: 5432,
|
||||
});
|
||||
pool.query("UPDATE subscriptions SET max_companies = -1", (err, res) => {
|
||||
console.log(err ? err : "Updated successfully!");
|
||||
pool.end();
|
||||
});
|
||||
@@ -13,12 +13,18 @@ export const CompaniesPage = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
const [isJoFotaraModalOpen, setIsJoFotaraModalOpen] = useState(false);
|
||||
const [selectedCompany, setSelectedCompany] = useState<any>(null);
|
||||
|
||||
// Form State
|
||||
// Form State (New Company)
|
||||
const [name, setName] = useState('');
|
||||
const [tin, setTin] = useState('');
|
||||
const [address, setAddress] = useState('');
|
||||
|
||||
// Form State (JoFotara)
|
||||
const [clientId, setClientId] = useState('');
|
||||
const [secretKey, setSecretKey] = useState('');
|
||||
|
||||
const fetchCompanies = async () => {
|
||||
try {
|
||||
const { data } = await apiClient.get('/companies');
|
||||
@@ -53,6 +59,29 @@ export const CompaniesPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenJoFotara = (company: any) => {
|
||||
setSelectedCompany(company);
|
||||
setClientId(''); // We don't fetch existing keys for security, user has to enter new ones if they want to update
|
||||
setSecretKey('');
|
||||
setIsJoFotaraModalOpen(true);
|
||||
};
|
||||
|
||||
const handleSubmitJoFotara = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await apiClient.put(`/companies/${selectedCompany.id}/jofotara`, {
|
||||
clientId,
|
||||
secretKey
|
||||
});
|
||||
setIsJoFotaraModalOpen(false);
|
||||
setSelectedCompany(null);
|
||||
fetchCompanies(); // Refresh to show "Linked" status
|
||||
} catch (error) {
|
||||
console.error('Failed to link JoFotara', error);
|
||||
alert('حدث خطأ أثناء ربط حساب جو فوترة');
|
||||
}
|
||||
};
|
||||
|
||||
const filteredCompanies = companies.filter(c =>
|
||||
c.name.includes(searchTerm) || c.tax_identification_number?.includes(searchTerm)
|
||||
);
|
||||
@@ -131,8 +160,11 @@ export const CompaniesPage = () => {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<button className="text-primary-600 text-sm font-bold hover:underline">
|
||||
التفاصيل
|
||||
<button
|
||||
onClick={() => handleOpenJoFotara(company)}
|
||||
className="text-primary-600 text-sm font-bold hover:underline"
|
||||
>
|
||||
{company.jofotara_client_id ? 'تحديث الربط' : 'إعداد جو فوترة'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,6 +228,62 @@ export const CompaniesPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── JoFotara Link Modal ───────────────────────────────── */}
|
||||
{isJoFotaraModalOpen && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/50 backdrop-blur-sm animate-in fade-in duration-200">
|
||||
<div className="bg-white rounded-3xl p-8 w-full max-w-md shadow-2xl animate-in zoom-in-95 duration-200">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 rounded-xl bg-primary-50 text-primary-600 flex items-center justify-center">
|
||||
<ShieldCheck className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-slate-900">ربط نظام جو فوترة</h3>
|
||||
<p className="text-sm text-slate-500">{selectedCompany?.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={handleSubmitJoFotara} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-slate-700 mb-1">Client ID</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={clientId}
|
||||
onChange={e => setClientId(e.target.value)}
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 transition-all font-mono text-sm"
|
||||
placeholder="أدخل Client ID..."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-slate-700 mb-1">Secret Key</label>
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
value={secretKey}
|
||||
onChange={e => setSecretKey(e.target.value)}
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 outline-none focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 transition-all font-mono text-sm"
|
||||
placeholder="أدخل Secret Key..."
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsJoFotaraModalOpen(false)}
|
||||
className="flex-1 bg-slate-100 text-slate-700 font-bold py-3 rounded-xl hover:bg-slate-200 transition-all"
|
||||
>
|
||||
إلغاء
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex-1 btn-primary py-3 rounded-xl"
|
||||
>
|
||||
حفظ وتشفير المفاتيح
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user