🚀 مُصادَق: تحديث برمجي جديد 2026-05-03 13:19
This commit is contained in:
@@ -1,73 +1,55 @@
|
||||
/**
|
||||
* مُصادَق — API Client with JWT Auth & Refresh Flow
|
||||
*/
|
||||
window.API = {
|
||||
baseUrl: '/Application/public/api.php',
|
||||
accessToken: localStorage.getItem('access_token'),
|
||||
|
||||
async get(path) {
|
||||
return this._request('GET', path);
|
||||
},
|
||||
|
||||
async post(path, body) {
|
||||
return this._request('POST', path, body);
|
||||
},
|
||||
|
||||
async upload(path, formData) {
|
||||
return this._request('POST', path, formData, true);
|
||||
},
|
||||
|
||||
async _request(method, path, body = null, isFormData = false) {
|
||||
const API = {
|
||||
baseUrl: '/api/v1',
|
||||
|
||||
async request(endpoint, options = {}) {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
const headers = {
|
||||
'Accept': 'application/json',
|
||||
...(options.body instanceof FormData ? {} : { 'Content-Type': 'application/json' }),
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
||||
...options.headers
|
||||
};
|
||||
|
||||
if (this.accessToken) {
|
||||
headers['Authorization'] = `Bearer ${this.accessToken}`;
|
||||
}
|
||||
|
||||
if (!isFormData && body) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}${path}`, {
|
||||
method,
|
||||
headers,
|
||||
body
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
const refreshed = await this.refreshToken();
|
||||
if (refreshed) {
|
||||
return this._request(method, path, body, isFormData);
|
||||
} else {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
const response = await fetch(url, { ...options, headers });
|
||||
|
||||
if (response.status === 401 && !options._retry) {
|
||||
// Attempt token refresh
|
||||
const refreshed = await this.refresh();
|
||||
if (refreshed) {
|
||||
return this.request(endpoint, { ...options, _retry: true });
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw data;
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || 'حدث خطأ ما');
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
async refreshToken() {
|
||||
async login(email, password) {
|
||||
const data = await this.request('/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
localStorage.setItem('access_token', data.data.access_token);
|
||||
return data;
|
||||
},
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/auth/refresh`, { method: 'POST' });
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
this.accessToken = result.data.access_token;
|
||||
localStorage.setItem('access_token', this.accessToken);
|
||||
const data = await fetch(`${this.baseUrl}/auth/refresh`, { method: 'POST' });
|
||||
if (data.ok) {
|
||||
const result = await data.json();
|
||||
localStorage.setItem('access_token', result.data.access_token);
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
console.error('Refresh failed', e);
|
||||
}
|
||||
localStorage.removeItem('access_token');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
102
public/index.html
Normal file
102
public/index.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl" x-data="{ darkMode: true }" :class="{ 'dark': darkMode }">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>مُصادَق — أتمتة الفواتير الضريبية</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&family=Noto+Sans+Arabic:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
[x-cloak] { display: none !important; }
|
||||
body { font-family: 'Noto Sans Arabic', 'Outfit', sans-serif; }
|
||||
.glass { background: rgba(255, 255, 255, 0.05); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); }
|
||||
.dark .glass { background: rgba(0, 0, 0, 0.2); border: 1px solid rgba(255, 255, 255, 0.05); }
|
||||
</style>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: { 50: '#f0f9ff', 100: '#e0f2fe', 200: '#bae6fd', 300: '#7dd3fc', 400: '#38bdf8', 500: '#0ea5e9', 600: '#0284c7', 700: '#0369a1', 800: '#075985', 900: '#0c4a6e' },
|
||||
accent: '#FFD700'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-gray-50 dark:bg-slate-950 text-slate-900 dark:text-slate-100 min-h-screen transition-colors duration-500 overflow-x-hidden">
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="sticky top-0 z-50 glass px-6 py-4 flex justify-between items-center mx-4 mt-4 rounded-2xl shadow-xl">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-gradient-to-br from-primary-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg shadow-primary-500/30">
|
||||
<span class="text-white font-bold text-xl">م</span>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-primary-600 to-indigo-500 dark:from-primary-400 dark:to-indigo-400">مُصادَق</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<button @click="darkMode = !darkMode" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-slate-800 transition-all">
|
||||
<template x-if="!darkMode">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path></svg>
|
||||
</template>
|
||||
<template x-if="darkMode">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364-6.364l-.707.707M6.343 17.657l-.707.707M16.071 16.071l.707.707M7.929 7.929l.707-.707M12 8a4 4 0 100 8 4 4 0 000-8z"></path></svg>
|
||||
</template>
|
||||
</button>
|
||||
<a href="#login" class="px-6 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded-xl font-semibold transition-all shadow-lg shadow-primary-600/20">دخول</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-12">
|
||||
<section class="text-center py-20 relative">
|
||||
<div class="absolute -top-20 left-1/2 -translate-x-1/2 w-64 h-64 bg-primary-500/20 blur-[100px] rounded-full"></div>
|
||||
<h2 class="text-5xl md:text-7xl font-extrabold mb-6 leading-tight">
|
||||
أتمتة <span class="text-primary-500">الفواتير</span> <br>بذكاء اصطناعي فائق
|
||||
</h2>
|
||||
<p class="text-xl text-slate-600 dark:text-slate-400 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||
مُصادَق هو شريكك التقني المعتمد للربط مع نظام "جوفوتارا" الأردني، استخرج بيانات فواتيرك آلياً وامتثل للأنظمة الضريبية بثوانٍ.
|
||||
</p>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<button class="px-10 py-4 bg-slate-900 dark:bg-white dark:text-slate-900 text-white rounded-2xl font-bold text-lg hover:scale-105 transition-all shadow-2xl">ابدأ التجربة المجانية</button>
|
||||
<button class="px-10 py-4 glass rounded-2xl font-bold text-lg hover:bg-gray-100 dark:hover:bg-slate-800 transition-all">شاهد العرض</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Grid -->
|
||||
<section class="grid md:grid-cols-3 gap-8 py-20">
|
||||
<div class="p-8 glass rounded-3xl hover:-translate-y-2 transition-all duration-300">
|
||||
<div class="w-14 h-14 bg-blue-100 dark:bg-blue-900/30 rounded-2xl flex items-center justify-center mb-6">
|
||||
<svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold mb-3">استخراج ذكي (OCR)</h3>
|
||||
<p class="text-slate-500">استخدام Gemini 2.0 لاستخراج كافة بنود الفواتير من الصور والـ PDF بدقة تصل لـ 99%.</p>
|
||||
</div>
|
||||
<div class="p-8 glass rounded-3xl hover:-translate-y-2 transition-all duration-300">
|
||||
<div class="w-14 h-14 bg-green-100 dark:bg-green-900/30 rounded-2xl flex items-center justify-center mb-6">
|
||||
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold mb-3">توافق جو-فواتير</h3>
|
||||
<p class="text-slate-500">ربط مباشر مع منصة الفوترة الوطنية الأردنية وإصدار ملفات UBL 2.1 المعتمدة.</p>
|
||||
</div>
|
||||
<div class="p-8 glass rounded-3xl hover:-translate-y-2 transition-all duration-300">
|
||||
<div class="w-14 h-14 bg-purple-100 dark:bg-purple-900/30 rounded-2xl flex items-center justify-center mb-6">
|
||||
<svg class="w-8 h-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 00-2 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold mb-3">حماية البيانات</h3>
|
||||
<p class="text-slate-500">تشفير AES-256 للبيانات الحساسة وعزل كامل لبيانات المستأجرين (Multi-tenancy).</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="py-10 text-center text-slate-500 text-sm">
|
||||
<p>© 2026 مُصادَق — جميع الحقوق محفوظة لشركة انتاليك للحلول البرمجية</p>
|
||||
</footer>
|
||||
|
||||
<script src="assets/js/api.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,6 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../app/Core/helpers.php';
|
||||
|
||||
use App\Core\Application;
|
||||
use App\Modules\Auth\AuthController;
|
||||
@@ -13,6 +14,7 @@ $router = $app->getRouter();
|
||||
|
||||
// ══ Auth Routes ══════════════════════════════════════════════
|
||||
$router->addRoute('POST', '/api/v1/auth/login', [AuthController::class, 'login']);
|
||||
$router->addRoute('POST', '/api/v1/auth/register', [AuthController::class, 'register']);
|
||||
|
||||
// ══ Company Routes ═══════════════════════════════════════════
|
||||
$router->addRoute('GET', '/api/v1/companies', [
|
||||
@@ -52,6 +54,12 @@ $router->addRoute('GET', '/api/v1/invoices/{id}', [
|
||||
'handler' => [\App\Modules\Invoices\InvoiceController::class, 'detail']
|
||||
]);
|
||||
|
||||
// ══ Subscriptions ═════════════════════════════════════════════════
|
||||
$router->addRoute('GET', '/api/v1/subscriptions/me', [
|
||||
'middleware' => [\App\Middleware\AuthMiddleware::class, \App\Middleware\TenantMiddleware::class],
|
||||
'handler' => [\App\Modules\Subscriptions\SubscriptionController::class, 'me']
|
||||
]);
|
||||
|
||||
// ══ External API (HMAC) ══════════════════════════════════════
|
||||
$router->addRoute('POST', '/api/v1/external/invoices/upload', [
|
||||
'middleware' => [\App\Middleware\HmacMiddleware::class],
|
||||
|
||||
Reference in New Issue
Block a user