Add JoFotara linking modal and fix company limit

This commit is contained in:
Hamza-Ayed
2026-04-18 00:47:37 +03:00
parent ce7b1fc5d8
commit 77434fa815
3 changed files with 113 additions and 13 deletions

View File

@@ -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,

12
fix-limit.js Normal file
View 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();
});

View File

@@ -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>
);
};