falak-app-duo 1.1.4 → 1.2.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 (34) hide show
  1. package/bin/falak-app-duo +364 -44
  2. package/package.json +5 -2
  3. package/templates/default/.env.example +8 -1
  4. package/templates/default/package.json +4 -0
  5. package/templates/default/src/data/demo/appointments.js +7 -0
  6. package/templates/default/src/data/demo/customers.js +7 -0
  7. package/templates/default/src/data/demo/events.js +7 -0
  8. package/templates/default/src/data/demo/invoices.js +7 -0
  9. package/templates/default/src/data/demo/messages.js +7 -0
  10. package/templates/default/src/data/demo/orders.js +7 -0
  11. package/templates/default/src/data/demo/payments.js +7 -0
  12. package/templates/default/src/data/demo/posts.js +7 -0
  13. package/templates/default/src/data/demo/products.js +7 -0
  14. package/templates/default/src/data/demo/projects.js +7 -0
  15. package/templates/default/src/data/demo/tasks.js +7 -0
  16. package/templates/default/src/data/demo/users.js +9 -0
  17. package/templates/default/src/data/demoAccounts.js +135 -0
  18. package/templates/default/src/data/roles.js +25 -0
  19. package/templates/default/src/i18n/locales/ar.json +26 -4
  20. package/templates/default/src/i18n/locales/de.json +26 -4
  21. package/templates/default/src/i18n/locales/en.json +26 -4
  22. package/templates/default/src/i18n/locales/es.json +26 -4
  23. package/templates/default/src/i18n/locales/fr.json +26 -4
  24. package/templates/default/src/i18n/locales/it.json +26 -4
  25. package/templates/default/src/i18n/locales/ru.json +26 -4
  26. package/templates/default/src/main.js +11 -0
  27. package/templates/default/src/services/dataClient.js +22 -0
  28. package/templates/default/src/services/demoApi.js +377 -0
  29. package/templates/default/src/stores/auth.js +22 -17
  30. package/templates/default/src/views/Home.vue +52 -2
  31. package/templates/default/src/views/Login.vue +26 -1
  32. package/templates/default/src/views/Profile.vue +13 -0
  33. package/templates/default/src/views/Register.vue +17 -2
  34. package/templates/default/vite.config.js +2 -1
package/bin/falak-app-duo CHANGED
@@ -35,6 +35,182 @@ const LOCALE_LABELS = {
35
35
 
36
36
  const EXTRA_LOCALE_FILES = ["ar.json", "de.json", "es.json", "fr.json", "it.json", "ru.json"];
37
37
 
38
+ const ROLE_ORDER = ["master", "super_admin", "admin", "user"];
39
+ const ROLE_META = {
40
+ master: { label: "roles.master.title", badge: "bg-rose-500/10 text-rose-300 ring-rose-400/30" },
41
+ super_admin: { label: "roles.super_admin.title", badge: "bg-amber-500/10 text-amber-300 ring-amber-400/30" },
42
+ admin: { label: "roles.admin.title", badge: "bg-cyan-500/10 text-cyan-300 ring-cyan-400/30" },
43
+ user: { label: "roles.user.title", badge: "bg-emerald-500/10 text-emerald-300 ring-emerald-400/30" },
44
+ };
45
+
46
+ const DEMO_ENTITIES = [
47
+ "users",
48
+ "products",
49
+ "orders",
50
+ "customers",
51
+ "invoices",
52
+ "payments",
53
+ "events",
54
+ "posts",
55
+ "messages",
56
+ "tasks",
57
+ "projects",
58
+ "appointments",
59
+ ];
60
+
61
+ const DEMO_ROLE_SEED = {
62
+ master: {
63
+ name: "Aya El-Amin",
64
+ email: "master@demo.dev",
65
+ phone: "+201000000001",
66
+ country_code: "EG",
67
+ city: "Cairo",
68
+ channel: "telegram",
69
+ channel_handle: "@aya_master",
70
+ ai_model: "llama-3.3-70b-versatile",
71
+ ai_enabled: true,
72
+ telegram_username: "@aya_master",
73
+ },
74
+ super_admin: {
75
+ name: "Omar El-Sayed",
76
+ email: "superadmin@demo.dev",
77
+ phone: "+966500000002",
78
+ country_code: "SA",
79
+ city: "Riyadh",
80
+ channel: "whatsapp",
81
+ channel_handle: "+966500000002",
82
+ ai_model: "mixtral-8x7b-32768",
83
+ ai_enabled: true,
84
+ telegram_username: "",
85
+ },
86
+ admin: {
87
+ name: "Layla Haddad",
88
+ email: "admin@demo.dev",
89
+ phone: "+971500000003",
90
+ country_code: "AE",
91
+ city: "Dubai",
92
+ channel: "none",
93
+ channel_handle: "",
94
+ ai_model: "llama-3.1-8b-instant",
95
+ ai_enabled: false,
96
+ telegram_username: "",
97
+ },
98
+ user: {
99
+ name: "Karim Nasser",
100
+ email: "user@demo.dev",
101
+ phone: "+120200000004",
102
+ country_code: "US",
103
+ city: "New York",
104
+ channel: "none",
105
+ channel_handle: "",
106
+ ai_model: "gemma2-9b-it",
107
+ ai_enabled: false,
108
+ telegram_username: "",
109
+ },
110
+ };
111
+
112
+ const DEMO_NOTIFICATIONS = {
113
+ account: { email: true, push: true, sms: false, telegram: false },
114
+ security: { email: true, push: true, sms: true, telegram: false },
115
+ marketing: { email: false, push: false, sms: false, telegram: false },
116
+ updates: { email: true, push: false, sms: false, telegram: false },
117
+ };
118
+
119
+ function buildRolesModule(roleKeys) {
120
+ const lines = [];
121
+ lines.push("// UserRole support. Generated by the scaffolder: only the roles");
122
+ lines.push("// you picked at scaffold time are kept; an empty ROLES means UserRole");
123
+ lines.push('// support is off. `label` is a vue-i18n key (see i18n/locales/*.json).');
124
+ lines.push("export const ROLES = [");
125
+ for (const key of ROLE_ORDER.filter((k) => roleKeys.includes(k))) {
126
+ lines.push(` { key: "${key}", label: "${ROLE_META[key].label}", badge: "${ROLE_META[key].badge}" },`);
127
+ }
128
+ lines.push("];");
129
+ return lines.join("\n") + "\n";
130
+ }
131
+
132
+ function demoSettingsFor(roleKey) {
133
+ const seed = roleKey ? DEMO_ROLE_SEED[roleKey] : null;
134
+ return {
135
+ notification_preferences: DEMO_NOTIFICATIONS,
136
+ two_factor_enabled: false,
137
+ ai_agent: {
138
+ enabled: seed ? seed.ai_enabled : false,
139
+ model: seed ? seed.ai_model : "llama-3.3-70b-versatile",
140
+ temperature: 0.7,
141
+ reasoning: false,
142
+ system_prompt: "",
143
+ },
144
+ telegram: { bot_token: "", chat_id: "", username: seed ? seed.telegram_username : "" },
145
+ };
146
+ }
147
+
148
+ // roleKeys == [] => a single normal demo user (no role on the account).
149
+ function buildDemoAccountsModule(roleKeys) {
150
+ const chosen = ROLE_ORDER.filter((k) => roleKeys.includes(k));
151
+ const accounts = chosen.length
152
+ ? chosen.map((key) => {
153
+ const s = DEMO_ROLE_SEED[key];
154
+ return {
155
+ role: key,
156
+ email: s.email,
157
+ password: "password",
158
+ profile: {
159
+ name: s.name,
160
+ email: s.email,
161
+ phone: s.phone,
162
+ country_code: s.country_code,
163
+ city: s.city,
164
+ channel: s.channel,
165
+ channel_handle: s.channel_handle,
166
+ photo: null,
167
+ },
168
+ settings: demoSettingsFor(key),
169
+ };
170
+ })
171
+ : [
172
+ {
173
+ role: null,
174
+ email: "demo@demo.dev",
175
+ password: "password",
176
+ profile: {
177
+ name: "Demo User",
178
+ email: "demo@demo.dev",
179
+ phone: "+201000000009",
180
+ country_code: "EG",
181
+ city: "Cairo",
182
+ channel: "none",
183
+ channel_handle: "",
184
+ photo: null,
185
+ },
186
+ settings: demoSettingsFor(null),
187
+ },
188
+ ];
189
+ const lines = [];
190
+ lines.push("// Static demo accounts for testing. Generated by the scaffolder: one");
191
+ lines.push("// account per role you picked, or a single normal demo user when");
192
+ lines.push("// UserRole support is off. Each carries its full profile and default");
193
+ lines.push("// settings so a backend seed (or a first-login init) has everything.");
194
+ lines.push("export const DEMO_ACCOUNTS = [");
195
+ for (const account of accounts) {
196
+ lines.push(" " + JSON.stringify(account) + ",");
197
+ }
198
+ lines.push("];");
199
+ return lines.join("\n") + "\n";
200
+ }
201
+
202
+ function parseList(value) {
203
+ return String(value || "")
204
+ .split(",")
205
+ .map((x) => x.trim())
206
+ .filter(Boolean);
207
+ }
208
+
209
+ function keepCanonicalOrder(value, allowed) {
210
+ const picked = parseList(value);
211
+ return allowed.filter((item) => picked.includes(item));
212
+ }
213
+
38
214
  function buildI18nModule(locales, defaultLocale) {
39
215
  const ordered = ["ar", "en", "es", "de", "fr", "ru", "it"].filter((c) => locales.includes(c));
40
216
  const lines = [];
@@ -82,8 +258,11 @@ program
82
258
  .name("falak-app")
83
259
  .description("Scaffold a Vue 3 app with Ably, Brevo, AES-256 and a Laravel API client baked in.")
84
260
  .argument("[project-name]", "Directory to create the project in")
85
- .option("-y, --yes", "Skip prompts and use defaults (all services enabled)")
261
+ .option("-y, --yes", "Skip prompts and use defaults (services + languages only; no roles/demo)")
86
262
  .option("--no-rtl", "Exclude Arabic/RTL (English or your chosen LTR languages only)")
263
+ .option("--roles <list>", "Enable UserRole support with the given comma-separated roles (master,super_admin,admin,user)")
264
+ .option("--demo-accounts", "Include static demo accounts (per selected role, or a normal demo user without roles)")
265
+ .option("--demo-data <list>", "Include static demo data for the given comma-separated entities (e.g. products,orders)")
87
266
  .parse(process.argv);
88
267
 
89
268
  const [argProjectName] = program.args;
@@ -120,49 +299,141 @@ async function printBanner() {
120
299
  async function main() {
121
300
  await printBanner();
122
301
 
123
- const answers = opts.yes
124
- ? {
125
- projectName: argProjectName || "falak-app-duo",
126
- services: ["ably", "brevo"],
127
- languages: ["ar"],
128
- apiBaseUrl: "http://localhost:8000/api",
129
- }
130
- : await prompts(
131
- [
132
- {
133
- type: argProjectName ? null : "text",
134
- name: "projectName",
135
- message: "Project name / folder:",
136
- initial: "falak-app-duo",
137
- },
138
- {
139
- type: "multiselect",
140
- name: "services",
141
- message: "Which services do you want wired up?",
142
- choices: [
143
- { title: "Ably (realtime, via Laravel Echo)", value: "ably", selected: true },
144
- { title: "Brevo (email/SMS, via backend)", value: "brevo", selected: true },
145
- ],
146
- hint: "Space to toggle, Enter to confirm",
147
- },
148
- {
149
- type: "multiselect",
150
- name: "languages",
151
- message: "Which languages should the app ship with? (English is always included)",
152
- choices: LANGUAGE_CHOICES.map((c) =>
153
- c.value === "ar" && !opts.rtl ? { ...c, selected: false } : c
154
- ),
155
- hint: "Space to toggle, Enter to confirm",
156
- },
157
- {
158
- type: "text",
159
- name: "apiBaseUrl",
160
- message: "Laravel API base URL:",
161
- initial: "http://localhost:8000/api",
162
- },
163
- ],
302
+ const defaults = {
303
+ projectName: argProjectName || "falak-app-duo",
304
+ services: ["ably", "brevo"],
305
+ languages: ["ar"],
306
+ apiBaseUrl: "http://localhost:8000/api",
307
+ };
308
+
309
+ const hasFeatureFlags =
310
+ opts.roles !== undefined || opts.demoAccounts !== undefined || opts.demoData !== undefined;
311
+
312
+ // Feature outcomes. Roles/accounts/data only come via explicit flags or
313
+ // interactive prompts — `--yes` keeps the minimal scaffold.
314
+ let roles = opts.yes ? [] : null;
315
+ let demoAccounts = opts.yes ? false : null;
316
+ let demoEntities = opts.yes ? [] : null;
317
+
318
+ let answers;
319
+ if (opts.yes || hasFeatureFlags) {
320
+ answers = { ...defaults };
321
+ } else {
322
+ answers = await prompts(
323
+ [
324
+ {
325
+ type: argProjectName ? null : "text",
326
+ name: "projectName",
327
+ message: "Project name / folder:",
328
+ initial: "falak-app-duo",
329
+ },
330
+ {
331
+ type: "multiselect",
332
+ name: "services",
333
+ message: "Which services do you want wired up?",
334
+ choices: [
335
+ { title: "Ably (realtime, via Laravel Echo)", value: "ably", selected: true },
336
+ { title: "Brevo (email/SMS, via backend)", value: "brevo", selected: true },
337
+ ],
338
+ hint: "Space to toggle, Enter to confirm",
339
+ },
340
+ {
341
+ type: "multiselect",
342
+ name: "languages",
343
+ message: "Which languages should the app ship with? (English is always included)",
344
+ choices: LANGUAGE_CHOICES.map((c) =>
345
+ c.value === "ar" && !opts.rtl ? { ...c, selected: false } : c
346
+ ),
347
+ hint: "Space to toggle, Enter to confirm",
348
+ },
349
+ {
350
+ type: "text",
351
+ name: "apiBaseUrl",
352
+ message: "Laravel API base URL:",
353
+ initial: "http://localhost:8000/api",
354
+ },
355
+ ],
356
+ { onCancel: () => process.exit(1) }
357
+ );
358
+ }
359
+
360
+ // ---- UserRole support ----
361
+ if (!opts.yes && hasFeatureFlags) {
362
+ roles = opts.roles !== undefined ? keepCanonicalOrder(opts.roles, ROLE_ORDER) : [];
363
+ demoAccounts = Boolean(opts.demoAccounts);
364
+ demoEntities = opts.demoData !== undefined ? keepCanonicalOrder(opts.demoData, DEMO_ENTITIES) : [];
365
+ } else if (!opts.yes) {
366
+ const wantRoles = (
367
+ await prompts(
368
+ {
369
+ type: "toggle",
370
+ name: "v",
371
+ message: "Register accounts with a role (UserRole support)?",
372
+ initial: true,
373
+ active: "yes",
374
+ inactive: "no",
375
+ },
376
+ { onCancel: () => process.exit(1) }
377
+ )
378
+ ).v;
379
+ if (wantRoles) {
380
+ const { r } = await prompts(
381
+ {
382
+ type: "multiselect",
383
+ name: "r",
384
+ message: "Which roles should auth support?",
385
+ choices: ROLE_ORDER.map((k) => ({ title: k, value: k, selected: true })),
386
+ hint: "Space to toggle, Enter to confirm",
387
+ },
164
388
  { onCancel: () => process.exit(1) }
165
389
  );
390
+ roles = r || [];
391
+ }
392
+
393
+ const wantAccounts = (
394
+ await prompts(
395
+ {
396
+ type: "toggle",
397
+ name: "v",
398
+ message: roles.length
399
+ ? "Create demo accounts (one per selected role)?"
400
+ : "Create a normal demo account?",
401
+ initial: true,
402
+ active: "yes",
403
+ inactive: "no",
404
+ },
405
+ { onCancel: () => process.exit(1) }
406
+ )
407
+ ).v;
408
+ demoAccounts = Boolean(wantAccounts);
409
+
410
+ const wantData = (
411
+ await prompts(
412
+ {
413
+ type: "toggle",
414
+ name: "v",
415
+ message: "Create static demo data to test with?",
416
+ initial: true,
417
+ active: "yes",
418
+ inactive: "no",
419
+ },
420
+ { onCancel: () => process.exit(1) }
421
+ )
422
+ ).v;
423
+ if (wantData) {
424
+ const { e } = await prompts(
425
+ {
426
+ type: "multiselect",
427
+ name: "e",
428
+ message: "Which demo-data entities should the app ship with?",
429
+ choices: DEMO_ENTITIES.map((k) => ({ title: k, value: k, selected: true })),
430
+ hint: "Space to toggle, Enter to confirm",
431
+ },
432
+ { onCancel: () => process.exit(1) }
433
+ );
434
+ demoEntities = e || [];
435
+ }
436
+ }
166
437
 
167
438
  // The set of locales the app ships: English is always bundled, plus the
168
439
  // languages the user picked. `--no-rtl` forces Arabic out (LTR only).
@@ -238,14 +509,57 @@ async function main() {
238
509
  await fs.writeFile(appPath, app);
239
510
  }
240
511
 
512
+ // UserRole support: rewrite src/data/roles.js to the picked role set. The
513
+ // file always stays (Register/Profile import it), just emptied when off.
514
+ await fs.writeFile(path.join(targetDir, "src", "data", "roles.js"), buildRolesModule(roles || []));
515
+
516
+ // Demo accounts: static module with one account per role, or a single
517
+ // normal demo user when roles are off. Removed entirely when not wanted.
518
+ const demoAccountsPath = path.join(targetDir, "src", "data", "demoAccounts.js");
519
+ if (demoAccounts) {
520
+ await fs.writeFile(demoAccountsPath, buildDemoAccountsModule(roles || []));
521
+ } else {
522
+ await fs.remove(demoAccountsPath);
523
+ }
524
+
525
+ // Static demo data: keep only the selected entity modules. The whole demo
526
+ // dir is dropped when demo data wasn't picked (nothing imports it).
527
+ const demoDir = path.join(targetDir, "src", "data", "demo");
528
+ for (const entity of DEMO_ENTITIES) {
529
+ if (!demoEntities || !demoEntities.includes(entity)) {
530
+ await fs.remove(path.join(demoDir, `${entity}.js`));
531
+ }
532
+ }
533
+ if (!demoEntities || (await fs.readdir(demoDir).catch(() => [])).length === 0) {
534
+ await fs.remove(demoDir);
535
+ }
536
+
537
+ const demoAccountsSummary = demoAccounts
538
+ ? roles && roles.length
539
+ ? `${roles.length} (${roles.join(", ")})`
540
+ : "1 (normal demo user)"
541
+ : "none";
542
+
543
+ // Data mode: demo apps are wired fully to the bundled demo data (auth +
544
+ // profile/settings + entity lists) and need no backend; everything else
545
+ // goes straight to the Laravel API.
546
+ const dataMode = demoAccounts || (demoEntities && demoEntities.length) ? "demo" : "live";
547
+
241
548
  console.log(
242
549
  chalk.gray(` Languages wired: ${locales.map((l) => LOCALE_LABELS[l]).join(", ")}${hasRtl ? " (RTL default)" : ""}`)
243
550
  );
551
+ console.log(
552
+ chalk.gray(
553
+ ` Roles: ${roles && roles.length ? roles.join(", ") : "none"} | Demo accounts: ${demoAccountsSummary} | Demo data: ${demoEntities && demoEntities.length ? demoEntities.join(", ") : "none"}`
554
+ )
555
+ );
556
+ console.log(chalk.gray(` Data mode: ${dataMode}${dataMode === "demo" ? " (no backend needed)" : " (Laravel API)"}`));
244
557
 
245
558
  // Generate a real .env from .env.example with a fresh encryption key
246
559
  const envExamplePath = path.join(targetDir, ".env.example");
247
560
  let envContent = await fs.readFile(envExamplePath, "utf-8");
248
561
  envContent = envContent
562
+ .replace("__DATA_MODE__", dataMode)
249
563
  .replace("__API_BASE_URL__", answers.apiBaseUrl || "http://localhost:8000/api")
250
564
  .replace("__AES_KEY__", randomHex(32)) // 256-bit key, hex-encoded
251
565
  .replace("__AES_IV__", randomHex(16)); // 128-bit IV, hex-encoded
@@ -254,11 +568,17 @@ async function main() {
254
568
  console.log(chalk.green(`\n Scaffolded ./${projectName} successfully.\n`));
255
569
  console.log(
256
570
  chalk.gray(
257
- " Fill in VITE_API_KEY, VITE_ABLY_PUBLIC_KEY, Brevo backend routes, and review .env before shipping.\n"
571
+ " Fill in VITE_API_KEY, VITE_ABLY_PUBLIC_KEY, Brevo backend routes, and review .env before shipping."
258
572
  )
259
573
  );
574
+ if (dataMode === "demo") {
575
+ console.log(chalk.gray(" Data mode: demo — the app runs on bundled demo data (no API keys needed)."));
576
+ console.log(chalk.gray(" Flip VITE_DATA_MODE=live in .env to wire the app to your Laravel backend.\n"));
577
+ } else {
578
+ console.log(chalk.gray("\n Data mode: live — the app calls your Laravel backend.\n"));
579
+ }
260
580
 
261
- const runChoice = opts.yes
581
+ const runChoice = opts.yes || hasFeatureFlags
262
582
  ? "install"
263
583
  : (
264
584
  await prompts(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "falak-app-duo",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "description": "Vue 3 boilerplate scaffolder — Ably (realtime), Brevo, AES-256, Arabic/RTL, and a Laravel API client (API key + user token auth). No Firebase, no Groq/Paymob on the frontend (Groq is backend-only; Paymob dropped).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,5 +32,8 @@
32
32
  "ably",
33
33
  "realtime"
34
34
  ],
35
- "license": "MIT"
35
+ "license": "MIT",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
36
39
  }
@@ -1,5 +1,12 @@
1
+ # --- Data source ---
2
+ # demo: the app runs entirely on bundled demo data (auth, profile, settings and
3
+ # entity lists) with no backend required.
4
+ # live: every request goes to the Laravel API below.
5
+ # The scaffolder sets this to "demo" when demo accounts/demo data were selected.
6
+ VITE_DATA_MODE=__DATA_MODE__
7
+
1
8
  # --- Laravel API (Falak Bridge or your own backend) ---
2
- # Base URL of your Laravel API
9
+ # Base URL of your Laravel API (not used in demo mode)
3
10
  VITE_API_BASE_URL=__API_BASE_URL__
4
11
  # Static API key issued to this client app (sent as X-API-Key on every request)
5
12
  VITE_API_KEY=replace-with-your-app-api-key
@@ -9,16 +9,20 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
+ "@primeuix/themes": "^1.2.5",
12
13
  "ably": "^2.5.0",
13
14
  "axios": "^1.7.7",
14
15
  "crypto-js": "^4.2.0",
15
16
  "laravel-echo": "^1.16.1",
16
17
  "pinia": "^2.2.2",
18
+ "primeicons": "^7.0.0",
19
+ "primevue": "^4.5.5",
17
20
  "vue": "^3.5.0",
18
21
  "vue-i18n": "^9.14.0",
19
22
  "vue-router": "^4.4.5"
20
23
  },
21
24
  "devDependencies": {
25
+ "@vitejs/plugin-basic-ssl": "^1.2.0",
22
26
  "@vitejs/plugin-vue": "^5.1.4",
23
27
  "autoprefixer": "^10.4.20",
24
28
  "postcss": "^8.4.47",
@@ -0,0 +1,7 @@
1
+ export const appointments = [
2
+ { id: 1, title: "Design review", attendee: "Aya El-Amin", date: "2026-09-15", time: "10:00", status: "confirmed" },
3
+ { id: 2, title: "Stakeholder sync", attendee: "Nour Farouk", date: "2026-09-15", time: "14:30", status: "confirmed" },
4
+ { id: 3, title: "Intro call", attendee: "Layla Haddad", date: "2026-09-16", time: "09:15", status: "pending" },
5
+ { id: 4, title: "User interview", attendee: "Omar El-Sayed", date: "2026-09-17", time: "11:45", status: "confirmed" },
6
+ { id: 5, title: "Sprint retro", attendee: "Karim Nasser", date: "2026-09-18", time: "16:00", status: "cancelled" },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const customers = [
2
+ { id: 1, name: "Aya El-Amin", email: "aya@example.com", segment: "enterprise", orders: 14, lifetime_value: 3200, created_at: "2025-11-02" },
3
+ { id: 2, name: "Omar El-Sayed", email: "omar@example.com", segment: "retail", orders: 6, lifetime_value: 880, created_at: "2026-01-24" },
4
+ { id: 3, name: "Layla Haddad", email: "layla@example.com", segment: "retail", orders: 2, lifetime_value: 428, created_at: "2026-03-18" },
5
+ { id: 4, name: "Karim Nasser", email: "karim@example.com", segment: "wholesale", orders: 9, lifetime_value: 5410, created_at: "2025-09-15" },
6
+ { id: 5, name: "Nour Farouk", email: "nour@example.com", segment: "enterprise", orders: 21, lifetime_value: 9800, created_at: "2025-07-30" },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const events = [
2
+ { id: 1, title: "Product Launch 2026", date: "2026-10-12", venue: "Cairo Marriott", capacity: 300, booked: 214, status: "published" },
3
+ { id: 2, title: "Design Meetup", date: "2026-10-20", venue: "Riyadh Hub", capacity: 120, booked: 98, status: "published" },
4
+ { id: 3, title: "Founders Breakfast", date: "2026-11-02", venue: "Dubai Mall Lounge", capacity: 40, booked: 40, status: "sold_out" },
5
+ { id: 4, title: "CS Workshop", date: "2026-11-15", venue: "Online", capacity: 500, booked: 156, status: "draft" },
6
+ { id: 5, title: "Annual Gala", date: "2026-12-05", venue: "InterContinental", capacity: 800, booked: 512, status: "published" },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const invoices = [
2
+ { id: 2001, number: "INV-2001", customer: "Aya El-Amin", amount: 1240, due_date: "2026-09-30", status: "sent", issued_at: "2026-09-01" },
3
+ { id: 2002, number: "INV-2002", customer: "Karim Nasser", amount: 3410, due_date: "2026-10-05", status: "partial", issued_at: "2026-09-04" },
4
+ { id: 2003, number: "INV-2003", customer: "Layla Haddad", amount: 428, due_date: "2026-09-20", status: "overdue", issued_at: "2026-08-05" },
5
+ { id: 2004, number: "INV-2004", customer: "Nour Farouk", amount: 2280, due_date: "2026-10-12", status: "sent", issued_at: "2026-09-09" },
6
+ { id: 2005, number: "INV-2005", customer: "Omar El-Sayed", amount: 640, due_date: "2026-10-01", status: "paid", issued_at: "2026-08-28" },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const messages = [
2
+ { id: 1, sender: "Aya El-Amin", channel: "email", preview: "Meeting rescheduled to 3pm…", sent_at: "2026-09-10 09:14", read: true },
3
+ { id: 2, sender: "Support Bot", channel: "chat", preview: "Your ticket #2918 was resolved", sent_at: "2026-09-10 10:02", read: true },
4
+ { id: 3, sender: "Omar El-Sayed", channel: "sms", preview: "Can you review the PR when you have a second?", sent_at: "2026-09-11 12:47", read: false },
5
+ { id: 4, sender: "Finance Team", channel: "email", preview: "October payroll timeline attached", sent_at: "2026-09-11 14:30", read: false },
6
+ { id: 5, sender: "Layla Haddad", channel: "telegram", preview: "Lunch on Thursday?", sent_at: "2026-09-12 08:05", read: false },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const orders = [
2
+ { id: 1001, number: "ORD-1001", customer: "Aya El-Amin", status: "paid", items: 3, total: 184, placed_at: "2026-09-01" },
3
+ { id: 1002, number: "ORD-1002", customer: "Omar El-Sayed", status: "pending", items: 1, total: 89, placed_at: "2026-09-03" },
4
+ { id: 1003, number: "ORD-1003", customer: "Layla Haddad", status: "shipped", items: 2, total: 428, placed_at: "2026-09-05" },
5
+ { id: 1004, number: "ORD-1004", customer: "Karim Nasser", status: "cancelled", items: 5, total: 612, placed_at: "2026-09-08" },
6
+ { id: 1005, number: "ORD-1005", customer: "Nour Farouk", status: "refunded", items: 1, total: 74, placed_at: "2026-09-10" },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const payments = [
2
+ { id: 1, method: "visa", amount: 184, status: "succeeded", reference: "PAY-8821", paid_at: "2026-09-01" },
3
+ { id: 2, method: "paypal", amount: 428, status: "succeeded", reference: "PAY-8843", paid_at: "2026-09-05" },
4
+ { id: 3, method: "mastercard", amount: 612, status: "failed", reference: "PAY-8850", paid_at: "2026-09-08" },
5
+ { id: 4, method: "bank_transfer", amount: 1240, status: "processing", reference: "PAY-8877", paid_at: "2026-09-09" },
6
+ { id: 5, method: "cash", amount: 74, status: "refunded", reference: "PAY-8892", paid_at: "2026-09-12" },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const posts = [
2
+ { id: 1, title: "Welcome to the new dashboard", author: "Aya El-Amin", status: "published", views: 1240, published_at: "2026-08-01" },
3
+ { id: 2, title: "How we cut load times by 60%", author: "Omar El-Sayed", status: "published", views: 860, published_at: "2026-08-14" },
4
+ { id: 3, title: "Roadmap: Q4 2026", author: "Nour Farouk", status: "draft", views: 0, published_at: null },
5
+ { id: 4, title: "Better onboarding in 5 steps", author: "Layla Haddad", status: "review", views: 0, published_at: null },
6
+ { id: 5, title: "Security hardening guide", author: "Karim Nasser", status: "published", views: 2230, published_at: "2026-09-02" },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const products = [
2
+ { id: 1, name: "Aurora Lamp", sku: "AUR-001", category: "Home", price: 89, stock: 42, status: "in_stock" },
3
+ { id: 2, name: "Nimbus Headphones", sku: "NIM-014", category: "Audio", price: 149, stock: 12, status: "in_stock" },
4
+ { id: 3, name: "Terrace Chair", sku: "TER-022", category: "Furniture", price: 210, stock: 0, status: "out_of_stock" },
5
+ { id: 4, name: "Lumen Desk", sku: "LUM-008", category: "Furniture", price: 340, stock: 7, status: "in_stock" },
6
+ { id: 5, name: "Drift Backpack", sku: "DRI-031", category: "Travel", price: 74, stock: 96, status: "low" },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const projects = [
2
+ { id: 1, name: "Atlas Rebrand", owner: "Aya El-Amin", progress: 65, status: "active", deadline: "2026-11-01" },
3
+ { id: 2, name: "Mobile App v2", owner: "Omar El-Sayed", progress: 28, status: "active", deadline: "2026-12-15" },
4
+ { id: 3, name: "API V2 Migration", owner: "Karim Nasser", progress: 100, status: "done", deadline: "2026-09-01" },
5
+ { id: 4, name: "Marketing Site", owner: "Layla Haddad", progress: 0, status: "on_hold", deadline: "2027-01-20" },
6
+ { id: 5, name: "Automation Playbook", owner: "Nour Farouk", progress: 12, status: "active", deadline: "2026-10-30" },
7
+ ];
@@ -0,0 +1,7 @@
1
+ export const tasks = [
2
+ { id: 1, title: "Finalize landing page copy", assignee: "Aya El-Amin", priority: "high", status: "in_progress", due_date: "2026-09-18" },
3
+ { id: 2, title: "Fix checkout bug #412", assignee: "Omar El-Sayed", priority: "critical", status: "todo", due_date: "2026-09-16" },
4
+ { id: 3, title: "Update pricing page", assignee: "Layla Haddad", priority: "medium", status: "done", due_date: "2026-09-12" },
5
+ { id: 4, title: "Migrate database indexes", assignee: "Karim Nasser", priority: "high", status: "todo", due_date: "2026-09-22" },
6
+ { id: 5, title: "Prepare Q4 roadmap", assignee: "Nour Farouk", priority: "medium", status: "in_progress", due_date: "2026-09-30" },
7
+ ];
@@ -0,0 +1,9 @@
1
+ // Static demo data — users. Import in your own code, e.g.
2
+ // `import { users } from "@/data/demo/users";`
3
+ export const users = [
4
+ { id: 1, name: "Aya El-Amin", email: "aya@example.com", role: "admin", status: "active", joined_at: "2026-01-12" },
5
+ { id: 2, name: "Omar El-Sayed", email: "omar@example.com", role: "user", status: "active", joined_at: "2026-02-03" },
6
+ { id: 3, name: "Layla Haddad", email: "layla@example.com", role: "user", status: "pending", joined_at: "2026-03-18" },
7
+ { id: 4, name: "Karim Nasser", email: "karim@example.com", role: "user", status: "suspended", joined_at: "2026-04-09" },
8
+ { id: 5, name: "Nour Farouk", email: "nour@example.com", role: "master", status: "active", joined_at: "2026-05-27" },
9
+ ];