@ma7moudsalama/falak-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +378 -0
  2. package/bin/falak.js +157 -0
  3. package/index.js +5 -0
  4. package/lib/scaffold.js +23 -0
  5. package/package.json +46 -0
  6. package/template/_env.example +34 -0
  7. package/template/_gitignore +8 -0
  8. package/template/firebase-rules.json +36 -0
  9. package/template/index.html +21 -0
  10. package/template/package.json +36 -0
  11. package/template/postcss.config.js +6 -0
  12. package/template/public/favicon.svg +5 -0
  13. package/template/src/App.vue +95 -0
  14. package/template/src/assets/main.css +100 -0
  15. package/template/src/components/layout/AppLayout.vue +163 -0
  16. package/template/src/composables/useAuth.js +393 -0
  17. package/template/src/composables/useCrypto.js +153 -0
  18. package/template/src/composables/useDatabase.js +341 -0
  19. package/template/src/composables/useGroq.js +237 -0
  20. package/template/src/composables/usePaymob.js +392 -0
  21. package/template/src/firebase/index.js +87 -0
  22. package/template/src/i18n/index.js +66 -0
  23. package/template/src/i18n/locales/ar.json +121 -0
  24. package/template/src/i18n/locales/en.json +121 -0
  25. package/template/src/main.js +59 -0
  26. package/template/src/router/index.js +127 -0
  27. package/template/src/stores/auth.js +14 -0
  28. package/template/src/views/AdminView.vue +67 -0
  29. package/template/src/views/DashboardView.vue +253 -0
  30. package/template/src/views/HomeView.vue +13 -0
  31. package/template/src/views/NotFoundView.vue +8 -0
  32. package/template/src/views/ProfileView.vue +134 -0
  33. package/template/src/views/auth/ForgotView.vue +57 -0
  34. package/template/src/views/auth/LoginView.vue +169 -0
  35. package/template/src/views/auth/RegisterView.vue +103 -0
  36. package/template/tailwind.config.js +41 -0
  37. package/template/vite.config.js +29 -0
@@ -0,0 +1,392 @@
1
+ /**
2
+ * usePaymob — Paymob Payment Gateway Composable
3
+ * ───────────────────────────────────────────────
4
+ * Handles:
5
+ * • Authentication token retrieval
6
+ * • Order registration
7
+ * • Payment key generation
8
+ * • iFrame rendering
9
+ * • HMAC signature verification
10
+ * • User subscription management in RTDB
11
+ *
12
+ * Docs: https://developers.paymob.com
13
+ *
14
+ * Usage:
15
+ * const paymob = usePaymob()
16
+ * await paymob.initPayment({ amountCents: 10000, ... })
17
+ * // opens Paymob iframe
18
+ *
19
+ * const sub = useSubscription()
20
+ * await sub.subscribe(userId, 'premium', planDetails)
21
+ */
22
+
23
+ import { ref, readonly, computed } from 'vue'
24
+ import axios from 'axios'
25
+ import { useDatabase } from './useDatabase.js'
26
+ import { useCrypto } from './useCrypto.js'
27
+ import { serverTimestamp } from 'firebase/database'
28
+
29
+ const PAYMOB_API_KEY = import.meta.env.VITE_PAYMOB_API_KEY || ''
30
+ const PAYMOB_INTEGRATION = import.meta.env.VITE_PAYMOB_INTEGRATION_ID || ''
31
+ const PAYMOB_IFRAME_ID = import.meta.env.VITE_PAYMOB_IFRAME_ID || ''
32
+ const PAYMOB_HMAC_SECRET = import.meta.env.VITE_PAYMOB_HMAC_SECRET || ''
33
+ const PAYMOB_BASE_URL = 'https://accept.paymob.com/api'
34
+
35
+ // ── Subscription Plans ─────────────────────────
36
+ export const PLANS = {
37
+ FREE: {
38
+ id: 'free',
39
+ name: 'Free',
40
+ amountCents: 0,
41
+ durationDays: 0, // unlimited
42
+ features: ['basic_access']
43
+ },
44
+ BASIC: {
45
+ id: 'basic',
46
+ name: 'Basic',
47
+ amountCents: 4900, // 49.00 EGP
48
+ durationDays: 30,
49
+ features: ['basic_access', 'priority_support']
50
+ },
51
+ PRO: {
52
+ id: 'pro',
53
+ name: 'Pro',
54
+ amountCents: 14900, // 149.00 EGP
55
+ durationDays: 30,
56
+ features: ['basic_access', 'priority_support', 'advanced_features', 'api_access']
57
+ },
58
+ ENTERPRISE: {
59
+ id: 'enterprise',
60
+ name: 'Enterprise',
61
+ amountCents: 49900,
62
+ durationDays: 30,
63
+ features: ['basic_access', 'priority_support', 'advanced_features', 'api_access', 'custom_integrations', 'dedicated_support']
64
+ }
65
+ }
66
+
67
+ // ── Paymob Core ────────────────────────────────
68
+ export function usePaymob() {
69
+ const isLoading = ref(false)
70
+ const lastError = ref(null)
71
+ const authToken = ref(null)
72
+ const orderId = ref(null)
73
+ const paymentKey = ref(null)
74
+ const iframeUrl = ref(null)
75
+
76
+ // ── Step 1: Get auth token ─────────────────
77
+ async function authenticate() {
78
+ const { data } = await axios.post(`${PAYMOB_BASE_URL}/auth/tokens`, {
79
+ api_key: PAYMOB_API_KEY
80
+ })
81
+ authToken.value = data.token
82
+ return data.token
83
+ }
84
+
85
+ // ── Step 2: Register order ─────────────────
86
+ async function registerOrder(token, { amountCents, currency = 'EGP', items = [] }) {
87
+ const { data } = await axios.post(`${PAYMOB_BASE_URL}/ecommerce/orders`, {
88
+ auth_token: token,
89
+ delivery_needed: false,
90
+ amount_cents: amountCents,
91
+ currency,
92
+ merchant_order_id: `falak_${Date.now()}`,
93
+ items
94
+ })
95
+ orderId.value = data.id
96
+ return data.id
97
+ }
98
+
99
+ // ── Step 3: Get payment key ────────────────
100
+ async function getPaymentKey(token, orderIdVal, { amountCents, currency = 'EGP', billingData }) {
101
+ const { data } = await axios.post(`${PAYMOB_BASE_URL}/acceptance/payment_keys`, {
102
+ auth_token: token,
103
+ amount_cents: amountCents,
104
+ expiration: 3600,
105
+ order_id: orderIdVal,
106
+ billing_data: billingData || {
107
+ apartment: 'NA',
108
+ email: 'customer@example.com',
109
+ floor: 'NA',
110
+ first_name: 'Customer',
111
+ street: 'NA',
112
+ building: 'NA',
113
+ phone_number: '+20xxxxxxxxxx',
114
+ shipping_method: 'NA',
115
+ postal_code: 'NA',
116
+ city: 'NA',
117
+ country: 'EG',
118
+ last_name: 'User',
119
+ state: 'NA'
120
+ },
121
+ currency,
122
+ integration_id: Number(PAYMOB_INTEGRATION),
123
+ lock_order_when_paid: true
124
+ })
125
+ paymentKey.value = data.token
126
+ return data.token
127
+ }
128
+
129
+ // ── Full flow ──────────────────────────────
130
+ /**
131
+ * Initialize a payment session and return the iframe URL.
132
+ * Optionally opens it in a new tab or embeds it.
133
+ *
134
+ * @param {object} paymentData
135
+ * @param {number} paymentData.amountCents - Amount in smallest unit (piasters)
136
+ * @param {string} [paymentData.currency]
137
+ * @param {object} [paymentData.billingData]
138
+ * @param {Array} [paymentData.items]
139
+ * @returns {{ success: boolean, iframeUrl: string, paymentKey: string, orderId: number }}
140
+ */
141
+ async function initPayment(paymentData) {
142
+ isLoading.value = true
143
+ lastError.value = null
144
+ try {
145
+ const token = await authenticate()
146
+ const oId = await registerOrder(token, paymentData)
147
+ const pKey = await getPaymentKey(token, oId, paymentData)
148
+
149
+ const url = `https://accept.paymob.com/api/acceptance/iframes/${PAYMOB_IFRAME_ID}?payment_token=${pKey}`
150
+ iframeUrl.value = url
151
+
152
+ return {
153
+ success: true,
154
+ iframeUrl: url,
155
+ paymentKey: pKey,
156
+ orderId: oId
157
+ }
158
+ } catch (err) {
159
+ lastError.value = err.response?.data?.message || err.message
160
+ return { success: false, error: lastError.value }
161
+ } finally {
162
+ isLoading.value = false
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Open payment in a new tab.
168
+ */
169
+ async function openPaymentTab(paymentData) {
170
+ const result = await initPayment(paymentData)
171
+ if (result.success) {
172
+ window.open(result.iframeUrl, '_blank')
173
+ }
174
+ return result
175
+ }
176
+
177
+ // ── HMAC Verification ──────────────────────
178
+ /**
179
+ * Verify Paymob callback HMAC signature.
180
+ * Call this in your webhook handler.
181
+ */
182
+ function verifyHmac(callbackData) {
183
+ const { hmac: receivedHmac, ...params } = callbackData
184
+ const { hmac: computeHmac } = useCrypto()
185
+
186
+ // Paymob HMAC fields (in specific order)
187
+ const fields = [
188
+ 'amount_cents', 'created_at', 'currency', 'error_occured',
189
+ 'has_parent_transaction', 'id', 'integration_id', 'is_3d_secure',
190
+ 'is_auth', 'is_capture', 'is_refunded', 'is_standalone_payment',
191
+ 'is_voided', 'order', 'owner', 'pending', 'source_data.pan',
192
+ 'source_data.sub_type', 'source_data.type', 'success'
193
+ ]
194
+
195
+ const message = fields
196
+ .map(f => {
197
+ const val = f.includes('.')
198
+ ? f.split('.').reduce((o, k) => o?.[k], params)
199
+ : params[f]
200
+ return String(val ?? '')
201
+ })
202
+ .join('')
203
+
204
+ const expected = computeHmac(message, PAYMOB_HMAC_SECRET)
205
+ return expected === receivedHmac
206
+ }
207
+
208
+ return {
209
+ isLoading: readonly(isLoading),
210
+ lastError: readonly(lastError),
211
+ authToken: readonly(authToken),
212
+ orderId: readonly(orderId),
213
+ paymentKey: readonly(paymentKey),
214
+ iframeUrl: readonly(iframeUrl),
215
+
216
+ initPayment,
217
+ openPaymentTab,
218
+ authenticate,
219
+ registerOrder,
220
+ getPaymentKey,
221
+ verifyHmac,
222
+ PLANS
223
+ }
224
+ }
225
+
226
+ // ── Subscription Manager ───────────────────────
227
+ /**
228
+ * useSubscription — Manage user subscriptions in RTDB
229
+ *
230
+ * Data structure in RTDB:
231
+ * /subscriptions/{userId} = {
232
+ * planId, planName, status, features,
233
+ * startedAt, expiresAt, paymobOrderId,
234
+ * autoRenew, paymentHistory: [...]
235
+ * }
236
+ */
237
+ export function useSubscription() {
238
+ const db = useDatabase()
239
+ const isLoading = ref(false)
240
+ const lastError = ref(null)
241
+
242
+ /**
243
+ * Get current subscription for a user.
244
+ */
245
+ async function getSubscription(userId) {
246
+ return db.get(`subscriptions/${userId}`)
247
+ }
248
+
249
+ /**
250
+ * Listen to subscription changes in real-time.
251
+ */
252
+ function listenSubscription(userId) {
253
+ return db.listen(`subscriptions/${userId}`)
254
+ }
255
+
256
+ /**
257
+ * Check if a user's subscription is active.
258
+ */
259
+ async function isActive(userId) {
260
+ const sub = await getSubscription(userId)
261
+ if (!sub) return false
262
+ if (sub.planId === 'free') return true
263
+ if (sub.status !== 'active') return false
264
+ return Date.now() < sub.expiresAt
265
+ }
266
+
267
+ /**
268
+ * Check if a user has a specific feature.
269
+ */
270
+ async function hasFeature(userId, feature) {
271
+ const sub = await getSubscription(userId)
272
+ if (!sub) return false
273
+ return sub.features?.includes(feature) || false
274
+ }
275
+
276
+ /**
277
+ * Activate / upgrade a subscription after successful payment.
278
+ * Call this from your Paymob webhook handler.
279
+ *
280
+ * @param {string} userId
281
+ * @param {string} planId - key from PLANS
282
+ * @param {object} paymentInfo - { paymobOrderId, amountCents, ... }
283
+ */
284
+ async function activateSubscription(userId, planId, paymentInfo = {}) {
285
+ isLoading.value = true
286
+ const plan = PLANS[planId.toUpperCase()]
287
+ if (!plan) {
288
+ lastError.value = `Unknown plan: ${planId}`
289
+ return { success: false, error: lastError.value }
290
+ }
291
+ try {
292
+ const now = Date.now()
293
+ const expiresAt = plan.durationDays > 0
294
+ ? now + plan.durationDays * 24 * 60 * 60 * 1000
295
+ : null
296
+
297
+ const existing = await getSubscription(userId) || {}
298
+ const history = existing.paymentHistory || []
299
+
300
+ await db.set(`subscriptions/${userId}`, {
301
+ planId: plan.id,
302
+ planName: plan.name,
303
+ status: 'active',
304
+ features: plan.features,
305
+ startedAt: now,
306
+ expiresAt,
307
+ autoRenew: true,
308
+ updatedAt: now,
309
+ paymentHistory: [
310
+ ...history,
311
+ {
312
+ planId,
313
+ amountCents: paymentInfo.amountCents || plan.amountCents,
314
+ paymobOrderId: paymentInfo.paymobOrderId || null,
315
+ paidAt: now
316
+ }
317
+ ].slice(-20) // keep last 20
318
+ })
319
+
320
+ return { success: true }
321
+ } catch (err) {
322
+ lastError.value = err.message
323
+ return { success: false, error: err.message }
324
+ } finally {
325
+ isLoading.value = false
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Cancel a subscription (mark inactive, keep data).
331
+ */
332
+ async function cancelSubscription(userId) {
333
+ return db.update(`subscriptions/${userId}`, {
334
+ status: 'cancelled',
335
+ autoRenew: false
336
+ })
337
+ }
338
+
339
+ /**
340
+ * Expire a subscription (typically called by a server cron).
341
+ */
342
+ async function expireSubscription(userId) {
343
+ return db.update(`subscriptions/${userId}`, {
344
+ status: 'expired'
345
+ })
346
+ }
347
+
348
+ /**
349
+ * Initialize free plan for a new user.
350
+ */
351
+ async function initFreeplan(userId) {
352
+ const existing = await getSubscription(userId)
353
+ if (existing) return { success: true } // already set
354
+
355
+ return db.set(`subscriptions/${userId}`, {
356
+ planId: 'free',
357
+ planName: 'Free',
358
+ status: 'active',
359
+ features: PLANS.FREE.features,
360
+ startedAt: Date.now(),
361
+ expiresAt: null,
362
+ autoRenew: false,
363
+ paymentHistory: []
364
+ })
365
+ }
366
+
367
+ /**
368
+ * Get all users with a specific plan (admin use).
369
+ */
370
+ function listenPlanUsers(planId) {
371
+ return db.listen('subscriptions', {
372
+ orderBy: 'planId',
373
+ equalTo: planId
374
+ })
375
+ }
376
+
377
+ return {
378
+ isLoading: readonly(isLoading),
379
+ lastError: readonly(lastError),
380
+ PLANS,
381
+
382
+ getSubscription,
383
+ listenSubscription,
384
+ isActive,
385
+ hasFeature,
386
+ activateSubscription,
387
+ cancelSubscription,
388
+ expireSubscription,
389
+ initFreeplan,
390
+ listenPlanUsers
391
+ }
392
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Firebase Configuration & Service Exports
3
+ * ─────────────────────────────────────────
4
+ * All Firebase services used across the app are initialized here.
5
+ * Import what you need from this file — not directly from firebase/x.
6
+ */
7
+
8
+ import { initializeApp, getApps, getApp } from 'firebase/app'
9
+ import {
10
+ getAuth,
11
+ setPersistence,
12
+ browserLocalPersistence,
13
+ connectAuthEmulator
14
+ } from 'firebase/auth'
15
+ import {
16
+ getDatabase,
17
+ connectDatabaseEmulator
18
+ } from 'firebase/database'
19
+ import {
20
+ getStorage,
21
+ connectStorageEmulator
22
+ } from 'firebase/storage'
23
+ import {
24
+ getFirestore,
25
+ connectFirestoreEmulator
26
+ } from 'firebase/firestore'
27
+ import { getAnalytics, isSupported } from 'firebase/analytics'
28
+ import { getMessaging, isSupported as messagingSupported } from 'firebase/messaging'
29
+
30
+ // ── Firebase Config from .env ──────────────────
31
+ const firebaseConfig = {
32
+ apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
33
+ authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
34
+ databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL,
35
+ projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
36
+ storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
37
+ messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
38
+ appId: import.meta.env.VITE_FIREBASE_APP_ID,
39
+ measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID
40
+ }
41
+
42
+ // ── Initialize App (singleton safe) ───────────
43
+ const firebaseApp = getApps().length ? getApp() : initializeApp(firebaseConfig)
44
+
45
+ // ── Auth ───────────────────────────────────────
46
+ const auth = getAuth(firebaseApp)
47
+ setPersistence(auth, browserLocalPersistence).catch(console.error)
48
+
49
+ // ── Realtime Database ──────────────────────────
50
+ const rtdb = getDatabase(firebaseApp)
51
+
52
+ // ── Firestore ──────────────────────────────────
53
+ const firestore = getFirestore(firebaseApp)
54
+
55
+ // ── Storage ────────────────────────────────────
56
+ const storage = getStorage(firebaseApp)
57
+
58
+ // ── Analytics (browser only) ───────────────────
59
+ let analytics = null
60
+ isSupported().then((yes) => {
61
+ if (yes) analytics = getAnalytics(firebaseApp)
62
+ })
63
+
64
+ // ── Messaging (browser only) ───────────────────
65
+ let messaging = null
66
+ messagingSupported().then((yes) => {
67
+ if (yes) messaging = getMessaging(firebaseApp)
68
+ })
69
+
70
+ // ── Emulators (dev only) ───────────────────────
71
+ if (import.meta.env.VITE_APP_ENV === 'emulator') {
72
+ connectAuthEmulator(auth, 'http://localhost:9099', { disableWarnings: true })
73
+ connectDatabaseEmulator(rtdb, 'localhost', 9000)
74
+ connectFirestoreEmulator(firestore, 'localhost', 8080)
75
+ connectStorageEmulator(storage, 'localhost', 9199)
76
+ console.info('🔥 Firebase Emulators connected')
77
+ }
78
+
79
+ export {
80
+ firebaseApp,
81
+ auth,
82
+ rtdb,
83
+ firestore,
84
+ storage,
85
+ analytics,
86
+ messaging
87
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Vue I18n Configuration
3
+ * ───────────────────────
4
+ * Supports: English (en) and Arabic (ar)
5
+ * Automatically applies RTL direction for Arabic.
6
+ * Persists selected locale to localStorage.
7
+ */
8
+
9
+ import { createI18n } from 'vue-i18n'
10
+ import en from './locales/en.json'
11
+ import ar from './locales/ar.json'
12
+
13
+ const LOCALE_KEY = 'falak_locale'
14
+
15
+ // Detect saved or browser locale
16
+ function detectLocale() {
17
+ const saved = localStorage.getItem(LOCALE_KEY)
18
+ const browser = navigator.language?.split('-')[0]
19
+ const supported = ['en', 'ar']
20
+ return supported.includes(saved) ? saved
21
+ : supported.includes(browser) ? browser
22
+ : 'en'
23
+ }
24
+
25
+ export const i18n = createI18n({
26
+ legacy: false, // Composition API mode
27
+ globalInjection: true, // $t available in templates
28
+ locale: detectLocale(),
29
+ fallbackLocale: 'en',
30
+ messages: { en, ar },
31
+ datetimeFormats: {
32
+ en: {
33
+ short: { year: 'numeric', month: 'short', day: 'numeric' },
34
+ long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' },
35
+ time: { hour: '2-digit', minute: '2-digit' }
36
+ },
37
+ ar: {
38
+ short: { year: 'numeric', month: 'short', day: 'numeric', calendar: 'gregory' },
39
+ long: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long', calendar: 'gregory' },
40
+ time: { hour: '2-digit', minute: '2-digit', hour12: true }
41
+ }
42
+ },
43
+ numberFormats: {
44
+ en: {
45
+ currency: { style: 'currency', currency: 'USD', minimumFractionDigits: 2 },
46
+ egp: { style: 'currency', currency: 'EGP', minimumFractionDigits: 2 }
47
+ },
48
+ ar: {
49
+ currency: { style: 'currency', currency: 'USD', minimumFractionDigits: 2 },
50
+ egp: { style: 'currency', currency: 'EGP', minimumFractionDigits: 2 }
51
+ }
52
+ }
53
+ })
54
+
55
+ // ── Locale switcher ────────────────────────────
56
+ export function setLocale(locale) {
57
+ i18n.global.locale.value = locale
58
+ localStorage.setItem(LOCALE_KEY, locale)
59
+ document.documentElement.lang = locale
60
+ document.documentElement.dir = locale === 'ar' ? 'rtl' : 'ltr'
61
+ }
62
+
63
+ // Apply direction on init
64
+ setLocale(detectLocale())
65
+
66
+ export default i18n
@@ -0,0 +1,121 @@
1
+ {
2
+ "app": {
3
+ "name": "فلك",
4
+ "tagline": "مبني بـ Vue 3 و Firebase"
5
+ },
6
+ "nav": {
7
+ "home": "الرئيسية",
8
+ "dashboard": "لوحة التحكم",
9
+ "profile": "الملف الشخصي",
10
+ "admin": "الإدارة",
11
+ "login": "تسجيل الدخول",
12
+ "register": "إنشاء حساب",
13
+ "logout": "تسجيل الخروج",
14
+ "settings": "الإعدادات"
15
+ },
16
+ "auth": {
17
+ "email": "البريد الإلكتروني",
18
+ "password": "كلمة المرور",
19
+ "confirmPassword": "تأكيد كلمة المرور",
20
+ "displayName": "الاسم الكامل",
21
+ "forgotPassword": "نسيت كلمة المرور؟",
22
+ "rememberMe": "تذكرني",
23
+ "noAccount": "ليس لديك حساب؟",
24
+ "hasAccount": "لديك حساب بالفعل؟",
25
+ "orContinueWith": "أو تابع عبر",
26
+ "signInWithGoogle": "الدخول بحساب Google",
27
+ "signInWithFacebook": "الدخول بحساب Facebook",
28
+ "resetPassword": "إعادة تعيين كلمة المرور",
29
+ "resetPasswordDesc": "سنرسل لك رابطاً لإعادة تعيين كلمة المرور.",
30
+ "backToLogin": "العودة لتسجيل الدخول",
31
+ "emailVerification": "يرجى تأكيد بريدك الإلكتروني. تحقق من صندوق الوارد.",
32
+ "passwordMismatch": "كلمتا المرور غير متطابقتين.",
33
+ "weakPassword": "يجب أن تتكون كلمة المرور من 8 أحرف على الأقل.",
34
+ "loginSuccess": "مرحباً بعودتك!",
35
+ "logoutSuccess": "تم تسجيل الخروج بنجاح.",
36
+ "registerSuccess": "تم إنشاء الحساب! يرجى تأكيد بريدك الإلكتروني."
37
+ },
38
+ "user": {
39
+ "role": "الصلاحية",
40
+ "roles": {
41
+ "super_admin": "المدير العام",
42
+ "admin": "مدير",
43
+ "user": "مستخدم",
44
+ "guest": "زائر"
45
+ },
46
+ "profile": "الملف الشخصي",
47
+ "editProfile": "تعديل الملف",
48
+ "changePassword": "تغيير كلمة المرور",
49
+ "deleteAccount": "حذف الحساب",
50
+ "deleteAccountConfirm": "هل أنت متأكد من حذف حسابك؟ هذا الإجراء لا يمكن التراجع عنه.",
51
+ "avatar": "الصورة الشخصية",
52
+ "memberSince": "عضو منذ"
53
+ },
54
+ "subscription": {
55
+ "title": "الاشتراك",
56
+ "currentPlan": "الخطة الحالية",
57
+ "upgradePlan": "ترقية الخطة",
58
+ "cancelPlan": "إلغاء الاشتراك",
59
+ "renewPlan": "تجديد",
60
+ "expiresOn": "تنتهي في",
61
+ "status": {
62
+ "active": "نشط",
63
+ "expired": "منتهي",
64
+ "cancelled": "ملغي",
65
+ "pending": "قيد الانتظار"
66
+ },
67
+ "plans": {
68
+ "free": "مجاني",
69
+ "basic": "أساسي",
70
+ "pro": "احترافي",
71
+ "enterprise": "مؤسسي"
72
+ },
73
+ "payNow": "ادفع الآن",
74
+ "paymentSuccess": "تمت عملية الدفع! تم ترقية خطتك.",
75
+ "paymentFailed": "فشلت عملية الدفع. يرجى المحاولة مجدداً."
76
+ },
77
+ "common": {
78
+ "save": "حفظ",
79
+ "cancel": "إلغاء",
80
+ "delete": "حذف",
81
+ "edit": "تعديل",
82
+ "confirm": "تأكيد",
83
+ "close": "إغلاق",
84
+ "back": "رجوع",
85
+ "next": "التالي",
86
+ "loading": "جارٍ التحميل...",
87
+ "search": "بحث",
88
+ "filter": "تصفية",
89
+ "reset": "إعادة تعيين",
90
+ "submit": "إرسال",
91
+ "yes": "نعم",
92
+ "no": "لا",
93
+ "or": "أو",
94
+ "and": "و",
95
+ "error": "حدث خطأ",
96
+ "success": "تمت العملية بنجاح",
97
+ "noData": "لا توجد بيانات",
98
+ "required": "هذا الحقل مطلوب",
99
+ "darkMode": "الوضع الداكن",
100
+ "lightMode": "الوضع الفاتح"
101
+ },
102
+ "offline": {
103
+ "title": "أنت غير متصل بالإنترنت",
104
+ "message": "البيانات للقراءة فقط في وضع عدم الاتصال. ستستأنف التغييرات عند إعادة الاتصال.",
105
+ "reconnected": "عدت متصلاً! يمكنك إجراء التغييرات مجدداً."
106
+ },
107
+ "errors": {
108
+ "404": "الصفحة غير موجودة",
109
+ "403": "ليس لديك صلاحية لعرض هذه الصفحة.",
110
+ "500": "حدث خطأ من جانبنا.",
111
+ "network": "خطأ في الشبكة. يرجى التحقق من اتصالك.",
112
+ "unknown": "حدث خطأ غير متوقع."
113
+ },
114
+ "ai": {
115
+ "title": "المساعد الذكي",
116
+ "placeholder": "اسألني أي شيء...",
117
+ "thinking": "جارٍ التفكير...",
118
+ "clearChat": "مسح المحادثة",
119
+ "send": "إرسال"
120
+ }
121
+ }