Add JoFotara linking modal and fix company limit
This commit is contained in:
@@ -27,7 +27,7 @@ export class AuthService {
|
|||||||
private jwtService: JwtService,
|
private jwtService: JwtService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private dataSource: DataSource,
|
private dataSource: DataSource,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* تسجيل مستخدم جديد (مدير مكتب)
|
* تسجيل مستخدم جديد (مدير مكتب)
|
||||||
@@ -60,7 +60,7 @@ export class AuthService {
|
|||||||
const subscription = queryRunner.manager.create(Subscription, {
|
const subscription = queryRunner.manager.create(Subscription, {
|
||||||
tenant_id: savedTenant.id,
|
tenant_id: savedTenant.id,
|
||||||
plan: SubscriptionPlan.BASIC,
|
plan: SubscriptionPlan.BASIC,
|
||||||
max_companies: 1,
|
max_companies: 3, // -1 means unlimited
|
||||||
max_invoices_per_month: 200,
|
max_invoices_per_month: 200,
|
||||||
price_jod: 15, // Basic price
|
price_jod: 15, // Basic price
|
||||||
status: SubscriptionStatus.ACTIVE,
|
status: SubscriptionStatus.ACTIVE,
|
||||||
|
|||||||
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 [isLoading, setIsLoading] = useState(true);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
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 [name, setName] = useState('');
|
||||||
const [tin, setTin] = useState('');
|
const [tin, setTin] = useState('');
|
||||||
const [address, setAddress] = useState('');
|
const [address, setAddress] = useState('');
|
||||||
|
|
||||||
|
// Form State (JoFotara)
|
||||||
|
const [clientId, setClientId] = useState('');
|
||||||
|
const [secretKey, setSecretKey] = useState('');
|
||||||
|
|
||||||
const fetchCompanies = async () => {
|
const fetchCompanies = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await apiClient.get('/companies');
|
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 =>
|
const filteredCompanies = companies.filter(c =>
|
||||||
c.name.includes(searchTerm) || c.tax_identification_number?.includes(searchTerm)
|
c.name.includes(searchTerm) || c.tax_identification_number?.includes(searchTerm)
|
||||||
);
|
);
|
||||||
@@ -131,8 +160,11 @@ export const CompaniesPage = () => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -196,6 +228,62 @@ export const CompaniesPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user