🚀 مُصادَق: الإطلاق الأولي للنظام المتكامل
This commit is contained in:
85
public/assets/css/app.css
Normal file
85
public/assets/css/app.css
Normal file
@@ -0,0 +1,85 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@400;500;600;700&family=JetBrains+Mono&family=Inter:wght@400;500;600&display=swap');
|
||||
|
||||
:root {
|
||||
--primary: #10b981;
|
||||
--primary-hover: #059669;
|
||||
--primary-muted: rgba(16,185,129,0.1);
|
||||
--danger: #ef4444;
|
||||
--warning: #f59e0b;
|
||||
--info: #3b82f6;
|
||||
--success: #22c55e;
|
||||
|
||||
/* Dark (default) */
|
||||
--bg-app: #0a0f1a;
|
||||
--bg-card: rgba(15,23,42,0.8);
|
||||
--bg-sidebar: #060b14;
|
||||
--bg-input: rgba(15,23,42,0.6);
|
||||
--border: rgba(51,65,85,0.6);
|
||||
--text-primary: #f1f5f9;
|
||||
--text-secondary: #94a3b8;
|
||||
--text-muted: #475569;
|
||||
--glass: rgba(15,23,42,0.6);
|
||||
--glass-border: rgba(255,255,255,0.06);
|
||||
--shadow-glow: 0 0 40px rgba(16,185,129,0.08);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg-app: #f1f5f9;
|
||||
--bg-card: #ffffff;
|
||||
--bg-sidebar: #ffffff;
|
||||
--bg-input: #f8fafc;
|
||||
--border: #e2e8f0;
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #475569;
|
||||
--text-muted: #94a3b8;
|
||||
--glass: rgba(255,255,255,0.8);
|
||||
--glass-border: rgba(0,0,0,0.04);
|
||||
--shadow-glow: 0 4px 24px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Inter', 'IBM Plex Sans Arabic', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-app);
|
||||
color: var(--text-primary);
|
||||
direction: rtl;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
/* Glassmorphism Utilities */
|
||||
.glass {
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.glow {
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
/* RTL Specifics */
|
||||
[dir="rtl"] .ml-auto { margin-right: auto; margin-left: 0; }
|
||||
[dir="rtl"] .mr-auto { margin-left: auto; margin-right: 0; }
|
||||
76
public/assets/js/api.js
Normal file
76
public/assets/js/api.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* مُصادَق — API Client with JWT Auth & Refresh Flow
|
||||
*/
|
||||
const API = {
|
||||
baseUrl: '/api/v1',
|
||||
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 headers = {
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
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) {
|
||||
// Try refresh token
|
||||
const refreshed = await this.refreshToken();
|
||||
if (refreshed) {
|
||||
return this._request(method, path, body, isFormData);
|
||||
} else {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw data;
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async refreshToken() {
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default API;
|
||||
Reference in New Issue
Block a user