Browse Source

feat: add core UI components, layout elements, and features for Aqila panel

master
parent
commit
78acbc4393
  1. 22
      index.html
  2. 40
      plugins/add-data-locator.cjs
  3. 4
      plugins/add-data-locator.d.ts
  4. BIN
      public/apple-touch-icon.png
  5. 61
      public/aqila-emblem.svg
  6. BIN
      public/aqila-logo-bronze.png
  7. BIN
      public/aqila-logo-white.png
  8. BIN
      public/aqila-logo.png
  9. BIN
      public/favicon-16x16.png
  10. BIN
      public/favicon-32x32.png
  11. BIN
      public/favicon.ico
  12. BIN
      public/favicon.png
  13. 67
      public/favicon.svg
  14. 14
      src/app/App.tsx
  15. 84
      src/components/AqilaLogo.tsx
  16. 36
      src/components/layout/AppSidebar.tsx
  17. 54
      src/components/layout/ServerHealthTrigger.tsx
  18. 177
      src/components/layout/ServerStatusDialog.tsx
  19. 28
      src/components/layout/ThemeToggle.tsx
  20. 19
      src/components/layout/Topbar.tsx
  21. 2
      src/components/ui/dialog.tsx
  22. 4
      src/components/ui/sidebar.tsx
  23. 4
      src/components/ui/switch.tsx
  24. 625
      src/components/utils/dev-click-to-component.tsx
  25. 2
      src/config/navigation.ts
  26. 8
      src/config/site.ts
  27. 3
      src/features/auth/LoginPage.tsx
  28. 165
      src/features/dashboard/DashboardHome.tsx
  29. 32
      src/features/global-search/GlobalSearchTrigger.tsx
  30. 5
      src/icons.tsx
  31. 47
      src/index.css
  32. 3
      src/main.tsx
  33. 55
      src/services/auth.ts
  34. 36
      src/stores/server-health.ts
  35. 48
      src/stores/theme.ts
  36. 115
      vite.config.ts

22
index.html

@ -2,12 +2,24 @@
<html lang="fa" dir="rtl"> <html lang="fa" dir="rtl">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg?v=aqila2" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=aqila2" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=aqila2" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=aqila2" />
<link rel="shortcut icon" href="/favicon.ico?v=aqila2" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>داشبورد مدیریت</title>
<!-- پیکربندی محیطِ runtime: در پروداکشن توسط entrypoint کانتینر بازنویسی
می‌شود (window.__ENV__). در dev این استابِ خالی است و app به
import.meta.env (از .env) fallback می‌کند. باید قبل از باندل لود شود. -->
<title>پنل مدیریت عقیله</title>
<!-- اطمینان از اعمال و رفرش آنی فاوآیکون در کش مرورگر -->
<script>
(function() {
var v = 'aqila_' + Date.now();
var links = document.querySelectorAll("link[rel*='icon']");
links.forEach(function(l) {
var h = l.getAttribute('href');
if (h) l.setAttribute('href', h.split('?')[0] + '?v=' + v);
});
})();
</script>
<script src="/config.js"></script> <script src="/config.js"></script>
</head> </head>
<body> <body>

40
plugins/add-data-locator.cjs

@ -0,0 +1,40 @@
module.exports = function addDataLocator({ types: t }) {
return {
name: "add-data-locator",
visitor: {
JSXOpeningElement(path, state) {
if (process.env.NODE_ENV && process.env.NODE_ENV !== "development") {
return;
}
const filePath = state.file.opts.filename;
if (!filePath || filePath.includes("node_modules")) {
return;
}
const attributeExists = path.node.attributes.some(
(attribute) =>
t.isJSXAttribute(attribute) &&
t.isJSXIdentifier(attribute.name) &&
attribute.name.name === "data-locator",
);
if (attributeExists) {
return;
}
const lineNumber = path.node.loc?.start.line ?? "unknown";
const columnNumber = path.node.loc?.start.column ?? "unknown";
const locatorValue = `${filePath}:${lineNumber}:${columnNumber}`;
path.node.attributes.push(
t.jsxAttribute(
t.jsxIdentifier("data-locator"),
t.stringLiteral(locatorValue),
),
);
},
},
};
};

4
plugins/add-data-locator.d.ts

@ -0,0 +1,4 @@
import type { PluginObj } from '@babel/core';
declare function addDataLocator(api: { types: unknown }): PluginObj;
export default addDataLocator;

BIN
public/apple-touch-icon.png

After

Width: 180  |  Height: 180  |  Size: 7.6 KiB

61
public/aqila-emblem.svg

@ -0,0 +1,61 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-91.3 -30.1 60 60" fill="currentColor">
<g>
<g transform="matrix(1 0 0 1 -61.89 -5.77)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-20.9, -22.17)" d="M 2.78484 39.2933 C 1.73484 38.0333 1.45484 37.2633 1.31484 35.0933 C 1.03484 30.2633 3.41484 27.3233 12.7248 21.0933 C 15.8748 18.9933 18.8848 16.8233 19.5148 16.1933 L 20.6348 15.1433 L 23.2948 17.3133 C 24.7648 18.5033 27.9148 20.7433 30.2248 22.3533 C 35.5448 25.9233 38.2748 28.5833 39.6748 31.5233 C 40.5848 33.5533 40.7248 34.0433 40.3748 37.0533 C 40.2348 38.8733 39.8148 41.2533 39.5348 42.3033 C 39.1848 43.3533 38.9748 44.3333 39.0448 44.3333 C 39.3248 44.6133 40.5848 41.1133 41.2148 38.4533 C 43.1048 30.8233 40.5848 26.4833 30.4348 19.6933 C 22.8748 14.5833 20.7748 12.0633 20.7748 11.5733 L 20.7748 0.0933333 C 20.7748 0.0933333 20.2148 -0.116667 20.2148 0.0933333 C 20.2148 1.56333 20.2148 0.653333 19.4448 1.14333 C 18.7448 1.63333 17.6948 2.19333 17.9748 0.863333 C 18.1848 -0.0466667 17.4148 -0.186667 16.2248 0.583334 C 15.5248 0.933334 14.4748 1.56333 14.1948 1.91333 C 12.9348 2.96333 10.8348 3.52333 9.29484 3.52333 C 7.75484 3.52333 7.75484 3.87333 8.45484 4.50333 C 9.15484 5.13333 10.6248 7.02333 14.8948 5.97333 C 15.3848 5.83333 15.5948 5.97333 15.5948 6.67333 C 15.5948 7.37333 15.7348 7.58333 17.2748 7.37333 C 18.1848 7.23333 19.8648 6.39333 20.2148 6.25333 C 20.3548 6.25333 20.5648 6.25333 20.4948 6.74333 C 20.4948 6.88333 20.4948 8.07333 20.4948 8.07333 L 20.4948 11.5733 C 20.4948 12.2033 17.0648 14.9333 11.2548 19.2733 C 2.92484 25.2933 0.474838 28.9333 0.0548377 34.7433 C -0.0851623 37.4733 -0.0151621 37.8933 0.964838 39.3633" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -53.84 -2.75)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-28.95, -25.19)" d="M 26.7949 23.7532 C 25.6749 25.9932 25.6749 26.4832 26.4449 26.4832 C 27.2149 26.4832 27.6349 26.9732 28.3349 27.5332 L 29.6649 28.5832 L 30.6449 27.3932 C 31.7649 25.9932 32.1849 25.0832 31.9049 24.4532 C 31.6949 23.9632 28.6149 21.7932 28.0549 21.7932 C 27.4949 21.7932 27.2849 22.6332 26.7949 23.6832 L 26.7949 23.7532 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -61.74 -0.27)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-21.04, -27.67)" d="M 19.0948 25.7834 C 18.6048 26.7634 18.1848 27.8134 18.1848 28.0234 C 18.1848 28.2334 19.0248 29.0034 20.0748 29.5634 C 21.1248 30.1934 21.9648 30.8234 21.9648 31.1034 C 21.9648 31.8734 22.8748 30.9634 23.5048 29.5634 C 24.0648 28.3034 23.9948 27.9534 23.5048 26.9034 C 22.9448 25.8534 20.8448 23.9634 20.2148 23.9634 C 19.5848 23.9634 19.5848 24.7334 19.0948 25.7834 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -62.01 -20.5)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-20.77, -7.44)" d="M 20.7749 8.14341 C 21.2001 8.14341 21.5449 7.83001 21.5449 7.44341 C 21.5449 7.05681 21.2001 6.74341 20.7749 6.74341 C 20.3496 6.74341 20.0049 7.05681 20.0049 7.44341 C 20.0049 7.83001 20.3496 8.14341 20.7749 8.14341 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -62.01 -18.12)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-20.77, -9.82)" d="M 20.7749 10.8734 C 21.3548 10.8734 21.8249 10.4033 21.8249 9.82344 C 21.8249 9.24354 21.3548 8.77344 20.7749 8.77344 C 20.195 8.77344 19.7249 9.24354 19.7249 9.82344 C 19.7249 10.4033 20.195 10.8734 20.7749 10.8734 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -53.38 10.83)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-29.4, -38.77)" d="M 21.4049 43.3533 C 21.0549 43.8433 20.7749 44.2633 20.4949 44.6833 C 20.0049 45.5233 20.1449 46.3633 20.7049 47.1333 C 21.5449 48.1833 22.7349 48.6033 24.0649 48.7433 C 25.6749 48.8833 27.2149 48.4633 28.7549 47.9033 C 31.0649 47.0633 33.1649 45.8033 34.9849 44.1933 C 36.9449 42.5133 37.4349 40.6233 36.3149 37.6833 C 35.7549 36.2133 34.7749 35.0933 33.7249 33.9733 C 33.2349 33.4833 32.8149 32.9933 32.3249 32.5033 C 32.1849 32.3633 32.0449 32.1533 32.1849 31.9433 C 32.8849 30.8933 33.5149 29.8433 34.2149 28.7933 C 34.6349 28.2333 34.9849 28.2333 35.4049 28.7933 C 35.9649 29.4933 36.4549 30.1933 36.8749 30.9633 C 37.5049 32.0833 38.0649 33.2033 38.4849 34.3933 C 38.9749 35.6533 38.9749 37.0533 38.6249 38.3833 C 37.6449 42.0233 35.7549 45.1733 33.3049 48.0433 C 33.0249 48.3933 32.6049 48.6733 32.2549 48.9533 C 32.1149 49.0933 31.9049 49.1633 31.6949 49.1633 C 31.3449 49.1633 31.2049 49.0933 31.2749 48.7433 C 31.3449 48.3933 31.2049 48.2533 30.9249 48.1133 C 30.4349 47.9033 29.9449 48.0433 29.4549 48.1133 C 28.4049 48.3933 27.4249 48.7433 26.3749 48.8833 C 24.9749 49.0933 23.5749 49.0933 22.2449 48.6733 C 22.0349 48.6733 21.7549 48.5333 21.5449 48.4633 C 20.2849 47.9033 19.7249 46.9233 20.0049 45.5933 C 20.1449 44.6833 20.5649 43.9133 21.3349 43.2833 L 21.4049 43.3533 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -50.28 24.78)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-32.51, -52.72)" d="M 35.3348 55.8134 C 35.1248 54.4834 34.1448 53.9234 33.0948 53.4334 C 32.7448 53.2934 32.4648 53.3634 32.1848 53.5734 C 31.6948 54.0634 31.2048 54.6234 30.7148 55.1834 C 30.6448 55.2534 30.5748 55.3934 30.5048 55.4634 C 30.5048 55.3934 30.3648 55.3234 30.3648 55.2534 C 29.7348 54.2734 28.8248 53.7834 27.7048 53.5034 C 27.2148 53.3634 27.0748 53.2234 27.2148 52.6634 C 27.4248 51.6834 27.9848 50.9134 28.6148 50.2134 C 28.9648 49.8634 29.2448 49.7234 29.7348 49.9334 C 30.3648 50.2134 31.0648 50.4234 31.7648 50.7734 C 32.1148 50.9134 32.3948 51.2634 32.7448 51.5434 C 32.8148 51.4034 32.9548 51.1934 33.0948 50.9834 C 33.3748 50.6334 33.5848 50.2134 33.8648 49.8634 C 34.0748 49.5834 34.4248 49.4434 34.7748 49.6534 C 35.4048 50.0034 36.0348 50.2834 36.6648 50.6334 C 36.8748 50.7734 37.0148 50.9134 37.2248 51.1234 C 37.9248 51.8234 38.0648 52.5234 37.5048 53.3634 C 36.9448 54.2034 36.2448 54.9034 35.6148 55.7434 C 35.6148 55.7434 35.4748 55.8134 35.4048 55.8834 L 35.3348 55.8134 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -70.52 22.38)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-12.26, -50.32)" d="M 13.0749 53.2232 C 12.7949 52.3832 12.2349 52.0332 11.5349 51.8232 C 10.9749 51.6832 10.4149 51.5432 9.85489 51.4032 C 9.71489 51.4032 9.57489 51.3332 9.43489 51.2632 C 9.15489 51.1232 9.01489 50.9832 9.22489 50.7032 C 9.78489 49.7932 10.3449 48.8832 10.9049 47.9732 C 10.9749 47.8332 11.1149 47.6932 11.2549 47.6232 C 11.4649 47.4132 11.7449 47.3432 12.0249 47.4832 C 12.7949 47.8332 13.5649 48.1132 14.3349 48.4632 C 15.4549 49.0232 15.7349 50.2132 14.9649 51.1932 C 14.7549 51.4732 14.4749 51.7532 14.1949 52.0332 C 13.8449 52.3832 13.4949 52.8032 13.0749 53.2232 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -70.83 5.53)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-11.95, -33.47)" d="M 10.9048 33.6233 C 10.9048 33.8333 10.9748 34.0433 11.0448 34.1133 C 11.1148 34.2533 11.2548 34.2533 11.3948 34.3233 C 11.3948 34.1833 11.5348 34.0433 11.4648 33.9033 C 11.3248 33.2733 11.0448 32.6433 10.9748 32.0133 C 10.8348 31.1033 11.1848 30.4733 11.8848 30.2633 C 11.4648 31.1733 11.6048 31.6633 12.7248 32.7833 C 12.9348 32.5733 13.1448 32.4333 13.4248 32.2233 C 13.6348 32.0833 13.9148 31.8733 14.1948 31.6633 C 13.9148 31.6633 13.7048 31.6633 13.4948 31.5933 C 13.3548 31.0333 13.6348 30.3333 14.0548 30.2633 C 14.5448 30.1233 15.2448 30.4733 15.3148 30.8933 C 15.4548 31.5233 14.8948 32.2233 14.0548 32.3633 C 14.1948 32.7133 14.3348 33.0633 14.4748 33.4133 C 14.6148 33.7633 14.5448 34.1133 14.3348 34.4633 C 14.3348 34.4633 14.2648 34.5333 14.2648 34.6033 C 14.2648 34.8133 14.1948 35.0233 14.1948 35.3033 C 14.1948 35.3033 14.4048 35.3733 14.4048 35.3033 C 14.6148 35.1633 14.8948 34.9533 14.8948 34.7433 C 14.9648 34.3233 14.8948 33.8333 14.8948 33.4133 C 14.8948 32.9933 14.8948 32.5733 14.9648 32.1533 C 14.9648 32.1533 15.1048 32.1533 15.1748 32.1533 C 15.1748 32.2933 15.2448 32.5033 15.2448 32.6433 C 15.2448 33.2733 15.2448 33.9733 15.2448 34.6033 C 15.2448 35.3733 15.3848 35.5833 16.1548 35.5133 C 17.1348 35.5133 17.9748 35.1633 18.6048 34.4633 C 19.0248 34.0433 19.3748 33.5533 19.7948 33.0633 C 19.9348 32.9233 20.0748 32.7833 20.2148 32.6433 C 20.2148 32.6433 20.2848 32.6433 20.3548 32.7133 C 20.2848 32.9933 20.2148 33.3433 20.0748 33.4833 C 19.3048 34.3933 18.6048 35.3733 17.4848 35.7933 C 16.9948 36.0033 16.4348 36.0733 15.8748 36.1433 C 15.5948 36.1433 15.3148 36.0033 14.8948 35.8633 C 14.8948 35.8633 14.7548 36.0733 14.6148 36.1433 C 14.2648 36.4933 13.9148 36.4233 13.7748 35.9333 C 13.5648 35.4433 13.5648 34.8833 13.9148 34.4633 C 14.3348 33.9033 14.3348 33.4133 13.9148 32.8533 C 13.9148 32.7833 13.8448 32.6433 13.7748 32.5733 C 13.6348 32.6433 13.4948 32.7833 13.3548 32.8533 C 13.0748 33.0633 13.0048 33.2033 13.1448 33.5533 C 13.4248 34.4633 13.2148 35.3033 12.5848 36.0033 C 12.7248 36.0733 12.7948 36.2133 12.9348 36.2833 C 12.3748 35.9333 12.0248 36.2833 11.5348 36.4933 C 10.9048 36.7033 10.2748 36.8433 9.71481 36.4933 C 10.0648 36.4933 10.5548 36.6333 10.9748 36.4933 C 11.3948 36.4233 11.8148 36.1433 12.1648 35.9333 C 12.0248 35.5833 11.8848 35.3033 11.8148 35.0933 C 11.7448 34.8833 11.0448 34.7433 10.9048 34.8833 C 10.8348 34.8833 10.7648 35.0933 10.6948 35.0933 C 10.4148 35.0933 10.1348 35.2333 9.92481 35.1633 C 9.78481 35.0933 9.64481 34.7433 9.64481 34.5333 C 9.64481 34.1133 9.78481 33.6933 9.78481 33.2033 C 9.57481 33.4133 9.29481 33.6933 9.08481 33.9033 C 8.38481 34.6033 7.61481 35.1633 6.63481 35.5133 C 6.28481 35.6533 5.93481 35.7233 5.58481 35.7233 C 4.67481 35.7233 4.04481 35.3033 3.76481 34.4633 C 3.62481 33.9733 3.55481 33.4833 3.55481 32.9933 C 3.55481 31.6633 4.67481 30.8233 6.00481 30.9633 C 6.35481 30.9633 6.70481 31.0333 7.05481 31.1033 C 7.96481 31.1733 8.45481 31.1033 9.08481 30.5433 C 8.80481 31.1733 7.96481 31.5233 7.05481 31.4533 C 6.63481 31.4533 6.14481 31.3133 5.72481 31.3133 C 4.88481 31.2433 4.39481 31.6633 4.04481 32.3633 C 3.69481 32.9933 3.62481 33.6233 4.04481 34.2533 C 4.46481 34.8833 5.16481 35.0233 5.86481 34.9533 C 6.70481 34.8833 7.40481 34.3933 8.03481 33.9033 C 8.45481 33.5533 8.87481 33.1333 9.29481 32.7133 C 9.50481 32.5033 9.71481 32.2933 9.92481 32.1533 C 10.2048 31.9433 10.4148 32.0133 10.3448 32.3633 C 10.3448 32.7833 10.2048 33.1333 10.0648 33.5533 C 9.99481 33.8333 9.71481 34.2533 10.0648 34.4633 C 10.4148 34.6033 10.5548 34.1133 10.6948 33.8333 C 10.6948 33.8333 10.6948 33.6933 10.7648 33.5533 L 10.9048 33.6233 Z M 12.5148 33.7633 C 11.8848 34.5333 11.7448 35.3733 12.3048 35.7933 C 13.0048 35.2333 13.0748 34.6033 12.5148 33.7633 Z M 12.0248 33.6933 C 12.3748 33.2733 12.1648 32.9233 11.5348 32.5033 C 11.7448 32.9233 11.8848 33.3433 12.0248 33.6933 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -76.11 5.05)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-6.67, -32.99)" d="M 8.0348 32.7831 C 8.0348 32.5031 8.0348 32.3631 7.9648 32.1531 C 7.8248 32.1531 7.6848 32.1531 7.6848 32.2231 C 7.6848 32.4331 7.5448 32.6431 7.6848 32.8531 C 7.6848 32.9931 8.0348 33.0631 8.1748 33.0631 C 8.3848 33.0631 8.5248 32.8531 8.6648 32.7131 C 8.4548 33.4131 7.5448 33.6931 6.9848 33.3431 C 6.7748 33.3431 6.6348 33.4131 6.4948 33.4831 C 6.3548 33.4831 6.2148 33.6231 6.0748 33.6931 C 5.9348 33.8331 5.7948 33.9031 5.65481 34.0431 C 5.58481 33.9031 5.44481 33.7631 5.30481 33.5531 C 5.09481 33.5531 4.8848 33.5531 4.6748 33.5531 C 4.6748 33.3431 4.6748 33.0631 4.8148 32.8531 C 5.0248 32.5731 5.30481 32.3631 5.65481 32.1531 C 5.44481 32.3631 5.2348 32.5731 5.0248 32.7831 C 4.8148 32.9931 4.8148 33.2031 5.0948 33.3431 C 5.2348 33.0631 5.3748 32.8531 5.5148 32.7131 C 5.7248 32.9231 5.9348 33.1331 6.1448 33.4131 C 6.1448 33.3431 6.2848 33.2031 6.4248 33.0631 C 6.4248 33.2031 6.5648 33.2731 6.6348 33.3431 C 6.8448 32.8531 7.0548 32.4331 7.2648 31.9431 C 7.2648 31.9431 7.3348 31.9431 7.4048 31.9431 C 7.3348 32.1531 7.1948 32.3631 7.1248 32.5731 C 7.1248 32.7831 7.1248 32.9231 7.1948 33.1331 C 7.2648 32.8531 7.3348 32.5731 7.4048 32.2931 C 7.4048 32.1531 7.6148 32.0131 7.6848 32.0131 C 7.8948 32.0131 8.1048 32.0831 8.3148 32.1531 C 8.2448 32.3631 8.1048 32.5731 8.0348 32.8531 L 8.0348 32.7831 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -65.44 6.09)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-17.34, -34.03)" d="M 18.4648 33.4133 L 18.4648 33.8333 C 18.4648 33.8333 18.5348 33.9033 18.6048 33.9733 C 18.6048 33.8333 18.6748 33.6933 18.7448 33.5533 C 18.8148 33.6933 18.8848 33.7633 19.0248 33.9033 C 18.6748 33.7633 18.3948 34.3933 18.1848 33.8333 C 17.1348 34.5333 16.5048 34.7433 15.6648 34.6033 C 15.9448 34.6033 16.2948 34.5333 16.5748 34.4633 C 16.8548 34.3933 17.2048 34.2533 17.4848 34.0433 C 17.7648 33.9033 18.0448 33.6933 18.3948 33.4133 L 18.4648 33.4133 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -72.68 3.55)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-10.1, -31.49)" d="M 9.64481 31.0332 C 9.64481 31.2432 9.71481 31.4532 9.78481 31.5932 C 9.99481 31.5232 10.2048 31.4532 10.4148 31.3132 C 10.5548 31.3132 10.6948 31.2432 10.7648 31.2432 C 10.7648 31.4532 9.92481 32.0132 9.64481 31.9432 C 9.50481 31.9432 9.43481 31.6632 9.43481 31.5232 C 9.43481 31.3832 9.57481 31.1732 9.64481 31.0332 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -64.88 4.91)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-17.9, -32.85)" d="M 16.7148 33.5533 C 17.4848 33.0633 18.2548 32.6433 19.0248 32.1533 C 19.0248 32.1533 19.0248 32.1533 19.0948 32.1533 C 18.9548 32.2933 18.8848 32.4333 18.7448 32.5033 C 18.1148 32.8533 17.4148 33.2033 16.7148 33.5533 C 16.7148 33.5533 16.7148 33.5533 16.7148 33.4833 L 16.7148 33.5533 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -69.95 2.32)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-12.83, -30.26)" d="M 12.2349 30.8232 C 12.5149 30.6132 12.7949 30.4732 13.0049 30.3332 C 13.0049 30.1232 12.8649 29.9832 12.8649 29.8432 C 12.8649 29.7032 13.1449 29.7032 13.2849 29.5632 C 13.2849 29.9132 13.3549 30.1232 13.4249 30.3332 C 13.0749 30.5432 12.6549 30.7532 12.3049 30.9632 C 12.3049 30.9632 12.3049 30.9632 12.3049 30.8932 L 12.2349 30.8232 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -73.84 7.22)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-8.94, -35.16)" d="M 8.66485 35.1632 C 8.66485 35.1632 8.80485 35.1632 8.87485 35.1632 C 8.87485 35.0232 8.87485 34.9532 8.80485 34.8132 C 8.87485 34.8132 9.01485 34.8132 9.01485 34.8132 C 9.15485 34.9532 9.22485 35.1632 9.29485 35.3732 L 9.15485 35.5132 C 8.94485 35.5132 8.80485 35.4432 8.59485 35.3732 C 8.59485 35.3732 8.59485 35.3032 8.59485 35.2332 L 8.66485 35.1632 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -65.26 10.65)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-17.53, -38.59)" d="M 0.124878 37.6832 C 0.264878 38.5932 1.80488 40.5532 1.80488 40.5532 C 3.06488 42.0932 4.67488 43.1432 6.42488 44.0532 C 6.56488 44.0532 6.63488 44.1932 6.77488 44.1932 C 6.77488 44.1932 6.77488 44.1932 6.77488 44.2632 L 5.51488 43.9132 C 5.72488 44.1232 5.93488 44.1932 6.14488 44.2632 C 7.12488 44.6132 8.17488 44.6832 9.15488 44.6832 C 9.50488 44.6832 9.64488 44.9632 9.50488 45.3132 C 9.29488 45.8732 8.66488 46.2232 8.10488 46.2232 C 7.68488 46.2232 7.19488 46.2232 6.77488 46.2232 C 6.84488 46.2932 6.98488 46.3632 7.12488 46.3632 C 7.82488 46.3632 8.52488 46.3632 9.22488 46.3632 C 10.0649 46.3632 10.8349 46.3632 11.6749 46.2232 C 13.7749 45.8032 15.5949 44.8232 17.2749 43.5632 C 19.3749 42.0232 21.1249 40.2032 22.7349 38.1732 C 23.1549 37.6832 23.6449 37.2632 24.2049 36.9132 C 24.8349 36.4932 25.3949 36.7732 25.6049 37.4032 C 26.0249 38.5932 26.0949 39.7132 25.3249 40.7632 C 24.9049 41.3232 24.4849 41.9532 24.1349 42.5832 C 23.6449 43.4932 23.4349 44.4732 23.6449 45.5232 C 23.7849 46.4332 24.4149 46.8532 25.3249 46.6432 C 25.6749 46.5732 26.0249 46.4332 26.3049 46.2932 C 26.9349 45.9432 27.4949 45.5232 28.0549 45.1732 C 28.3349 44.9632 28.4749 44.9632 28.6849 45.2432 C 29.0349 45.6632 29.5249 45.8032 30.0149 45.8732 C 31.2049 46.0132 32.0449 45.3832 32.7449 44.6132 C 33.7949 43.4932 34.4249 42.1632 34.7749 40.6932 C 35.1249 39.2232 34.9149 37.8232 33.9349 36.5632 C 33.5849 36.0732 33.2349 35.5832 32.8849 35.1632 C 31.6949 33.6232 30.4349 32.0832 29.1749 30.5432 C 29.1749 30.4732 28.8949 30.4732 28.7549 30.5432 C 28.6149 30.5432 28.5449 30.7532 28.4749 30.8932 C 27.9149 31.9432 27.3549 32.9232 26.7949 33.9732 C 26.6549 34.1832 26.7249 34.3932 26.9349 34.5332 C 27.0049 34.5332 27.0749 34.6732 27.2149 34.8132 C 25.8149 34.3232 24.7649 34.8132 23.7849 35.6532 C 22.8049 36.4932 21.8949 37.3332 20.9149 38.1732 C 19.5149 39.3632 18.1149 40.4832 16.3649 41.1832 C 14.0549 42.0932 11.6749 42.7932 9.15488 42.7232 C 5.65488 42.7232 2.36488 39.0132 1.87488 37.8232 C 1.38488 36.6332 0.194878 37.6132 0.194878 37.6132 L 0.124878 37.6832 Z M 32.1849 42.3032 C 31.8349 42.4432 31.4849 42.6532 31.1349 42.3032 C 30.7849 42.0232 30.9949 41.6732 31.1349 41.3232 C 31.1349 41.1132 31.2049 40.9032 31.2049 40.6932 C 31.2049 40.5532 31.2049 40.4132 31.0649 40.4132 C 30.9949 40.4132 30.7849 40.4132 30.7149 40.5532 C 30.4349 40.8332 30.2249 41.1132 29.9449 41.4632 C 29.1749 42.4432 28.1949 42.9332 26.9349 42.7232 C 26.0249 42.5832 25.7449 42.0932 26.0249 41.1832 C 26.3049 40.3432 26.7949 39.6432 27.3549 39.0132 C 27.9149 38.3832 28.3349 37.6832 28.3349 36.8432 C 28.3349 36.6332 28.3349 36.3532 28.3349 36.0732 C 28.6149 36.2832 28.8949 36.4232 29.1749 36.5632 C 30.7849 37.6132 31.8349 39.0832 32.2549 40.9732 C 32.2549 41.2532 32.3949 41.5332 32.4649 41.8132 C 32.6049 42.0932 32.4649 42.1632 32.2549 42.2332 L 32.1849 42.3032 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -63.97 -6.87)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-18.81, -21.08)" d="M 16.6448 20.1134 C 16.6448 19.9034 16.7848 19.5534 16.9948 19.3434 C 17.2048 19.1334 17.6948 19.0634 17.9748 19.4834 C 18.1148 19.6934 18.2548 19.9034 18.3248 20.1834 C 18.4648 20.6034 18.6048 21.0234 18.6748 21.4434 C 18.6748 21.5834 18.6748 21.7234 18.7448 21.8634 C 18.7448 21.8634 18.7448 21.8634 18.8148 21.8634 C 18.8148 21.7934 18.8148 21.7234 18.8148 21.6534 C 18.8148 21.3734 18.8848 21.0234 18.9548 20.7434 C 18.9548 20.7434 18.9548 20.6034 18.9548 20.5334 C 19.0248 20.3234 19.0948 20.1834 19.1648 19.9734 C 19.1648 19.7634 19.2348 19.6234 19.3048 19.4134 C 19.4448 19.2734 19.5848 19.0634 19.7248 18.9934 C 20.0048 18.8534 20.2148 18.8534 20.4948 18.9934 C 20.7048 19.1334 20.8448 19.3434 20.9848 19.6234 C 20.9848 19.6234 20.9848 19.7634 20.9848 19.8334 C 20.8448 20.0434 20.7048 20.1834 20.6348 20.3934 C 20.5648 20.5334 20.4948 20.5334 20.4248 20.3934 C 20.2848 20.2534 20.2148 20.1134 20.0748 19.9734 C 20.0048 19.9034 19.9348 19.9034 19.8648 19.9734 C 19.8648 20.1134 19.7248 20.1834 19.7248 20.3234 C 19.5848 20.6734 19.5148 21.0934 19.4448 21.5134 C 19.4448 21.7234 19.3748 21.9334 19.3048 22.1434 C 19.3048 22.2834 19.2348 22.3534 19.1648 22.4934 C 18.9548 22.7734 18.7448 23.0534 18.4648 23.2634 C 18.4648 23.2634 18.4648 23.2634 18.3948 23.2634 C 18.3248 23.2634 18.3248 23.1934 18.3248 23.0534 C 18.3248 22.8434 18.3248 22.5634 18.3248 22.2834 C 18.3248 22.0734 18.2548 21.8634 18.1848 21.6534 C 18.0448 21.3734 17.9048 21.1634 17.7648 20.9534 C 17.7648 20.9534 17.7648 20.9534 17.6948 20.9534 C 17.6248 20.9534 17.4848 21.0934 17.4148 21.0934 C 17.4148 21.0934 17.3448 21.0934 17.2748 21.0934 C 17.0648 21.0234 16.8548 20.8834 16.7848 20.6734 C 16.7848 20.6034 16.7848 20.4634 16.7848 20.1834 L 16.6448 20.1134 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -74.43 10.83)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-8.35, -38.77)" d="M 6.63489 38.6634 C 6.63489 38.6634 6.77489 38.6634 6.84489 38.6634 C 6.98489 38.6634 7.12489 38.7334 7.19489 38.8034 C 7.40489 38.9434 7.68489 38.9434 7.89489 39.0134 C 8.10489 39.0134 8.24489 39.0134 8.45489 39.0134 C 8.59489 39.0134 8.66489 39.0134 8.73489 38.9434 C 8.87489 38.8734 8.94489 38.8034 9.08489 38.7334 C 9.22489 38.5934 9.22489 38.4534 9.22489 38.2434 C 9.22489 38.2434 9.22489 38.2434 9.22489 38.1734 C 9.22489 38.1034 9.22489 38.0334 9.36489 38.0334 C 9.50489 38.0334 9.64489 38.0334 9.71489 38.0334 C 9.99489 38.0334 10.0649 38.3134 10.0649 38.5234 C 10.0649 38.7334 10.0649 38.8734 9.92489 39.0834 C 9.71489 39.2934 9.43489 39.4334 9.08489 39.5034 C 8.73489 39.5034 8.38489 39.5034 8.03489 39.4334 C 7.82489 39.4334 7.61489 39.2934 7.47489 39.2234 C 7.26489 39.0834 7.05489 39.0134 6.84489 38.8734 C 6.77489 38.8734 6.70489 38.7334 6.70489 38.7334 L 6.63489 38.6634 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -74.78 0.4)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: currentColor; opacity: 1;" transform=" translate(-8, -28.34)" d="M 6.28479 28.2333 C 6.28479 28.2333 6.42479 28.2333 6.49479 28.2333 C 6.63479 28.2333 6.77479 28.3033 6.84479 28.3733 C 7.05479 28.5133 7.33479 28.5133 7.54479 28.5833 C 7.75479 28.5833 7.89479 28.5833 8.10479 28.5833 C 8.24479 28.5833 8.31479 28.5833 8.38479 28.5133 C 8.52479 28.4433 8.59479 28.3733 8.73479 28.3033 C 8.87479 28.1633 8.87479 28.0233 8.87479 27.8133 C 8.87479 27.8133 8.87479 27.8133 8.87479 27.7433 C 8.87479 27.6733 8.87479 27.6033 9.01479 27.6033 C 9.15479 27.6033 9.29479 27.6033 9.36479 27.6033 C 9.64479 27.6033 9.71479 27.8833 9.71479 28.0933 C 9.71479 28.3033 9.71479 28.4433 9.57479 28.6533 C 9.36479 28.8633 9.08479 29.0033 8.73479 29.0733 C 8.38479 29.0733 8.03479 29.0733 7.68479 29.0033 C 7.47479 29.0033 7.26479 28.8633 7.12479 28.7933 C 6.91479 28.6533 6.70479 28.5833 6.49479 28.4433 C 6.42479 28.4433 6.35479 28.3033 6.35479 28.3033 L 6.28479 28.2333 Z" stroke-linecap="round" />
</g>
</g>
</svg>

BIN
public/aqila-logo-bronze.png

After

Width: 256  |  Height: 335  |  Size: 39 KiB

BIN
public/aqila-logo-white.png

After

Width: 256  |  Height: 335  |  Size: 39 KiB

BIN
public/aqila-logo.png

After

Width: 256  |  Height: 335  |  Size: 54 KiB

BIN
public/favicon-16x16.png

After

Width: 16  |  Height: 16  |  Size: 608 B

BIN
public/favicon-32x32.png

After

Width: 32  |  Height: 32  |  Size: 1.1 KiB

BIN
public/favicon.ico

BIN
public/favicon.png

After

Width: 64  |  Height: 64  |  Size: 2.4 KiB

67
public/favicon.svg

@ -1,7 +1,64 @@
<svg xmlns="http://www.w3.org/2000/svg" width="38.72" height="40.277" viewBox="0 0 38.72 40.277">
<g id="par-04" transform="translate(-63.28 -49.67)">
<path id="Path_1621" data-name="Path 1621" d="M67.1,90.615h0a.855.855,0,0,1,1.047.6l8.35,30.79a.855.855,0,0,1-.6,1.047h0a.855.855,0,0,1-1.047-.6L66.5,91.662A.855.855,0,0,1,67.1,90.615Z" transform="translate(-2.58 -33.139)" fill="#fff"/>
<circle id="Ellipse_266" data-name="Ellipse 266" cx="1.31" cy="1.31" r="1.31" transform="translate(63.28 56.429)" fill="#fff"/>
<path id="Path_1622" data-name="Path 1622" d="M110.17,49.67s-3.357,6.4-15.648,4.624S80.33,58.983,80.33,58.983l5.449,20.652s3.041-6.97,15.332-4.814,14.7-4.434,14.7-4.434ZM91.063,65.955c.046-.156.12-.4.211-.709.12.055.258.12.433.19a2.281,2.281,0,0,1,.4.22.95.95,0,0,1,.23.3.739.739,0,0,1,.065.359,2.026,2.026,0,0,1-.074.479,1.653,1.653,0,0,1-.211.515.464.464,0,0,1-.19.19.6.6,0,0,1-.258-.084c-.139-.065-.268-.12-.38-.175s-.211-.1-.295-.148-.129-.091-.139-.139C90.962,66.458,91.017,66.111,91.063,65.955Zm7.711,3.231c-.12.323-.23.5-.314.515s-.19-.036-.38-.148a4.39,4.39,0,0,0-.5-.258c-.139.654-.295,1-.46,1.042a.35.35,0,0,1-.2-.055c-.091-.046-.23-.11-.424-.19s-.295-.148-.295-.175a2.223,2.223,0,0,1,.129-.783,2.42,2.42,0,0,1,.239-.737c.084-.055.23-.029.443.091a2.979,2.979,0,0,1,.507.34c.065-.2.129-.424.2-.673s.129-.38.175-.4a.771.771,0,0,1,.553.165.977.977,0,0,1,.433.469,1.294,1.294,0,0,1-.11.794Zm3.463-12.544a2.392,2.392,0,0,1-.036.534.694.694,0,0,1-.148.34c-.029.029-.074.074-.129.12a2.486,2.486,0,0,0-.239.285q-.165.207-.608.857c-.295.433-.627.884-.986,1.355l-.015-.019v.019l-.074.1a.475.475,0,0,1-.165.165c-.074.046-.12.084-.148.1a.852.852,0,0,0-.139.156c-.036.055-.091.12-.148.19a.262.262,0,0,1-.165.12.49.49,0,0,0-.19.12.829.829,0,0,0-.15.241.9.9,0,0,1-.3.331c-.22.036-.34.055-.369.065-.156.036-.323.314-.488.819a1.676,1.676,0,0,1-.38.644,1.076,1.076,0,0,1-.4.255h-.036a.907.907,0,0,1-.57-.044,1.234,1.234,0,0,1-.525-.443,1.823,1.823,0,0,1-.344.654,2.887,2.887,0,0,1-.813.595v.044a2.821,2.821,0,0,1,.07.43c.015.137.027.27.036.4a10.307,10.307,0,0,1-.017,2.2,8.492,8.492,0,0,1-.469,1.9,3.947,3.947,0,0,1-2.661,2.57,2.121,2.121,0,0,1-1.65-.239,2.387,2.387,0,0,1-.994-1.456,7.493,7.493,0,0,1,.156-3.815c.074-.295.139-.4.19-.314a.5.5,0,0,1,.065.285,5.977,5.977,0,0,0-.1,2.617,1.562,1.562,0,0,0,.867,1.057,2.248,2.248,0,0,0,1.446.055,4.094,4.094,0,0,0,1.851-1.059,3.912,3.912,0,0,0,.838-1.483,4.72,4.72,0,0,0-.129-2.893,3.633,3.633,0,0,0-.165-.443,4.311,4.311,0,0,1-.165-.443.439.439,0,0,1,.046-.4.393.393,0,0,1,.3-.165h0v-.01a2.454,2.454,0,0,0,.608-.12,1.3,1.3,0,0,0,.848-1.422c.029-.22.084-.331.165-.35s.156.084.211.285a1.357,1.357,0,0,0,.091.2,1.225,1.225,0,0,0,.129.175.686.686,0,0,0,.156.129.363.363,0,0,0,.19.055,1.062,1.062,0,0,0,.711-.22,2.494,2.494,0,0,0,.57-.6,1.315,1.315,0,0,1,.369-.414,1,1,0,0,1,.2-.036,1.4,1.4,0,0,0,.211-.019.435.435,0,0,0,.285-.258,1.873,1.873,0,0,1,.139-.276.355.355,0,0,0,.268-.036.7.7,0,0,0,.19-.369.266.266,0,0,1,.074-.19,2.108,2.108,0,0,0,.23-.046l.084-.074h0v-.015c.359-.424.76-.967,1.216-1.621a4.291,4.291,0,0,0-.534-.1,2.011,2.011,0,0,0-.673,0c-.165.046-.331.22-.507.544q-.055.1-.11.084c-.036,0-.065-.046-.065-.11a.868.868,0,0,1,.046-.295,5.987,5.987,0,0,1,.22-.608.671.671,0,0,1,.57-.433c.046,0,.331,0,.857.019a2.357,2.357,0,0,0,.589-.046,1.04,1.04,0,0,0,.314-.165c.065-.055.165-.148.3-.285.055-.019.084,0,.091.046a.45.45,0,0,1,.011.165Zm1.834,8.439a2.864,2.864,0,0,1-.515-1.14l-.015-.048h0c-.1-.414-.2-1-.314-1.77s-.19-1.621-.268-2.56c.019-.285.074-.793.156-1.521,0-.074.036-.1.065-.1a.078.078,0,0,1,.065.065c.074.331.165.967.285,1.916s.211,1.595.268,1.944.1.534.11.57a1.016,1.016,0,0,1,.017.1l.038.148a1.143,1.143,0,0,0,.6.848.57.57,0,0,0,.46-.019.515.515,0,0,0,.276-.314,2.266,2.266,0,0,0,.091-.46c0-.175.046-.268.091-.276s.091.036.11.156a4.832,4.832,0,0,1,.065.977,3.193,3.193,0,0,1-.19,1.178.933.933,0,0,1-.6.618.777.777,0,0,1-.794-.316Zm3.132,1.935c-.12.323-.23.5-.314.515s-.19-.036-.38-.148a4.392,4.392,0,0,0-.5-.258c-.139.654-.295,1-.46,1.042a.35.35,0,0,1-.2-.055c-.091-.046-.23-.11-.424-.19s-.3-.139-.314-.175a2.779,2.779,0,0,1,.148-.783,2.42,2.42,0,0,1,.239-.737c.084-.055.23-.029.443.091a2.98,2.98,0,0,1,.507.34c.065-.2.129-.424.2-.673s.129-.38.175-.4a.771.771,0,0,1,.553.165.977.977,0,0,1,.433.469,1.294,1.294,0,0,1-.11.791Z" transform="translate(-13.809)" fill="#fff"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-96.3 -35.1 70 70" width="100%" height="100%">
<!-- Bronze badge circle for Aqila brand identity -->
<circle cx="-61.28" cy="-0.1" r="33.5" fill="#D09460" />
<!-- White calligraphy dome & flag emblem -->
<g fill="#FFFFFF">
<g transform="matrix(1 0 0 1 -61.89 -5.77)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-20.9, -22.17)" d="M 2.78484 39.2933 C 1.73484 38.0333 1.45484 37.2633 1.31484 35.0933 C 1.03484 30.2633 3.41484 27.3233 12.7248 21.0933 C 15.8748 18.9933 18.8848 16.8233 19.5148 16.1933 L 20.6348 15.1433 L 23.2948 17.3133 C 24.7648 18.5033 27.9148 20.7433 30.2248 22.3533 C 35.5448 25.9233 38.2748 28.5833 39.6748 31.5233 C 40.5848 33.5533 40.7248 34.0433 40.3748 37.0533 C 40.2348 38.8733 39.8148 41.2533 39.5348 42.3033 C 39.1848 43.3533 38.9748 44.3333 39.0448 44.3333 C 39.3248 44.6133 40.5848 41.1133 41.2148 38.4533 C 43.1048 30.8233 40.5848 26.4833 30.4348 19.6933 C 22.8748 14.5833 20.7748 12.0633 20.7748 11.5733 L 20.7748 0.0933333 C 20.7748 0.0933333 20.2148 -0.116667 20.2148 0.0933333 C 20.2148 1.56333 20.2148 0.653333 19.4448 1.14333 C 18.7448 1.63333 17.6948 2.19333 17.9748 0.863333 C 18.1848 -0.0466667 17.4148 -0.186667 16.2248 0.583334 C 15.5248 0.933334 14.4748 1.56333 14.1948 1.91333 C 12.9348 2.96333 10.8348 3.52333 9.29484 3.52333 C 7.75484 3.52333 7.75484 3.87333 8.45484 4.50333 C 9.15484 5.13333 10.6248 7.02333 14.8948 5.97333 C 15.3848 5.83333 15.5948 5.97333 15.5948 6.67333 C 15.5948 7.37333 15.7348 7.58333 17.2748 7.37333 C 18.1848 7.23333 19.8648 6.39333 20.2148 6.25333 C 20.3548 6.25333 20.5648 6.25333 20.4948 6.74333 C 20.4948 6.88333 20.4948 8.07333 20.4948 8.07333 L 20.4948 11.5733 C 20.4948 12.2033 17.0648 14.9333 11.2548 19.2733 C 2.92484 25.2933 0.474838 28.9333 0.0548377 34.7433 C -0.0851623 37.4733 -0.0151621 37.8933 0.964838 39.3633" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -53.84 -2.75)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-28.95, -25.19)" d="M 26.7949 23.7532 C 25.6749 25.9932 25.6749 26.4832 26.4449 26.4832 C 27.2149 26.4832 27.6349 26.9732 28.3349 27.5332 L 29.6649 28.5832 L 30.6449 27.3932 C 31.7649 25.9932 32.1849 25.0832 31.9049 24.4532 C 31.6949 23.9632 28.6149 21.7932 28.0549 21.7932 C 27.4949 21.7932 27.2849 22.6332 26.7949 23.6832 L 26.7949 23.7532 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -61.74 -0.27)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-21.04, -27.67)" d="M 19.0948 25.7834 C 18.6048 26.7634 18.1848 27.8134 18.1848 28.0234 C 18.1848 28.2334 19.0248 29.0034 20.0748 29.5634 C 21.1248 30.1934 21.9648 30.8234 21.9648 31.1034 C 21.9648 31.8734 22.8748 30.9634 23.5048 29.5634 C 24.0648 28.3034 23.9948 27.9534 23.5048 26.9034 C 22.9448 25.8534 20.8448 23.9634 20.2148 23.9634 C 19.5848 23.9634 19.5848 24.7334 19.0948 25.7834 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -62.01 -20.5)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-20.77, -7.44)" d="M 20.7749 8.14341 C 21.2001 8.14341 21.5449 7.83001 21.5449 7.44341 C 21.5449 7.05681 21.2001 6.74341 20.7749 6.74341 C 20.3496 6.74341 20.0049 7.05681 20.0049 7.44341 C 20.0049 7.83001 20.3496 8.14341 20.7749 8.14341 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -62.01 -18.12)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-20.77, -9.82)" d="M 20.7749 10.8734 C 21.3548 10.8734 21.8249 10.4033 21.8249 9.82344 C 21.8249 9.24354 21.3548 8.77344 20.7749 8.77344 C 20.195 8.77344 19.7249 9.24354 19.7249 9.82344 C 19.7249 10.4033 20.195 10.8734 20.7749 10.8734 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -53.38 10.83)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-29.4, -38.77)" d="M 21.4049 43.3533 C 21.0549 43.8433 20.7749 44.2633 20.4949 44.6833 C 20.0049 45.5233 20.1449 46.3633 20.7049 47.1333 C 21.5449 48.1833 22.7349 48.6033 24.0649 48.7433 C 25.6749 48.8833 27.2149 48.4633 28.7549 47.9033 C 31.0649 47.0633 33.1649 45.8033 34.9849 44.1933 C 36.9449 42.5133 37.4349 40.6233 36.3149 37.6833 C 35.7549 36.2133 34.7749 35.0933 33.7249 33.9733 C 33.2349 33.4833 32.8149 32.9933 32.3249 32.5033 C 32.1849 32.3633 32.0449 32.1533 32.1849 31.9433 C 32.8849 30.8933 33.5149 29.8433 34.2149 28.7933 C 34.6349 28.2333 34.9849 28.2333 35.4049 28.7933 C 35.9649 29.4933 36.4549 30.1933 36.8749 30.9633 C 37.5049 32.0833 38.0649 33.2033 38.4849 34.3933 C 38.9749 35.6533 38.9749 37.0533 38.6249 38.3833 C 37.6449 42.0233 35.7549 45.1733 33.3049 48.0433 C 33.0249 48.3933 32.6049 48.6733 32.2549 48.9533 C 32.1149 49.0933 31.9049 49.1633 31.6949 49.1633 C 31.3449 49.1633 31.2049 49.0933 31.2749 48.7433 C 31.3449 48.3933 31.2049 48.2533 30.9249 48.1133 C 30.4349 47.9033 29.9449 48.0433 29.4549 48.1133 C 28.4049 48.3933 27.4249 48.7433 26.3749 48.8833 C 24.9749 49.0933 23.5749 49.0933 22.2449 48.6733 C 22.0349 48.6733 21.7549 48.5333 21.5449 48.4633 C 20.2849 47.9033 19.7249 46.9233 20.0049 45.5933 C 20.1449 44.6833 20.5649 43.9133 21.3349 43.2833 L 21.4049 43.3533 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -50.28 24.78)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-32.51, -52.72)" d="M 35.3348 55.8134 C 35.1248 54.4834 34.1448 53.9234 33.0948 53.4334 C 32.7448 53.2934 32.4648 53.3634 32.1848 53.5734 C 31.6948 54.0634 31.2048 54.6234 30.7148 55.1834 C 30.6448 55.2534 30.5748 55.3934 30.5048 55.4634 C 30.5048 55.3934 30.3648 55.3234 30.3648 55.2534 C 29.7348 54.2734 28.8248 53.7834 27.7048 53.5034 C 27.2148 53.3634 27.0748 53.2234 27.2148 52.6634 C 27.4248 51.6834 27.9848 50.9134 28.6148 50.2134 C 28.9648 49.8634 29.2448 49.7234 29.7348 49.9334 C 30.3648 50.2134 31.0648 50.4234 31.7648 50.7734 C 32.1148 50.9134 32.3948 51.2634 32.7448 51.5434 C 32.8148 51.4034 32.9548 51.1934 33.0948 50.9834 C 33.3748 50.6334 33.5848 50.2134 33.8648 49.8634 C 34.0748 49.5834 34.4248 49.4434 34.7748 49.6534 C 35.4048 50.0034 36.0348 50.2834 36.6648 50.6334 C 36.8748 50.7734 37.0148 50.9134 37.2248 51.1234 C 37.9248 51.8234 38.0648 52.5234 37.5048 53.3634 C 36.9448 54.2034 36.2448 54.9034 35.6148 55.7434 C 35.6148 55.7434 35.4748 55.8134 35.4048 55.8834 L 35.3348 55.8134 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -70.52 22.38)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-12.26, -50.32)" d="M 13.0749 53.2232 C 12.7949 52.3832 12.2349 52.0332 11.5349 51.8232 C 10.9749 51.6832 10.4149 51.5432 9.85489 51.4032 C 9.71489 51.4032 9.57489 51.3332 9.43489 51.2632 C 9.15489 51.1232 9.01489 50.9832 9.22489 50.7032 C 9.78489 49.7932 10.3449 48.8832 10.9049 47.9732 C 10.9749 47.8332 11.1149 47.6932 11.2549 47.6232 C 11.4649 47.4132 11.7449 47.3432 12.0249 47.4832 C 12.7949 47.8332 13.5649 48.1132 14.3349 48.4632 C 15.4549 49.0232 15.7349 50.2132 14.9649 51.1932 C 14.7549 51.4732 14.4749 51.7532 14.1949 52.0332 C 13.8449 52.3832 13.4949 52.8032 13.0749 53.2232 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -70.83 5.53)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-11.95, -33.47)" d="M 10.9048 33.6233 C 10.9048 33.8333 10.9748 34.0433 11.0448 34.1133 C 11.1148 34.2533 11.2548 34.2533 11.3948 34.3233 C 11.3948 34.1833 11.5348 34.0433 11.4648 33.9033 C 11.3248 33.2733 11.0448 32.6433 10.9748 32.0133 C 10.8348 31.1033 11.1848 30.4733 11.8848 30.2633 C 11.4648 31.1733 11.6048 31.6633 12.7248 32.7833 C 12.9348 32.5733 13.1448 32.4333 13.4248 32.2233 C 13.6348 32.0833 13.9148 31.8733 14.1948 31.6633 C 13.9148 31.6633 13.7048 31.6633 13.4948 31.5933 C 13.3548 31.0333 13.6348 30.3333 14.0548 30.2633 C 14.5448 30.1233 15.2448 30.4733 15.3148 30.8933 C 15.4548 31.5233 14.8948 32.2233 14.0548 32.3633 C 14.1948 32.7133 14.3348 33.0633 14.4748 33.4133 C 14.6148 33.7633 14.5448 34.1133 14.3348 34.4633 C 14.3348 34.4633 14.2648 34.5333 14.2648 34.6033 C 14.2648 34.8133 14.1948 35.0233 14.1948 35.3033 C 14.1948 35.3033 14.4048 35.3733 14.4048 35.3033 C 14.6148 35.1633 14.8948 34.9533 14.8948 34.7433 C 14.9648 34.3233 14.8948 33.8333 14.8948 33.4133 C 14.8948 32.9933 14.8948 32.5733 14.9648 32.1533 C 14.9648 32.1533 15.1048 32.1533 15.1748 32.1533 C 15.1748 32.2933 15.2448 32.5033 15.2448 32.6433 C 15.2448 33.2733 15.2448 33.9733 15.2448 34.6033 C 15.2448 35.3733 15.3848 35.5833 16.1548 35.5133 C 17.1348 35.5133 17.9748 35.1633 18.6048 34.4633 C 19.0248 34.0433 19.3748 33.5533 19.7948 33.0633 C 19.9348 32.9233 20.0748 32.7833 20.2148 32.6433 C 20.2148 32.6433 20.2848 32.6433 20.3548 32.7133 C 20.2848 32.9933 20.2148 33.3433 20.0748 33.4833 C 19.3048 34.3933 18.6048 35.3733 17.4848 35.7933 C 16.9948 36.0033 16.4348 36.0733 15.8748 36.1433 C 15.5948 36.1433 15.3148 36.0033 14.8948 35.8633 C 14.8948 35.8633 14.7548 36.0733 14.6148 36.1433 C 14.2648 36.4933 13.9148 36.4233 13.7748 35.9333 C 13.5648 35.4433 13.5648 34.8833 13.9148 34.4633 C 14.3348 33.9033 14.3348 33.4133 13.9148 32.8533 C 13.9148 32.7833 13.8448 32.6433 13.7748 32.5733 C 13.6348 32.6433 13.4948 32.7833 13.3548 32.8533 C 13.0748 33.0633 13.0048 33.2033 13.1448 33.5533 C 13.4248 34.4633 13.2148 35.3033 12.5848 36.0033 C 12.7248 36.0733 12.7948 36.2133 12.9348 36.2833 C 12.3748 35.9333 12.0248 36.2833 11.5348 36.4933 C 10.9048 36.7033 10.2748 36.8433 9.71481 36.4933 C 10.0648 36.4933 10.5548 36.6333 10.9748 36.4933 C 11.3948 36.4233 11.8148 36.1433 12.1648 35.9333 C 12.0248 35.5833 11.8848 35.3033 11.8148 35.0933 C 11.7448 34.8833 11.0448 34.7433 10.9048 34.8833 C 10.8348 34.8833 10.7648 35.0933 10.6948 35.0933 C 10.4148 35.0933 10.1348 35.2333 9.92481 35.1633 C 9.78481 35.0933 9.64481 34.7433 9.64481 34.5333 C 9.64481 34.1133 9.78481 33.6933 9.78481 33.2033 C 9.57481 33.4133 9.29481 33.6933 9.08481 33.9033 C 8.38481 34.6033 7.61481 35.1633 6.63481 35.5133 C 6.28481 35.6533 5.93481 35.7233 5.58481 35.7233 C 4.67481 35.7233 4.04481 35.3033 3.76481 34.4633 C 3.62481 33.9733 3.55481 33.4833 3.55481 32.9933 C 3.55481 31.6633 4.67481 30.8233 6.00481 30.9633 C 6.35481 30.9633 6.70481 31.0333 7.05481 31.1033 C 7.96481 31.1733 8.45481 31.1033 9.08481 30.5433 C 8.80481 31.1733 7.96481 31.5233 7.05481 31.4533 C 6.63481 31.4533 6.14481 31.3133 5.72481 31.3133 C 4.88481 31.2433 4.39481 31.6633 4.04481 32.3633 C 3.69481 32.9933 3.62481 33.6233 4.04481 34.2533 C 4.46481 34.8833 5.16481 35.0233 5.86481 34.9533 C 6.70481 34.8833 7.40481 34.3933 8.03481 33.9033 C 8.45481 33.5533 8.87481 33.1333 9.29481 32.7133 C 9.50481 32.5033 9.71481 32.2933 9.92481 32.1533 C 10.2048 31.9433 10.4148 32.0133 10.3448 32.3633 C 10.3448 32.7833 10.2048 33.1333 10.0648 33.5533 C 9.99481 33.8333 9.71481 34.2533 10.0648 34.4633 C 10.4148 34.6033 10.5548 34.1133 10.6948 33.8333 C 10.6948 33.8333 10.6948 33.6933 10.7648 33.5533 L 10.9048 33.6233 Z M 12.5148 33.7633 C 11.8848 34.5333 11.7448 35.3733 12.3048 35.7933 C 13.0048 35.2333 13.0748 34.6033 12.5148 33.7633 Z M 12.0248 33.6933 C 12.3748 33.2733 12.1648 32.9233 11.5348 32.5033 C 11.7448 32.9233 11.8848 33.3433 12.0248 33.6933 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -76.11 5.05)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-6.67, -32.99)" d="M 8.0348 32.7831 C 8.0348 32.5031 8.0348 32.3631 7.9648 32.1531 C 7.8248 32.1531 7.6848 32.1531 7.6848 32.2231 C 7.6848 32.4331 7.5448 32.6431 7.6848 32.8531 C 7.6848 32.9931 8.0348 33.0631 8.1748 33.0631 C 8.3848 33.0631 8.5248 32.8531 8.6648 32.7131 C 8.4548 33.4131 7.5448 33.6931 6.9848 33.3431 C 6.7748 33.3431 6.6348 33.4131 6.4948 33.4831 C 6.3548 33.4831 6.2148 33.6231 6.0748 33.6931 C 5.9348 33.8331 5.7948 33.9031 5.65481 34.0431 C 5.58481 33.9031 5.44481 33.7631 5.30481 33.5531 C 5.09481 33.5531 4.8848 33.5531 4.6748 33.5531 C 4.6748 33.3431 4.6748 33.0631 4.8148 32.8531 C 5.0248 32.5731 5.30481 32.3631 5.65481 32.1531 C 5.44481 32.3631 5.2348 32.5731 5.0248 32.7831 C 4.8148 32.9931 4.8148 33.2031 5.0948 33.3431 C 5.2348 33.0631 5.3748 32.8531 5.5148 32.7131 C 5.7248 32.9231 5.9348 33.1331 6.1448 33.4131 C 6.1448 33.3431 6.2848 33.2031 6.4248 33.0631 C 6.4248 33.2031 6.5648 33.2731 6.6348 33.3431 C 6.8448 32.8531 7.0548 32.4331 7.2648 31.9431 C 7.2648 31.9431 7.3348 31.9431 7.4048 31.9431 C 7.3348 32.1531 7.1948 32.3631 7.1248 32.5731 C 7.1248 32.7831 7.1248 32.9231 7.1948 33.1331 C 7.2648 32.8531 7.3348 32.5731 7.4048 32.2931 C 7.4048 32.1531 7.6148 32.0131 7.6848 32.0131 C 7.8948 32.0131 8.1048 32.0831 8.3148 32.1531 C 8.2448 32.3631 8.1048 32.5731 8.0348 32.8531 L 8.0348 32.7831 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -65.44 6.09)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-17.34, -34.03)" d="M 18.4648 33.4133 L 18.4648 33.8333 C 18.4648 33.8333 18.5348 33.9033 18.6048 33.9733 C 18.6048 33.8333 18.6748 33.6933 18.7448 33.5533 C 18.8148 33.6933 18.8848 33.7633 19.0248 33.9033 C 18.6748 33.7633 18.3948 34.3933 18.1848 33.8333 C 17.1348 34.5333 16.5048 34.7433 15.6648 34.6033 C 15.9448 34.6033 16.2948 34.5333 16.5748 34.4633 C 16.8548 34.3933 17.2048 34.2533 17.4848 34.0433 C 17.7648 33.9033 18.0448 33.6933 18.3948 33.4133 L 18.4648 33.4133 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -72.68 3.55)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-10.1, -31.49)" d="M 9.64481 31.0332 C 9.64481 31.2432 9.71481 31.4532 9.78481 31.5932 C 9.99481 31.5232 10.2048 31.4532 10.4148 31.3132 C 10.5548 31.3132 10.6948 31.2432 10.7648 31.2432 C 10.7648 31.4532 9.92481 32.0132 9.64481 31.9432 C 9.50481 31.9432 9.43481 31.6632 9.43481 31.5232 C 9.43481 31.3832 9.57481 31.1732 9.64481 31.0332 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -64.88 4.91)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-17.9, -32.85)" d="M 16.7148 33.5533 C 17.4848 33.0633 18.2548 32.6433 19.0248 32.1533 C 19.0248 32.1533 19.0248 32.1533 19.0948 32.1533 C 18.9548 32.2933 18.8848 32.4333 18.7448 32.5033 C 18.1148 32.8533 17.4148 33.2033 16.7148 33.5533 C 16.7148 33.5533 16.7148 33.5533 16.7148 33.4833 L 16.7148 33.5533 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -69.95 2.32)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-12.83, -30.26)" d="M 12.2349 30.8232 C 12.5149 30.6132 12.7949 30.4732 13.0049 30.3332 C 13.0049 30.1232 12.8649 29.9832 12.8649 29.8432 C 12.8649 29.7032 13.1449 29.7032 13.2849 29.5632 C 13.2849 29.9132 13.3549 30.1232 13.4249 30.3332 C 13.0749 30.5432 12.6549 30.7532 12.3049 30.9632 C 12.3049 30.9632 12.3049 30.9632 12.3049 30.8932 L 12.2349 30.8232 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -73.84 7.22)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-8.94, -35.16)" d="M 8.66485 35.1632 C 8.66485 35.1632 8.80485 35.1632 8.87485 35.1632 C 8.87485 35.0232 8.87485 34.9532 8.80485 34.8132 C 8.87485 34.8132 9.01485 34.8132 9.01485 34.8132 C 9.15485 34.9532 9.22485 35.1632 9.29485 35.3732 L 9.15485 35.5132 C 8.94485 35.5132 8.80485 35.4432 8.59485 35.3732 C 8.59485 35.3732 8.59485 35.3032 8.59485 35.2332 L 8.66485 35.1632 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -65.26 10.65)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-17.53, -38.59)" d="M 0.124878 37.6832 C 0.264878 38.5932 1.80488 40.5532 1.80488 40.5532 C 3.06488 42.0932 4.67488 43.1432 6.42488 44.0532 C 6.56488 44.0532 6.63488 44.1932 6.77488 44.1932 C 6.77488 44.1932 6.77488 44.1932 6.77488 44.2632 L 5.51488 43.9132 C 5.72488 44.1232 5.93488 44.1932 6.14488 44.2632 C 7.12488 44.6132 8.17488 44.6832 9.15488 44.6832 C 9.50488 44.6832 9.64488 44.9632 9.50488 45.3132 C 9.29488 45.8732 8.66488 46.2232 8.10488 46.2232 C 7.68488 46.2232 7.19488 46.2232 6.77488 46.2232 C 6.84488 46.2932 6.98488 46.3632 7.12488 46.3632 C 7.82488 46.3632 8.52488 46.3632 9.22488 46.3632 C 10.0649 46.3632 10.8349 46.3632 11.6749 46.2232 C 13.7749 45.8032 15.5949 44.8232 17.2749 43.5632 C 19.3749 42.0232 21.1249 40.2032 22.7349 38.1732 C 23.1549 37.6832 23.6449 37.2632 24.2049 36.9132 C 24.8349 36.4932 25.3949 36.7732 25.6049 37.4032 C 26.0249 38.5932 26.0949 39.7132 25.3249 40.7632 C 24.9049 41.3232 24.4849 41.9532 24.1349 42.5832 C 23.6449 43.4932 23.4349 44.4732 23.6449 45.5232 C 23.7849 46.4332 24.4149 46.8532 25.3249 46.6432 C 25.6749 46.5732 26.0249 46.4332 26.3049 46.2932 C 26.9349 45.9432 27.4949 45.5232 28.0549 45.1732 C 28.3349 44.9632 28.4749 44.9632 28.6849 45.2432 C 29.0349 45.6632 29.5249 45.8032 30.0149 45.8732 C 31.2049 46.0132 32.0449 45.3832 32.7449 44.6132 C 33.7949 43.4932 34.4249 42.1632 34.7749 40.6932 C 35.1249 39.2232 34.9149 37.8232 33.9349 36.5632 C 33.5849 36.0732 33.2349 35.5832 32.8849 35.1632 C 31.6949 33.6232 30.4349 32.0832 29.1749 30.5432 C 29.1749 30.4732 28.8949 30.4732 28.7549 30.5432 C 28.6149 30.5432 28.5449 30.7532 28.4749 30.8932 C 27.9149 31.9432 27.3549 32.9232 26.7949 33.9732 C 26.6549 34.1832 26.7249 34.3932 26.9349 34.5332 C 27.0049 34.5332 27.0749 34.6732 27.2149 34.8132 C 25.8149 34.3232 24.7649 34.8132 23.7849 35.6532 C 22.8049 36.4932 21.8949 37.3332 20.9149 38.1732 C 19.5149 39.3632 18.1149 40.4832 16.3649 41.1832 C 14.0549 42.0932 11.6749 42.7932 9.15488 42.7232 C 5.65488 42.7232 2.36488 39.0132 1.87488 37.8232 C 1.38488 36.6332 0.194878 37.6132 0.194878 37.6132 L 0.124878 37.6832 Z M 32.1849 42.3032 C 31.8349 42.4432 31.4849 42.6532 31.1349 42.3032 C 30.7849 42.0232 30.9949 41.6732 31.1349 41.3232 C 31.1349 41.1132 31.2049 40.9032 31.2049 40.6932 C 31.2049 40.5532 31.2049 40.4132 31.0649 40.4132 C 30.9949 40.4132 30.7849 40.4132 30.7149 40.5532 C 30.4349 40.8332 30.2249 41.1132 29.9449 41.4632 C 29.1749 42.4432 28.1949 42.9332 26.9349 42.7232 C 26.0249 42.5832 25.7449 42.0932 26.0249 41.1832 C 26.3049 40.3432 26.7949 39.6432 27.3549 39.0132 C 27.9149 38.3832 28.3349 37.6832 28.3349 36.8432 C 28.3349 36.6332 28.3349 36.3532 28.3349 36.0732 C 28.6149 36.2832 28.8949 36.4232 29.1749 36.5632 C 30.7849 37.6132 31.8349 39.0832 32.2549 40.9732 C 32.2549 41.2532 32.3949 41.5332 32.4649 41.8132 C 32.6049 42.0932 32.4649 42.1632 32.2549 42.2332 L 32.1849 42.3032 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -63.97 -6.87)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-18.81, -21.08)" d="M 16.6448 20.1134 C 16.6448 19.9034 16.7848 19.5534 16.9948 19.3434 C 17.2048 19.1334 17.6948 19.0634 17.9748 19.4834 C 18.1148 19.6934 18.2548 19.9034 18.3248 20.1834 C 18.4648 20.6034 18.6048 21.0234 18.6748 21.4434 C 18.6748 21.5834 18.6748 21.7234 18.7448 21.8634 C 18.7448 21.8634 18.7448 21.8634 18.8148 21.8634 C 18.8148 21.7934 18.8148 21.7234 18.8148 21.6534 C 18.8148 21.3734 18.8848 21.0234 18.9548 20.7434 C 18.9548 20.7434 18.9548 20.6034 18.9548 20.5334 C 19.0248 20.3234 19.0948 20.1834 19.1648 19.9734 C 19.1648 19.7634 19.2348 19.6234 19.3048 19.4134 C 19.4448 19.2734 19.5848 19.0634 19.7248 18.9934 C 20.0048 18.8534 20.2148 18.8534 20.4948 18.9934 C 20.7048 19.1334 20.8448 19.3434 20.9848 19.6234 C 20.9848 19.6234 20.9848 19.7634 20.9848 19.8334 C 20.8448 20.0434 20.7048 20.1834 20.6348 20.3934 C 20.5648 20.5334 20.4948 20.5334 20.4248 20.3934 C 20.2848 20.2534 20.2148 20.1134 20.0748 19.9734 C 20.0048 19.9034 19.9348 19.9034 19.8648 19.9734 C 19.8648 20.1134 19.7248 20.1834 19.7248 20.3234 C 19.5848 20.6734 19.5148 21.0934 19.4448 21.5134 C 19.4448 21.7234 19.3748 21.9334 19.3048 22.1434 C 19.3048 22.2834 19.2348 22.3534 19.1648 22.4934 C 18.9548 22.7734 18.7448 23.0534 18.4648 23.2634 C 18.4648 23.2634 18.4648 23.2634 18.3948 23.2634 C 18.3248 23.2634 18.3248 23.1934 18.3248 23.0534 C 18.3248 22.8434 18.3248 22.5634 18.3248 22.2834 C 18.3248 22.0734 18.2548 21.8634 18.1848 21.6534 C 18.0448 21.3734 17.9048 21.1634 17.7648 20.9534 C 17.7648 20.9534 17.7648 20.9534 17.6948 20.9534 C 17.6248 20.9534 17.4848 21.0934 17.4148 21.0934 C 17.4148 21.0934 17.3448 21.0934 17.2748 21.0934 C 17.0648 21.0234 16.8548 20.8834 16.7848 20.6734 C 16.7848 20.6034 16.7848 20.4634 16.7848 20.1834 L 16.6448 20.1134 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -74.43 10.83)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-8.35, -38.77)" d="M 6.63489 38.6634 C 6.63489 38.6634 6.77489 38.6634 6.84489 38.6634 C 6.98489 38.6634 7.12489 38.7334 7.19489 38.8034 C 7.40489 38.9434 7.68489 38.9434 7.89489 39.0134 C 8.10489 39.0134 8.24489 39.0134 8.45489 39.0134 C 8.59489 39.0134 8.66489 39.0134 8.73489 38.9434 C 8.87489 38.8734 8.94489 38.8034 9.08489 38.7334 C 9.22489 38.5934 9.22489 38.4534 9.22489 38.2434 C 9.22489 38.2434 9.22489 38.2434 9.22489 38.1734 C 9.22489 38.1034 9.22489 38.0334 9.36489 38.0334 C 9.50489 38.0334 9.64489 38.0334 9.71489 38.0334 C 9.99489 38.0334 10.0649 38.3134 10.0649 38.5234 C 10.0649 38.7334 10.0649 38.8734 9.92489 39.0834 C 9.71489 39.2934 9.43489 39.4334 9.08489 39.5034 C 8.73489 39.5034 8.38489 39.5034 8.03489 39.4334 C 7.82489 39.4334 7.61489 39.2934 7.47489 39.2234 C 7.26489 39.0834 7.05489 39.0134 6.84489 38.8734 C 6.77489 38.8734 6.70489 38.7334 6.70489 38.7334 L 6.63489 38.6634 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1 0 0 1 -74.78 0.4)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: #ffffff; opacity: 1;" transform=" translate(-8, -28.34)" d="M 6.28479 28.2333 C 6.28479 28.2333 6.42479 28.2333 6.49479 28.2333 C 6.63479 28.2333 6.77479 28.3033 6.84479 28.3733 C 7.05479 28.5133 7.33479 28.5133 7.54479 28.5833 C 7.75479 28.5833 7.89479 28.5833 8.10479 28.5833 C 8.24479 28.5833 8.31479 28.5833 8.38479 28.5133 C 8.52479 28.4433 8.59479 28.3733 8.73479 28.3033 C 8.87479 28.1633 8.87479 28.0233 8.87479 27.8133 C 8.87479 27.8133 8.87479 27.8133 8.87479 27.7433 C 8.87479 27.6733 8.87479 27.6033 9.01479 27.6033 C 9.15479 27.6033 9.29479 27.6033 9.36479 27.6033 C 9.64479 27.6033 9.71479 27.8833 9.71479 28.0933 C 9.71479 28.3033 9.71479 28.4433 9.57479 28.6533 C 9.36479 28.8633 9.08479 29.0033 8.73479 29.0733 C 8.38479 29.0733 8.03479 29.0733 7.68479 29.0033 C 7.47479 29.0033 7.26479 28.8633 7.12479 28.7933 C 6.91479 28.6533 6.70479 28.5833 6.49479 28.4433 C 6.42479 28.4433 6.35479 28.3033 6.35479 28.3033 L 6.28479 28.2333 Z" stroke-linecap="round" />
</g>
</g> </g>
</svg> </svg>

14
src/app/App.tsx

@ -7,6 +7,7 @@ import AppRouter from '@/app/router'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { usePanelRouting } from '@/hooks/use-panel-routing' import { usePanelRouting } from '@/hooks/use-panel-routing'
import { useAuth } from '@/features/auth/use-auth' import { useAuth } from '@/features/auth/use-auth'
import DevClickToComponent from '@/components/utils/dev-click-to-component'
/* /*
* شل اصلی داشبورد (دولایه و راستچین RTL): * شل اصلی داشبورد (دولایه و راستچین RTL):
@ -20,11 +21,12 @@ import { useAuth } from '@/features/auth/use-auth'
export default function App() { export default function App() {
const { isAuthenticated } = useAuth() const { isAuthenticated } = useAuth()
if (!isAuthenticated) {
return <AppRouter />
}
return <AuthenticatedShell />
return (
<>
{isAuthenticated ? <AuthenticatedShell /> : <AppRouter />}
{import.meta.env.DEV && <DevClickToComponent />}
</>
)
} }
function AuthenticatedShell() { function AuthenticatedShell() {
@ -32,7 +34,7 @@ function AuthenticatedShell() {
return ( return (
<> <>
<SidebarProvider className="min-h-0">
<SidebarProvider>
<AppSidebar /> <AppSidebar />
<SidebarInset className="min-w-0"> <SidebarInset className="min-w-0">

84
src/components/AqilaLogo.tsx

@ -0,0 +1,84 @@
import React from 'react'
interface AqilaLogoProps extends React.SVGProps<SVGSVGElement> {
className?: string
}
/**
* آیکون و نشان رسمی عقیله (برگرفته از اپلیکیشن فلاتر و هویت بصری سامانه)
*/
export function AqilaLogo({ className = 'size-6', ...props }: AqilaLogoProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="-91.3 -30.1 60 60"
fill="currentColor"
className={className}
{...props}
>
<g>
<g transform="matrix(1 0 0 1 -61.89 -5.77)" >
<path fill="currentColor" transform=" translate(-20.9, -22.17)" d="M 2.78484 39.2933 C 1.73484 38.0333 1.45484 37.2633 1.31484 35.0933 C 1.03484 30.2633 3.41484 27.3233 12.7248 21.0933 C 15.8748 18.9933 18.8848 16.8233 19.5148 16.1933 L 20.6348 15.1433 L 23.2948 17.3133 C 24.7648 18.5033 27.9148 20.7433 30.2248 22.3533 C 35.5448 25.9233 38.2748 28.5833 39.6748 31.5233 C 40.5848 33.5533 40.7248 34.0433 40.3748 37.0533 C 40.2348 38.8733 39.8148 41.2533 39.5348 42.3033 C 39.1848 43.3533 38.9748 44.3333 39.0448 44.3333 C 39.3248 44.6133 40.5848 41.1133 41.2148 38.4533 C 43.1048 30.8233 40.5848 26.4833 30.4348 19.6933 C 22.8748 14.5833 20.7748 12.0633 20.7748 11.5733 L 20.7748 0.0933333 C 20.7748 0.0933333 20.2148 -0.116667 20.2148 0.0933333 C 20.2148 1.56333 20.2148 0.653333 19.4448 1.14333 C 18.7448 1.63333 17.6948 2.19333 17.9748 0.863333 C 18.1848 -0.0466667 17.4148 -0.186667 16.2248 0.583334 C 15.5248 0.933334 14.4748 1.56333 14.1948 1.91333 C 12.9348 2.96333 10.8348 3.52333 9.29484 3.52333 C 7.75484 3.52333 7.75484 3.87333 8.45484 4.50333 C 9.15484 5.13333 10.6248 7.02333 14.8948 5.97333 C 15.3848 5.83333 15.5948 5.97333 15.5948 6.67333 C 15.5948 7.37333 15.7348 7.58333 17.2748 7.37333 C 18.1848 7.23333 19.8648 6.39333 20.2148 6.25333 C 20.3548 6.25333 20.5648 6.25333 20.4948 6.74333 C 20.4948 6.88333 20.4948 8.07333 20.4948 8.07333 L 20.4948 11.5733 C 20.4948 12.2033 17.0648 14.9333 11.2548 19.2733 C 2.92484 25.2933 0.474838 28.9333 0.0548377 34.7433 C -0.0851623 37.4733 -0.0151621 37.8933 0.964838 39.3633" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -53.84 -2.75)" >
<path fill="currentColor" transform=" translate(-28.95, -25.19)" d="M 26.7949 23.7532 C 25.6749 25.9932 25.6749 26.4832 26.4449 26.4832 C 27.2149 26.4832 27.6349 26.9732 28.3349 27.5332 L 29.6649 28.5832 L 30.6449 27.3932 C 31.7649 25.9932 32.1849 25.0832 31.9049 24.4532 C 31.6949 23.9632 28.6149 21.7932 28.0549 21.7932 C 27.4949 21.7932 27.2849 22.6332 26.7949 23.6832 L 26.7949 23.7532 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -61.74 -0.27)" >
<path fill="currentColor" transform=" translate(-21.04, -27.67)" d="M 19.0948 25.7834 C 18.6048 26.7634 18.1848 27.8134 18.1848 28.0234 C 18.1848 28.2334 19.0248 29.0034 20.0748 29.5634 C 21.1248 30.1934 21.9648 30.8234 21.9648 31.1034 C 21.9648 31.8734 22.8748 30.9634 23.5048 29.5634 C 24.0648 28.3034 23.9948 27.9534 23.5048 26.9034 C 22.9448 25.8534 20.8448 23.9634 20.2148 23.9634 C 19.5848 23.9634 19.5848 24.7334 19.0948 25.7834 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -62.01 -20.5)" >
<path fill="currentColor" transform=" translate(-20.77, -7.44)" d="M 20.7749 8.14341 C 21.2001 8.14341 21.5449 7.83001 21.5449 7.44341 C 21.5449 7.05681 21.2001 6.74341 20.7749 6.74341 C 20.3496 6.74341 20.0049 7.05681 20.0049 7.44341 C 20.0049 7.83001 20.3496 8.14341 20.7749 8.14341 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -62.01 -18.12)" >
<path fill="currentColor" transform=" translate(-20.77, -9.82)" d="M 20.7749 10.8734 C 21.3548 10.8734 21.8249 10.4033 21.8249 9.82344 C 21.8249 9.24354 21.3548 8.77344 20.7749 8.77344 C 20.195 8.77344 19.7249 9.24354 19.7249 9.82344 C 19.7249 10.4033 20.195 10.8734 20.7749 10.8734 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -53.38 10.83)" >
<path fill="currentColor" transform=" translate(-29.4, -38.77)" d="M 21.4049 43.3533 C 21.0549 43.8433 20.7749 44.2633 20.4949 44.6833 C 20.0049 45.5233 20.1449 46.3633 20.7049 47.1333 C 21.5449 48.1833 22.7349 48.6033 24.0649 48.7433 C 25.6749 48.8833 27.2149 48.4633 28.7549 47.9033 C 31.0649 47.0633 33.1649 45.8033 34.9849 44.1933 C 36.9449 42.5133 37.4349 40.6233 36.3149 37.6833 C 35.7549 36.2133 34.7749 35.0933 33.7249 33.9733 C 33.2349 33.4833 32.8149 32.9933 32.3249 32.5033 C 32.1849 32.3633 32.0449 32.1533 32.1849 31.9433 C 32.8849 30.8933 33.5149 29.8433 34.2149 28.7933 C 34.6349 28.2333 34.9849 28.2333 35.4049 28.7933 C 35.9649 29.4933 36.4549 30.1933 36.8749 30.9633 C 37.5049 32.0833 38.0649 33.2033 38.4849 34.3933 C 38.9749 35.6533 38.9749 37.0533 38.6249 38.3833 C 37.6449 42.0233 35.7549 45.1733 33.3049 48.0433 C 33.0249 48.3933 32.6049 48.6733 32.2549 48.9533 C 32.1149 49.0933 31.9049 49.1633 31.6949 49.1633 C 31.3449 49.1633 31.2049 49.0933 31.2749 48.7433 C 31.3449 48.3933 31.2049 48.2533 30.9249 48.1133 C 30.4349 47.9033 29.9449 48.0433 29.4549 48.1133 C 28.4049 48.3933 27.4249 48.7433 26.3749 48.8833 C 24.9749 49.0933 23.5749 49.0933 22.2449 48.6733 C 22.0349 48.6733 21.7549 48.5333 21.5449 48.4633 C 20.2849 47.9033 19.7249 46.9233 20.0049 45.5933 C 20.1449 44.6833 20.5649 43.9133 21.3349 43.2833 L 21.4049 43.3533 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -50.28 24.78)" >
<path fill="currentColor" transform=" translate(-32.51, -52.72)" d="M 35.3348 55.8134 C 35.1248 54.4834 34.1448 53.9234 33.0948 53.4334 C 32.7448 53.2934 32.4648 53.3634 32.1848 53.5734 C 31.6948 54.0634 31.2048 54.6234 30.7148 55.1834 C 30.6448 55.2534 30.5748 55.3934 30.5048 55.4634 C 30.5048 55.3934 30.3648 55.3234 30.3648 55.2534 C 29.7348 54.2734 28.8248 53.7834 27.7048 53.5034 C 27.2148 53.3634 27.0748 53.2234 27.2148 52.6634 C 27.4248 51.6834 27.9848 50.9134 28.6148 50.2134 C 28.9648 49.8634 29.2448 49.7234 29.7348 49.9334 C 30.3648 50.2134 31.0648 50.4234 31.7648 50.7734 C 32.1148 50.9134 32.3948 51.2634 32.7448 51.5434 C 32.8148 51.4034 32.9548 51.1934 33.0948 50.9834 C 33.3748 50.6334 33.5848 50.2134 33.8648 49.8634 C 34.0748 49.5834 34.4248 49.4434 34.7748 49.6534 C 35.4048 50.0034 36.0348 50.2834 36.6648 50.6334 C 36.8748 50.7734 37.0148 50.9134 37.2248 51.1234 C 37.9248 51.8234 38.0648 52.5234 37.5048 53.3634 C 36.9448 54.2034 36.2448 54.9034 35.6148 55.7434 C 35.6148 55.7434 35.4748 55.8134 35.4048 55.8834 L 35.3348 55.8134 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -70.52 22.38)" >
<path fill="currentColor" transform=" translate(-12.26, -50.32)" d="M 13.0749 53.2232 C 12.7949 52.3832 12.2349 52.0332 11.5349 51.8232 C 10.9749 51.6832 10.4149 51.5432 9.85489 51.4032 C 9.71489 51.4032 9.57489 51.3332 9.43489 51.2632 C 9.15489 51.1232 9.01489 50.9832 9.22489 50.7032 C 9.78489 49.7932 10.3449 48.8832 10.9049 47.9732 C 10.9749 47.8332 11.1149 47.6932 11.2549 47.6232 C 11.4649 47.4132 11.7449 47.3432 12.0249 47.4832 C 12.7949 47.8332 13.5649 48.1132 14.3349 48.4632 C 15.4549 49.0232 15.7349 50.2132 14.9649 51.1932 C 14.7549 51.4732 14.4749 51.7532 14.1949 52.0332 C 13.8449 52.3832 13.4949 52.8032 13.0749 53.2232 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -70.83 5.53)" >
<path fill="currentColor" transform=" translate(-11.95, -33.47)" d="M 10.9048 33.6233 C 10.9048 33.8333 10.9748 34.0433 11.0448 34.1133 C 11.1148 34.2533 11.2548 34.2533 11.3948 34.3233 C 11.3948 34.1833 11.5348 34.0433 11.4648 33.9033 C 11.3248 33.2733 11.0448 32.6433 10.9748 32.0133 C 10.8348 31.1033 11.1848 30.4733 11.8848 30.2633 C 11.4648 31.1733 11.6048 31.6633 12.7248 32.7833 C 12.9348 32.5733 13.1448 32.4333 13.4248 32.2233 C 13.6348 32.0833 13.9148 31.8733 14.1948 31.6633 C 13.9148 31.6633 13.7048 31.6633 13.4948 31.5933 C 13.3548 31.0333 13.6348 30.3333 14.0548 30.2633 C 14.5448 30.1233 15.2448 30.4733 15.3148 30.8933 C 15.4548 31.5233 14.8948 32.2233 14.0548 32.3633 C 14.1948 32.7133 14.3348 33.0633 14.4748 33.4133 C 14.6148 33.7633 14.5448 34.1133 14.3348 34.4633 C 14.3348 34.4633 14.2648 34.5333 14.2648 34.6033 C 14.2648 34.8133 14.1948 35.0233 14.1948 35.3033 C 14.1948 35.3033 14.4048 35.3733 14.4048 35.3033 C 14.6148 35.1633 14.8948 34.9533 14.8948 34.7433 C 14.9648 34.3233 14.8948 33.8333 14.8948 33.4133 C 14.8948 32.9933 14.8948 32.5733 14.9648 32.1533 C 14.9648 32.1533 15.1048 32.1533 15.1748 32.1533 C 15.1748 32.2933 15.2448 32.5033 15.2448 32.6433 C 15.2448 33.2733 15.2448 33.9733 15.2448 34.6033 C 15.2448 35.3733 15.3848 35.5833 16.1548 35.5133 C 17.1348 35.5133 17.9748 35.1633 18.6048 34.4633 C 19.0248 34.0433 19.3748 33.5533 19.7948 33.0633 C 19.9348 32.9233 20.0748 32.7833 20.2148 32.6433 C 20.2148 32.6433 20.2848 32.6433 20.3548 32.7133 C 20.2848 32.9933 20.2148 33.3433 20.0748 33.4833 C 19.3048 34.3933 18.6048 35.3733 17.4848 35.7933 C 16.9948 36.0033 16.4348 36.0733 15.8748 36.1433 C 15.5948 36.1433 15.3148 36.0033 14.8948 35.8633 C 14.8948 35.8633 14.7548 36.0733 14.6148 36.1433 C 14.2648 36.4933 13.9148 36.4233 13.7748 35.9333 C 13.5648 35.4433 13.5648 34.8833 13.9148 34.4633 C 14.3348 33.9033 14.3348 33.4133 13.9148 32.8533 C 13.9148 32.7833 13.8448 32.6433 13.7748 32.5733 C 13.6348 32.6433 13.4948 32.7833 13.3548 32.8533 C 13.0748 33.0633 13.0048 33.2033 13.1448 33.5533 C 13.4248 34.4633 13.2148 35.3033 12.5848 36.0033 C 12.7248 36.0733 12.7948 36.2133 12.9348 36.2833 C 12.3748 35.9333 12.0248 36.2833 11.5348 36.4933 C 10.9048 36.7033 10.2748 36.8433 9.71481 36.4933 C 10.0648 36.4933 10.5548 36.6333 10.9748 36.4933 C 11.3948 36.4233 11.8148 36.1433 12.1648 35.9333 C 12.0248 35.5833 11.8848 35.3033 11.8148 35.0933 C 11.7448 34.8833 11.0448 34.7433 10.9048 34.8833 C 10.8348 34.8833 10.7648 35.0933 10.6948 35.0933 C 10.4148 35.0933 10.1348 35.2333 9.92481 35.1633 C 9.78481 35.0933 9.64481 34.7433 9.64481 34.5333 C 9.64481 34.1133 9.78481 33.6933 9.78481 33.2033 C 9.57481 33.4133 9.29481 33.6933 9.08481 33.9033 C 8.38481 34.6033 7.61481 35.1633 6.63481 35.5133 C 6.28481 35.6533 5.93481 35.7233 5.58481 35.7233 C 4.67481 35.7233 4.04481 35.3033 3.76481 34.4633 C 3.62481 33.9733 3.55481 33.4833 3.55481 32.9933 C 3.55481 31.6633 4.67481 30.8233 6.00481 30.9633 C 6.35481 30.9633 6.70481 31.0333 7.05481 31.1033 C 7.96481 31.1733 8.45481 31.1033 9.08481 30.5433 C 8.80481 31.1733 7.96481 31.5233 7.05481 31.4533 C 6.63481 31.4533 6.14481 31.3133 5.72481 31.3133 C 4.88481 31.2433 4.39481 31.6633 4.04481 32.3633 C 3.69481 32.9933 3.62481 33.6233 4.04481 34.2533 C 4.46481 34.8833 5.16481 35.0233 5.86481 34.9533 C 6.70481 34.8833 7.40481 34.3933 8.03481 33.9033 C 8.45481 33.5533 8.87481 33.1333 9.29481 32.7133 C 9.50481 32.5033 9.71481 32.2933 9.92481 32.1533 C 10.2048 31.9433 10.4148 32.0133 10.3448 32.3633 C 10.3448 32.7833 10.2048 33.1333 10.0648 33.5533 C 9.99481 33.8333 9.71481 34.2533 10.0648 34.4633 C 10.4148 34.6033 10.5548 34.1133 10.6948 33.8333 C 10.6948 33.8333 10.6948 33.6933 10.7648 33.5533 L 10.9048 33.6233 Z M 12.5148 33.7633 C 11.8848 34.5333 11.7448 35.3733 12.3048 35.7933 C 13.0048 35.2333 13.0748 34.6033 12.5148 33.7633 Z M 12.0248 33.6933 C 12.3748 33.2733 12.1648 32.9233 11.5348 32.5033 C 11.7448 32.9233 11.8848 33.3433 12.0248 33.6933 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -76.11 5.05)" >
<path fill="currentColor" transform=" translate(-6.67, -32.99)" d="M 8.0348 32.7831 C 8.0348 32.5031 8.0348 32.3631 7.9648 32.1531 C 7.8248 32.1531 7.6848 32.1531 7.6848 32.2231 C 7.6848 32.4331 7.5448 32.6431 7.6848 32.8531 C 7.6848 32.9931 8.0348 33.0631 8.1748 33.0631 C 8.3848 33.0631 8.5248 32.8531 8.6648 32.7131 C 8.4548 33.4131 7.5448 33.6931 6.9848 33.3431 C 6.7748 33.3431 6.6348 33.4131 6.4948 33.4831 C 6.3548 33.4831 6.2148 33.6231 6.0748 33.6931 C 5.9348 33.8331 5.7948 33.9031 5.65481 34.0431 C 5.58481 33.9031 5.44481 33.7631 5.30481 33.5531 C 5.09481 33.5531 4.8848 33.5531 4.6748 33.5531 C 4.6748 33.3431 4.6748 33.0631 4.8148 32.8531 C 5.0248 32.5731 5.30481 32.3631 5.65481 32.1531 C 5.44481 32.3631 5.2348 32.5731 5.0248 32.7831 C 4.8148 32.9931 4.8148 33.2031 5.0948 33.3431 C 5.2348 33.0631 5.3748 32.8531 5.5148 32.7131 C 5.7248 32.9231 5.9348 33.1331 6.1448 33.4131 C 6.1448 33.3431 6.2848 33.2031 6.4248 33.0631 C 6.4248 33.2031 6.5648 33.2731 6.6348 33.3431 C 6.8448 32.8531 7.0548 32.4331 7.2648 31.9431 C 7.2648 31.9431 7.3348 31.9431 7.4048 31.9431 C 7.3348 32.1531 7.1948 32.3631 7.1248 32.5731 C 7.1248 32.7831 7.1248 32.9231 7.1948 33.1331 C 7.2648 32.8531 7.3348 32.5731 7.4048 32.2931 C 7.4048 32.1531 7.6148 32.0131 7.6848 32.0131 C 7.8948 32.0131 8.1048 32.0831 8.3148 32.1531 C 8.2448 32.3631 8.1048 32.5731 8.0348 32.8531 L 8.0348 32.7831 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -65.44 6.09)" >
<path fill="currentColor" transform=" translate(-17.34, -34.03)" d="M 18.4648 33.4133 L 18.4648 33.8333 C 18.4648 33.8333 18.5348 33.9033 18.6048 33.9733 C 18.6048 33.8333 18.6748 33.6933 18.7448 33.5533 C 18.8148 33.6933 18.8848 33.7633 19.0248 33.9033 C 18.6748 33.7633 18.3948 34.3933 18.1848 33.8333 C 17.1348 34.5333 16.5048 34.7433 15.6648 34.6033 C 15.9448 34.6033 16.2948 34.5333 16.5748 34.4633 C 16.8548 34.3933 17.2048 34.2533 17.4848 34.0433 C 17.7648 33.9033 18.0448 33.6933 18.3948 33.4133 L 18.4648 33.4133 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -72.68 3.55)" >
<path fill="currentColor" transform=" translate(-10.1, -31.49)" d="M 9.64481 31.0332 C 9.64481 31.2432 9.71481 31.4532 9.78481 31.5932 C 9.99481 31.5232 10.2048 31.4532 10.4148 31.3132 C 10.5548 31.3132 10.6948 31.2432 10.7648 31.2432 C 10.7648 31.4532 9.92481 32.0132 9.64481 31.9432 C 9.50481 31.9432 9.43481 31.6632 9.43481 31.5232 C 9.43481 31.3832 9.57481 31.1732 9.64481 31.0332 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -64.88 4.91)" >
<path fill="currentColor" transform=" translate(-17.9, -32.85)" d="M 16.7148 33.5533 C 17.4848 33.0633 18.2548 32.6433 19.0248 32.1533 C 19.0248 32.1533 19.0248 32.1533 19.0948 32.1533 C 18.9548 32.2933 18.8848 32.4333 18.7448 32.5033 C 18.1148 32.8533 17.4148 33.2033 16.7148 33.5533 C 16.7148 33.5533 16.7148 33.5533 16.7148 33.4833 L 16.7148 33.5533 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -69.95 2.32)" >
<path fill="currentColor" transform=" translate(-12.83, -30.26)" d="M 12.2349 30.8232 C 12.5149 30.6132 12.7949 30.4732 13.0049 30.3332 C 13.0049 30.1232 12.8649 29.9832 12.8649 29.8432 C 12.8649 29.7032 13.1449 29.7032 13.2849 29.5632 C 13.2849 29.9132 13.3549 30.1232 13.4249 30.3332 C 13.0749 30.5432 12.6549 30.7532 12.3049 30.9632 C 12.3049 30.9632 12.3049 30.9632 12.3049 30.8932 L 12.2349 30.8232 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -73.84 7.22)" >
<path fill="currentColor" transform=" translate(-8.94, -35.16)" d="M 8.66485 35.1632 C 8.66485 35.1632 8.80485 35.1632 8.87485 35.1632 C 8.87485 35.0232 8.87485 34.9532 8.80485 34.8132 C 8.87485 34.8132 9.01485 34.8132 9.01485 34.8132 C 9.15485 34.9532 9.22485 35.1632 9.29485 35.3732 L 9.15485 35.5132 C 8.94485 35.5132 8.80485 35.4432 8.59485 35.3732 C 8.59485 35.3732 8.59485 35.3032 8.59485 35.2332 L 8.66485 35.1632 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -65.26 10.65)" >
<path fill="currentColor" transform=" translate(-17.53, -38.59)" d="M 0.124878 37.6832 C 0.264878 38.5932 1.80488 40.5532 1.80488 40.5532 C 3.06488 42.0932 4.67488 43.1432 6.42488 44.0532 C 6.56488 44.0532 6.63488 44.1932 6.77488 44.1932 C 6.77488 44.1932 6.77488 44.1932 6.77488 44.2632 L 5.51488 43.9132 C 5.72488 44.1232 5.93488 44.1932 6.14488 44.2632 C 7.12488 44.6132 8.17488 44.6832 9.15488 44.6832 C 9.50488 44.6832 9.64488 44.9632 9.50488 45.3132 C 9.29488 45.8732 8.66488 46.2232 8.10488 46.2232 C 7.68488 46.2232 7.19488 46.2232 6.77488 46.2232 C 6.84488 46.2932 6.98488 46.3632 7.12488 46.3632 C 7.82488 46.3632 8.52488 46.3632 9.22488 46.3632 C 10.0649 46.3632 10.8349 46.3632 11.6749 46.2232 C 13.7749 45.8032 15.5949 44.8232 17.2749 43.5632 C 19.3749 42.0232 21.1249 40.2032 22.7349 38.1732 C 23.1549 37.6832 23.6449 37.2632 24.2049 36.9132 C 24.8349 36.4932 25.3949 36.7732 25.6049 37.4032 C 26.0249 38.5932 26.0949 39.7132 25.3249 40.7632 C 24.9049 41.3232 24.4849 41.9532 24.1349 42.5832 C 23.6449 43.4932 23.4349 44.4732 23.6449 45.5232 C 23.7849 46.4332 24.4149 46.8532 25.3249 46.6432 C 25.6749 46.5732 26.0249 46.4332 26.3049 46.2932 C 26.9349 45.9432 27.4949 45.5232 28.0549 45.1732 C 28.3349 44.9632 28.4749 44.9632 28.6849 45.2432 C 29.0349 45.6632 29.5249 45.8032 30.0149 45.8732 C 31.2049 46.0132 32.0449 45.3832 32.7449 44.6132 C 33.7949 43.4932 34.4249 42.1632 34.7749 40.6932 C 35.1249 39.2232 34.9149 37.8232 33.9349 36.5632 C 33.5849 36.0732 33.2349 35.5832 32.8849 35.1632 C 31.6949 33.6232 30.4349 32.0832 29.1749 30.5432 C 29.1749 30.4732 28.8949 30.4732 28.7549 30.5432 C 28.6149 30.5432 28.5449 30.7532 28.4749 30.8932 C 27.9149 31.9432 27.3549 32.9232 26.7949 33.9732 C 26.6549 34.1832 26.7249 34.3932 26.9349 34.5332 C 27.0049 34.5332 27.0749 34.6732 27.2149 34.8132 C 25.8149 34.3232 24.7649 34.8132 23.7849 35.6532 C 22.8049 36.4932 21.8949 37.3332 20.9149 38.1732 C 19.5149 39.3632 18.1149 40.4832 16.3649 41.1832 C 14.0549 42.0932 11.6749 42.7932 9.15488 42.7232 C 5.65488 42.7232 2.36488 39.0132 1.87488 37.8232 C 1.38488 36.6332 0.194878 37.6132 0.194878 37.6132 L 0.124878 37.6832 Z M 32.1849 42.3032 C 31.8349 42.4432 31.4849 42.6532 31.1349 42.3032 C 30.7849 42.0232 30.9949 41.6732 31.1349 41.3232 C 31.1349 41.1132 31.2049 40.9032 31.2049 40.6932 C 31.2049 40.5532 31.2049 40.4132 31.0649 40.4132 C 30.9949 40.4132 30.7849 40.4132 30.7149 40.5532 C 30.4349 40.8332 30.2249 41.1132 29.9449 41.4632 C 29.1749 42.4432 28.1949 42.9332 26.9349 42.7232 C 26.0249 42.5832 25.7449 42.0932 26.0249 41.1832 C 26.3049 40.3432 26.7949 39.6432 27.3549 39.0132 C 27.9149 38.3832 28.3349 37.6832 28.3349 36.8432 C 28.3349 36.6332 28.3349 36.3532 28.3349 36.0732 C 28.6149 36.2832 28.8949 36.4232 29.1749 36.5632 C 30.7849 37.6132 31.8349 39.0832 32.2549 40.9732 C 32.2549 41.2532 32.3949 41.5332 32.4649 41.8132 C 32.6049 42.0932 32.4649 42.1632 32.2549 42.2332 L 32.1849 42.3032 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -63.97 -6.87)" >
<path fill="currentColor" transform=" translate(-18.81, -21.08)" d="M 16.6448 20.1134 C 16.6448 19.9034 16.7848 19.5534 16.9948 19.3434 C 17.2048 19.1334 17.6948 19.0634 17.9748 19.4834 C 18.1148 19.6934 18.2548 19.9034 18.3248 20.1834 C 18.4648 20.6034 18.6048 21.0234 18.6748 21.4434 C 18.6748 21.5834 18.6748 21.7234 18.7448 21.8634 C 18.7448 21.8634 18.7448 21.8634 18.8148 21.8634 C 18.8148 21.7934 18.8148 21.7234 18.8148 21.6534 C 18.8148 21.3734 18.8848 21.0234 18.9548 20.7434 C 18.9548 20.7434 18.9548 20.6034 18.9548 20.5334 C 19.0248 20.3234 19.0948 20.1834 19.1648 19.9734 C 19.1648 19.7634 19.2348 19.6234 19.3048 19.4134 C 19.4448 19.2734 19.5848 19.0634 19.7248 18.9934 C 20.0048 18.8534 20.2148 18.8534 20.4948 18.9934 C 20.7048 19.1334 20.8448 19.3434 20.9848 19.6234 C 20.9848 19.6234 20.9848 19.7634 20.9848 19.8334 C 20.8448 20.0434 20.7048 20.1834 20.6348 20.3934 C 20.5648 20.5334 20.4948 20.5334 20.4248 20.3934 C 20.2848 20.2534 20.2148 20.1134 20.0748 19.9734 C 20.0048 19.9034 19.9348 19.9034 19.8648 19.9734 C 19.8648 20.1134 19.7248 20.1834 19.7248 20.3234 C 19.5848 20.6734 19.5148 21.0934 19.4448 21.5134 C 19.4448 21.7234 19.3748 21.9334 19.3048 22.1434 C 19.3048 22.2834 19.2348 22.3534 19.1648 22.4934 C 18.9548 22.7734 18.7448 23.0534 18.4648 23.2634 C 18.4648 23.2634 18.4648 23.2634 18.3948 23.2634 C 18.3248 23.2634 18.3248 23.1934 18.3248 23.0534 C 18.3248 22.8434 18.3248 22.5634 18.3248 22.2834 C 18.3248 22.0734 18.2548 21.8634 18.1848 21.6534 C 18.0448 21.3734 17.9048 21.1634 17.7648 20.9534 C 17.7648 20.9534 17.7648 20.9534 17.6948 20.9534 C 17.6248 20.9534 17.4848 21.0934 17.4148 21.0934 C 17.4148 21.0934 17.3448 21.0934 17.2748 21.0934 C 17.0648 21.0234 16.8548 20.8834 16.7848 20.6734 C 16.7848 20.6034 16.7848 20.4634 16.7848 20.1834 L 16.6448 20.1134 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -74.43 10.83)" >
<path fill="currentColor" transform=" translate(-8.35, -38.77)" d="M 6.63489 38.6634 C 6.63489 38.6634 6.77489 38.6634 6.84489 38.6634 C 6.98489 38.6634 7.12489 38.7334 7.19489 38.8034 C 7.40489 38.9434 7.68489 38.9434 7.89489 39.0134 C 8.10489 39.0134 8.24489 39.0134 8.45489 39.0134 C 8.59489 39.0134 8.66489 39.0134 8.73489 38.9434 C 8.87489 38.8734 8.94489 38.8034 9.08489 38.7334 C 9.22489 38.5934 9.22489 38.4534 9.22489 38.2434 C 9.22489 38.2434 9.22489 38.2434 9.22489 38.1734 C 9.22489 38.1034 9.22489 38.0334 9.36489 38.0334 C 9.50489 38.0334 9.64489 38.0334 9.71489 38.0334 C 9.99489 38.0334 10.0649 38.3134 10.0649 38.5234 C 10.0649 38.7334 10.0649 38.8734 9.92489 39.0834 C 9.71489 39.2934 9.43489 39.4334 9.08489 39.5034 C 8.73489 39.5034 8.38489 39.5034 8.03489 39.4334 C 7.82489 39.4334 7.61489 39.2934 7.47489 39.2234 C 7.26489 39.0834 7.05489 39.0134 6.84489 38.8734 C 6.77489 38.8734 6.70489 38.7334 6.70489 38.7334 L 6.63489 38.6634 Z" strokeLinecap="round" />
</g>
<g transform="matrix(1 0 0 1 -74.78 0.4)" >
<path fill="currentColor" transform=" translate(-8, -28.34)" d="M 6.28479 28.2333 C 6.28479 28.2333 6.42479 28.2333 6.49479 28.2333 C 6.63479 28.2333 6.77479 28.3033 6.84479 28.3733 C 7.05479 28.5133 7.33479 28.5133 7.54479 28.5833 C 7.75479 28.5833 7.89479 28.5833 8.10479 28.5833 C 8.24479 28.5833 8.31479 28.5833 8.38479 28.5133 C 8.52479 28.4433 8.59479 28.3733 8.73479 28.3033 C 8.87479 28.1633 8.87479 28.0233 8.87479 27.8133 C 8.87479 27.8133 8.87479 27.8133 8.87479 27.7433 C 8.87479 27.6733 8.87479 27.6033 9.01479 27.6033 C 9.15479 27.6033 9.29479 27.6033 9.36479 27.6033 C 9.64479 27.6033 9.71479 27.8833 9.71479 28.0933 C 9.71479 28.3033 9.71479 28.4433 9.57479 28.6533 C 9.36479 28.8633 9.08479 29.0033 8.73479 29.0733 C 8.38479 29.0733 8.03479 29.0733 7.68479 29.0033 C 7.47479 29.0033 7.26479 28.8633 7.12479 28.7933 C 6.91479 28.6533 6.70479 28.5833 6.49479 28.4433 C 6.42479 28.4433 6.35479 28.3033 6.35479 28.3033 L 6.28479 28.2333 Z" strokeLinecap="round" />
</g>
</g>
</svg>
)
}
export default AqilaLogo

36
src/components/layout/AppSidebar.tsx

@ -16,6 +16,7 @@ import {
import { useAuth, useLogout } from '@/features/auth/use-auth' import { useAuth, useLogout } from '@/features/auth/use-auth'
import { navigationConfig } from '@/config/navigation' import { navigationConfig } from '@/config/navigation'
import { siteConfig } from '@/config/site' import { siteConfig } from '@/config/site'
import { AqilaLogo } from '@/components/AqilaLogo'
type NavItemProps = { type NavItemProps = {
to?: string to?: string
@ -70,17 +71,17 @@ function SidebarUserFooter() {
const firstLetter = user?.name?.[0] ?? user?.email?.[0]?.toUpperCase() ?? 'م' const firstLetter = user?.name?.[0] ?? user?.email?.[0]?.toUpperCase() ?? 'م'
return ( return (
<SidebarFooter className="mt-auto">
<SidebarSeparator className="mb-1" />
<div className="flex items-center gap-2.5 px-2 py-1">
<div className="flex size-8 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-bold text-white">
<SidebarFooter className="mt-auto pb-4">
<SidebarSeparator className="mb-2" />
<div className="flex items-center gap-2.5 px-2 py-1.5">
<div className="flex size-9 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-bold text-white shadow-sm">
{firstLetter} {firstLetter}
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="truncate text-xs font-semibold text-grey-1">
<div className="truncate text-xs font-bold text-grey-1">
{user?.name?.trim() || 'مدیر کل سیستم'} {user?.name?.trim() || 'مدیر کل سیستم'}
</div> </div>
<div dir="ltr" className="truncate text-start text-[10px] text-grey-3">
<div dir="ltr" className="truncate text-start text-[11px] text-grey-3">
{user?.email ?? 'admin@gmail.com'} {user?.email ?? 'admin@gmail.com'}
</div> </div>
</div> </div>
@ -103,28 +104,25 @@ function SidebarUserFooter() {
export default function AppSidebar() { export default function AppSidebar() {
return ( return (
<Sidebar side="right" collapsible="offcanvas">
<SidebarHeader className="pt-[6px] pb-[6px]">
<div className="flex items-center gap-3 px-2 py-1.5">
<div className="flex size-[40px] items-center justify-center rounded-xl bg-primary text-white shadow-md">
<Ic name="sparkles" className="size-6" />
<Sidebar side="right" collapsible="offcanvas" className="h-svh">
<SidebarHeader className="py-3">
<div className="flex items-center gap-3 px-2">
<div className="flex size-10 items-center justify-center rounded-xl bg-primary text-white shadow-md">
<AqilaLogo className="size-6 text-white" />
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<div className="truncate text-sm font-extrabold leading-tight text-foreground">
<div className="truncate text-base font-extrabold leading-tight text-foreground">
{siteConfig.name} {siteConfig.name}
</div> </div>
<div className="truncate text-[10px] text-grey-3 mt-0.5">
{siteConfig.description}
</div>
</div> </div>
</div> </div>
</SidebarHeader> </SidebarHeader>
<SidebarSeparator /> <SidebarSeparator />
<SidebarContent className="no-scrollbar px-1">
{navigationConfig.map((group) => (
<SidebarGroup key={group.title}>
<SidebarGroupLabel>{group.title}</SidebarGroupLabel>
<SidebarContent className="no-scrollbar px-1 flex-1">
{navigationConfig.map((group, idx) => (
<SidebarGroup key={group.title || idx}>
{group.title ? <SidebarGroupLabel>{group.title}</SidebarGroupLabel> : null}
<SidebarMenu> <SidebarMenu>
{group.items.map((item) => ( {group.items.map((item) => (
<NavItem <NavItem

54
src/components/layout/ServerHealthTrigger.tsx

@ -0,0 +1,54 @@
import { useEffect, useState } from 'react'
import { useServerHealthStore } from '@/stores/server-health'
import ServerStatusDialog from './ServerStatusDialog'
export default function ServerHealthTrigger() {
const health = useServerHealthStore()
const [open, setOpen] = useState(false)
useEffect(() => {
health.checkHealth(false)
const timer = setInterval(() => {
health.checkHealth(true)
}, 15000)
return () => clearInterval(timer)
}, [])
return (
<>
<button
type="button"
onClick={() => setOpen(true)}
aria-label="وضعیت ارتباط سرور"
title={
health.status === 'connected'
? `سرور آنلاین (${health.latencyMs ?? 0}ms) - کلیک جهت جزئیات`
: health.status === 'checking'
? 'در حال بررسی سرور...'
: 'سرور قطع است - کلیک جهت جزئیات'
}
className="relative flex size-9 items-center justify-center rounded-xl border border-border-soft bg-card/60 transition-all hover:bg-card hover:border-primary/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring shadow-sm cursor-pointer"
>
<span className="relative flex size-2.5">
{health.status === 'connected' && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
)}
{health.status === 'error' && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-rose-400 opacity-75" />
)}
<span
className={`relative inline-flex size-2.5 rounded-full ${
health.status === 'connected'
? 'bg-emerald-500 shadow-[0_0_8px_#10b981]'
: health.status === 'checking'
? 'bg-amber-400 animate-pulse'
: 'bg-rose-500 shadow-[0_0_8px_#f43f5e]'
}`}
/>
</span>
</button>
<ServerStatusDialog open={open} onOpenChange={setOpen} />
</>
)
}

177
src/components/layout/ServerStatusDialog.tsx

@ -0,0 +1,177 @@
import { useState } from 'react'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Ic } from '@/icons'
import { useServerHealthStore } from '@/stores/server-health'
import { useAuthStore } from '@/stores/auth'
interface ServerStatusDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
}
export default function ServerStatusDialog({ open, onOpenChange }: ServerStatusDialogProps) {
const health = useServerHealthStore()
const user = useAuthStore((s) => s.user)
const [isRefreshing, setIsRefreshing] = useState(false)
const handleRefresh = async () => {
setIsRefreshing(true)
await health.checkHealth(false)
setIsRefreshing(false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[540px] p-6 space-y-5">
<DialogHeader className="pb-3 border-b border-border-soft">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div
className={`flex size-9 items-center justify-center rounded-xl ${
health.status === 'connected'
? 'bg-emerald-500/15 text-emerald-400'
: health.status === 'checking'
? 'bg-amber-500/15 text-amber-400'
: 'bg-rose-500/15 text-rose-400'
}`}
>
<Ic name="server" className="size-5" />
</div>
<div>
<DialogTitle className="text-base font-bold text-foreground">
وضعیت سیستم و سرور
</DialogTitle>
<DialogDescription className="text-xs text-grey-3 mt-0.5">
خلاصه ارتباط سرور، پیکربندی API و اطلاعات حساب
</DialogDescription>
</div>
</div>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleRefresh}
disabled={isRefreshing}
className="h-8 gap-1.5 text-xs"
>
<Ic name="refresh" className={`size-3.5 ${isRefreshing ? 'animate-spin' : ''}`} />
<span>{isRefreshing ? 'در حال تست...' : 'بررسی مجدد'}</span>
</Button>
</div>
</DialogHeader>
<div className="space-y-3.5">
{/* بخش ۱: وضعیت سرور جنگو */}
<div className="rounded-xl border border-border-soft bg-card/60 p-4 space-y-2.5">
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-foreground flex items-center gap-2">
<span
className={`size-2.5 rounded-full ${
health.status === 'connected'
? 'bg-emerald-500 shadow-[0_0_8px_#10b981]'
: health.status === 'checking'
? 'bg-amber-400 animate-pulse'
: 'bg-rose-500 shadow-[0_0_8px_#f43f5e]'
}`}
/>
وضعیت سرور جنگو (Backend)
</span>
<Badge
variant="outline"
className={
health.status === 'connected'
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400'
: health.status === 'checking'
? 'border-amber-500/30 bg-amber-500/10 text-amber-400'
: 'border-rose-500/30 bg-rose-500/10 text-rose-400'
}
>
{health.status === 'connected'
? 'آنلاین و متصل'
: health.status === 'checking'
? 'در حال بررسی...'
: 'قطع ارتباط'}
</Badge>
</div>
<div className="flex items-center justify-between text-xs text-grey-2 pt-1 border-t border-border-soft">
<span className="text-grey-3">{health.message}</span>
{health.latencyMs !== undefined && health.status === 'connected' && (
<span dir="ltr" className="font-mono text-emerald-400 text-[11px] font-semibold">
{health.latencyMs} ms
</span>
)}
</div>
</div>
{/* بخش ۲: پیکربندی API و سرور */}
<div className="rounded-xl border border-border-soft bg-card/60 p-4 space-y-2">
<div className="flex items-center justify-between pb-1 border-b border-border-soft">
<span className="text-xs font-semibold text-foreground flex items-center gap-1.5">
<Ic name="code" className="size-3.5 text-grey-3" />
پیکربندی API و سرور
</span>
</div>
<div className="space-y-1.5 font-mono text-[11px]" dir="ltr">
<div className="flex justify-between items-center text-grey-2">
<span className="text-grey-3 font-sans text-xs">Target Server:</span>
<span className="text-foreground">http://127.0.0.1:8000</span>
</div>
<div className="flex justify-between items-center text-grey-2">
<span className="text-grey-3 font-sans text-xs">API Prefix:</span>
<span className="text-foreground">/api</span>
</div>
<div className="flex justify-between items-center text-grey-2">
<span className="text-grey-3 font-sans text-xs">Auth Header:</span>
<span className="text-primary font-semibold">Token (DRF)</span>
</div>
</div>
</div>
{/* بخش ۳: اطلاعات حساب کاربری جاری */}
<div className="rounded-xl border border-border-soft bg-card/60 p-4 space-y-2">
<div className="flex items-center justify-between pb-1 border-b border-border-soft">
<span className="text-xs font-semibold text-foreground flex items-center gap-1.5">
<Ic name="users" className="size-3.5 text-grey-3" />
اطلاعات حساب کاربری جاری
</span>
<span className="text-[11px] text-primary font-medium">
{user?.role === 'superadmin'
? 'مدیر ارشد (Superuser)'
: user?.role === 'admin'
? 'مدیر سیستم (Staff)'
: 'کاربر'}
</span>
</div>
<div className="space-y-1.5 text-xs">
<div className="flex justify-between items-center">
<span className="text-grey-3">نام و نام خانوادگی:</span>
<span className="font-semibold text-foreground">{user?.name || 'مدیر سیستم'}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-grey-3">ایمیل:</span>
<span dir="ltr" className="font-mono text-[11px] text-grey-2">
{user?.email || '-'}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-grey-3">شناسه کاربری (ID):</span>
<span dir="ltr" className="font-mono text-[11px] text-grey-3">
#{user?.id ?? 1}
</span>
</div>
</div>
</div>
</div>
</DialogContent>
</Dialog>
)
}

28
src/components/layout/ThemeToggle.tsx

@ -0,0 +1,28 @@
import { useThemeStore } from '@/stores/theme'
import { Switch } from '@/components/ui/switch'
import { Ic } from '@/icons'
export default function ThemeToggle() {
const { isDark, toggleTheme } = useThemeStore()
return (
<div
className="flex items-center gap-1.5 rounded-xl border border-border-soft bg-card/60 px-2 py-1 shadow-sm transition-colors"
title={isDark ? 'تغییر به حالت روشن (لایت)' : 'تغییر به حالت تاریک (دارک)'}
>
<Ic
name="sun"
className={`size-3.5 transition-colors ${!isDark ? 'text-amber-500' : 'text-grey-4'}`}
/>
<Switch
checked={isDark}
onCheckedChange={toggleTheme}
aria-label="سوئیچ دارک و لایت"
/>
<Ic
name="moon"
className={`size-3.5 transition-colors ${isDark ? 'text-primary' : 'text-grey-4'}`}
/>
</div>
)
}

19
src/components/layout/Topbar.tsx

@ -1,6 +1,7 @@
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { SidebarTrigger } from '@/components/ui/sidebar' import { SidebarTrigger } from '@/components/ui/sidebar'
import GlobalSearchTrigger from '@/features/global-search/GlobalSearchTrigger'
import ThemeToggle from '@/components/layout/ThemeToggle'
import ServerHealthTrigger from '@/components/layout/ServerHealthTrigger'
import { navigationConfig } from '@/config/navigation' import { navigationConfig } from '@/config/navigation'
import { siteConfig } from '@/config/site' import { siteConfig } from '@/config/site'
@ -19,11 +20,12 @@ export default function Topbar() {
if (pathname === '/') currentTitle = 'داشبورد مدیریت' if (pathname === '/') currentTitle = 'داشبورد مدیریت'
return ( return (
<header className="flex h-16 shrink-0 items-center gap-3 border-b border-border-soft bg-background px-4 md:px-8">
<header className="flex h-16 shrink-0 items-center justify-between gap-3 border-b border-border-soft bg-background px-4 md:px-8">
<div className="flex items-center gap-3 min-w-0">
<SidebarTrigger className="-ms-1 shrink-0" /> <SidebarTrigger className="-ms-1 shrink-0" />
{/* عنوان و مسیر */} {/* عنوان و مسیر */}
<div className="min-w-0 flex-1">
<div className="min-w-0">
<p className="truncate text-[11px] text-grey-3"> <p className="truncate text-[11px] text-grey-3">
{siteConfig.shortName} / {currentTitle} {siteConfig.shortName} / {currentTitle}
</p> </p>
@ -31,9 +33,16 @@ export default function Topbar() {
{currentTitle} {currentTitle}
</h1> </h1>
</div> </div>
</div>
{/* ابزارهای هدر: وضعیت سرور، سوئیچ تم */}
<div className="flex items-center gap-2.5 shrink-0">
{/* نشانگر کوچک وضعیت اتصال سرور */}
<ServerHealthTrigger />
{/* ابزار جستجوی سراسری (Cmd+K) */}
<GlobalSearchTrigger />
{/* سوئیچ دارک و لایت */}
<ThemeToggle />
</div>
</header> </header>
) )
} }

2
src/components/ui/dialog.tsx

@ -22,7 +22,7 @@ function DialogContent({ className, children, ...props }: React.ComponentProps<t
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
className={cn( className={cn(
'fixed left-1/2 top-1/2 z-50 w-full max-w-[640px] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border-soft bg-[#27282e] shadow-[0_30px_80px_rgba(0,0,0,.55)] anim-pop outline-none',
'fixed left-1/2 top-1/2 z-50 w-full max-w-[640px] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border-soft bg-card text-card-foreground shadow-[0_30px_80px_rgba(0,0,0,.35)] anim-pop outline-none',
className, className,
)} )}
{...props} {...props}

4
src/components/ui/sidebar.tsx

@ -98,7 +98,7 @@ function SidebarProvider({
data-slot="sidebar-wrapper" data-slot="sidebar-wrapper"
style={{ '--sidebar-width': SIDEBAR_WIDTH, '--sidebar-width-icon': SIDEBAR_WIDTH_ICON, ...style } as React.CSSProperties} style={{ '--sidebar-width': SIDEBAR_WIDTH, '--sidebar-width-icon': SIDEBAR_WIDTH_ICON, ...style } as React.CSSProperties}
className={cn( className={cn(
'group/sidebar-wrapper flex h-[calc(100svh-var(--player-h))] w-full has-data-[variant=inset]:bg-sidebar',
'group/sidebar-wrapper flex min-h-svh h-svh w-full has-data-[variant=inset]:bg-sidebar',
className, className,
)} )}
{...props} {...props}
@ -181,7 +181,7 @@ function Sidebar({
<div <div
data-slot="sidebar-container" data-slot="sidebar-container"
className={cn( className={cn(
'fixed top-0 z-10 hidden h-[calc(100svh-var(--player-h))] w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
'fixed top-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
side === 'left' side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]' ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]', : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',

4
src/components/ui/switch.tsx

@ -6,12 +6,12 @@ function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimi
return ( return (
<SwitchPrimitive.Root <SwitchPrimitive.Root
className={cn( className={cn(
'peer inline-flex h-[22px] w-[40px] shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors data-[state=checked]:bg-[#2ecc71] data-[state=unchecked]:bg-white/15 outline-none focus-visible:ring-2 focus-visible:ring-ring',
'peer inline-flex h-[22px] w-[40px] shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors data-[state=checked]:bg-primary data-[state=unchecked]:bg-grey-3/30 outline-none focus-visible:ring-2 focus-visible:ring-ring',
className, className,
)} )}
{...props} {...props}
> >
<SwitchPrimitive.Thumb className="pointer-events-none block size-[17px] rounded-full bg-white shadow transition-transform data-[state=checked]:-translate-x-[19px] data-[state=unchecked]:-translate-x-[2px]" />
<SwitchPrimitive.Thumb className="pointer-events-none block size-[17px] rounded-full bg-white shadow-sm transition-transform data-[state=checked]:-translate-x-[19px] data-[state=unchecked]:-translate-x-[2px]" />
</SwitchPrimitive.Root> </SwitchPrimitive.Root>
) )
} }

625
src/components/utils/dev-click-to-component.tsx

@ -0,0 +1,625 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
"use client";
import { useEffect, useState, useRef } from "react";
const IDE_SCHEMES = [
{
matches: ["antigravity"],
createUrl: (locator: string) => `antigravity://file/${locator}`,
},
{
matches: ["cursor"],
createUrl: (locator: string) => `cursor://file/${locator}`,
},
{
matches: ["vscode", "code"],
createUrl: (locator: string) => `vscode://file/${locator}`,
},
{
matches: ["webstorm", "intellij"],
createUrl: (locator: string) => `webstorm://open?file=${locator}`,
},
{
matches: ["sublime"],
createUrl: (locator: string) => `subl://open?url=file://${locator}`,
},
{
matches: ["atom", "nova"],
createUrl: (locator: string) => `atom://open?url=file://${locator}`,
},
] as const;
function parseLocator(locator: string) {
const match = locator.match(/^(.*):(\d+|unknown):(\d+|unknown)$/);
if (!match) {
return { filePath: locator, line: null, column: null };
}
const [, filePath, line, column] = match;
return {
filePath,
line: line === "unknown" ? null : Number(line),
column: column === "unknown" ? null : Number(column),
};
}
export function DevClickToComponent() {
const [isInspecting, setIsInspecting] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const [position, setPosition] = useState<{ x: number; y: number } | null>(null);
const [isDragging, setIsDragging] = useState(false);
const dragStart = useRef({ x: 0, y: 0, buttonX: 0, buttonY: 0, hasMoved: false });
// Detect mobile size
useEffect(() => {
const checkSize = () => {
setIsMobile(window.innerWidth <= 768);
};
checkSize();
window.addEventListener("resize", checkSize);
return () => window.removeEventListener("resize", checkSize);
}, []);
// Initialize button position
useEffect(() => {
if (typeof window !== "undefined") {
setPosition({
x: window.innerWidth - 66, // 50px width + 16px margin
y: window.innerHeight - 66, // 50px height + 16px margin
});
}
}, [isMobile]);
// Keep button in bounds on resize
useEffect(() => {
const handleResize = () => {
if (!isMobile) return;
setPosition((prev) => {
if (!prev) return null;
return {
x: Math.max(16, Math.min(window.innerWidth - 66, prev.x)),
y: Math.max(16, Math.min(window.innerHeight - 66, prev.y)),
};
});
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [isMobile]);
useEffect(() => {
const userAgent = navigator.userAgent.toLowerCase();
// Desktop: Alt + Click logic
const handleDesktopClick = (event: MouseEvent) => {
if (!event.altKey) {
return;
}
const target = event.target;
if (!(target instanceof HTMLElement)) {
return;
}
const locator = target
.closest<HTMLElement>("[data-locator]")
?.getAttribute("data-locator");
if (!locator) {
return;
}
event.preventDefault();
event.stopPropagation();
const { filePath, line, column } = parseLocator(locator);
const positionSuffix =
line === null ? "" : `:${line}${column === null ? "" : `:${column}`}`;
const locatorString = `${filePath}${positionSuffix}`;
// Use the API route to execute the shell command which reuses the existing window
fetch("/api/open-in-ide", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ locator: locatorString, userAgent }),
}).catch((err) => {
console.error("[DevClickToComponent] Error sending API request:", err);
// Fallback to URL scheme
const ideUrl =
IDE_SCHEMES.find(({ matches }) =>
matches.some((match) => userAgent.includes(match)),
)?.createUrl(locatorString) ??
`antigravity://file/${locatorString}`;
try {
window.location.href = ideUrl;
} catch {
window.open(`file://${filePath}`, "_blank", "noopener,noreferrer");
}
});
};
document.addEventListener("click", handleDesktopClick, true);
return () => {
document.removeEventListener("click", handleDesktopClick, true);
};
}, []);
// Mobile: Inspect Mode click interception
useEffect(() => {
if (!isMobile || !isInspecting) return;
const userAgent = navigator.userAgent.toLowerCase();
const handleInspectClick = async (event: MouseEvent) => {
const target = event.target;
if (!(target instanceof Element)) return;
// Ignore clicks on the inspect button itself
if (target.closest(".dev-inspect-btn")) {
return;
}
event.preventDefault();
event.stopPropagation();
const locator = target
.closest("[data-locator]")
?.getAttribute("data-locator");
if (locator) {
console.log(`[DevClickToComponent] Inspect Mode matched element. Opening in IDE:`, locator);
// Flash target element outline briefly as visual feedback
const element = target.closest<HTMLElement>("[data-locator]");
if (element) {
const originalOutline = element.style.outline;
element.style.outline = "3px solid #3b82f6";
setTimeout(() => {
element.style.outline = originalOutline;
}, 400);
}
try {
await fetch("/api/open-in-ide", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ locator, userAgent }),
});
} catch (err) {
console.error("[DevClickToComponent] Error sending API request:", err);
}
}
setIsInspecting(false);
};
// Use capturing phase to intercept clicks/taps before they trigger other actions
document.addEventListener("click", handleInspectClick, true);
return () => {
document.removeEventListener("click", handleInspectClick, true);
};
}, [isMobile, isInspecting]);
// Dev mode: Intercept and forward console logs globally
useEffect(() => {
const originalLog = console.log;
const originalWarn = console.warn;
const originalError = console.error;
let isSending = false;
const forwardLog = async (type: "log" | "warn" | "error", args: any[]) => {
if (isSending) return;
isSending = true;
try {
await fetch("/api/remote-logs", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ type, args }),
});
} catch (err) {
// Print failure directly to the original console to prevent recursion
originalError.call(console, "[DevClickToComponent] Failed to forward log to server:", err);
} finally {
isSending = false;
}
};
console.log = (...args: any[]) => {
originalLog.apply(console, args);
forwardLog("log", args);
};
console.warn = (...args: any[]) => {
originalWarn.apply(console, args);
forwardLog("warn", args);
};
console.error = (...args: any[]) => {
originalError.apply(console, args);
forwardLog("error", args);
};
return () => {
console.log = originalLog;
console.warn = originalWarn;
console.error = originalError;
};
}, []);
// Dev mode: Intercept and forward fetch requests globally
useEffect(() => {
const originalFetch = window.fetch;
window.fetch = async (input, init) => {
const url =
typeof input === "string"
? input
: input instanceof URL
? input.href
: input instanceof Request
? input.url
: "";
// Ignore logging requests to prevent infinite recursion
if (
url.includes("/api/remote-logs") ||
url.includes("/api/remote-network") ||
url.includes("/api/open-in-ide")
) {
return originalFetch(input, init);
}
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
const startTime = Date.now();
let requestBody: string | null = null;
if (init?.body) {
if (typeof init.body === "string") {
requestBody = init.body;
} else {
requestBody = "[Non-string payload]";
}
} else if (input instanceof Request) {
try {
const clonedReq = input.clone();
requestBody = await clonedReq.text();
} catch {
requestBody = null;
}
}
try {
const response = await originalFetch(input, init);
const duration = Date.now() - startTime;
const status = response.status;
let responseBody: string | null = null;
try {
const clonedRes = response.clone();
responseBody = await clonedRes.text();
} catch {
responseBody = "[Unreadable response body]";
}
originalFetch("/api/remote-network", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
method,
url,
status,
requestBody,
responseBody,
duration,
}),
}).catch(() => {});
return response;
} catch (err) {
const duration = Date.now() - startTime;
originalFetch("/api/remote-network", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
method,
url,
status: 0,
requestBody,
responseBody: String(err),
duration,
}),
}).catch(() => {});
throw err;
}
};
return () => {
window.fetch = originalFetch;
};
}, []);
// Dev mode: Intercept and forward XMLHttpRequest (XHR) requests globally
useEffect(() => {
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (this: any, method: any, url: any, ...args: any[]) {
this._method = method;
this._url = typeof url === "string" ? url : url.toString();
return originalOpen.apply(this, [method, url, ...args] as any);
};
XMLHttpRequest.prototype.send = function (this: any, body?: any) {
const url = this._url || "";
const method = this._method || "GET";
const startTime = Date.now();
// Avoid infinite recursion on dev logging endpoints
if (
url.includes("/api/remote-logs") ||
url.includes("/api/remote-network") ||
url.includes("/api/open-in-ide")
) {
return originalSend.apply(this, [body]);
}
this.addEventListener("loadend", () => {
const duration = Date.now() - startTime;
const status = this.status;
let responseBody = "";
try {
responseBody = this.responseText;
} catch {
// If responseText is not accessible (e.g. responseType is not '' or 'text'), fall back to response
try {
if (typeof this.response === "string") {
responseBody = this.response;
} else if (this.response) {
responseBody = JSON.stringify(this.response);
}
} catch {
responseBody = "[Binary or unreadable response]";
}
}
let requestBody: string | null = null;
if (body) {
if (typeof body === "string") {
requestBody = body;
} else {
requestBody = "[Non-string payload]";
}
}
window.fetch("/api/remote-network", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
method,
url,
status,
requestBody,
responseBody,
duration,
}),
}).catch(() => {});
});
return originalSend.apply(this, [body]);
};
return () => {
XMLHttpRequest.prototype.open = originalOpen;
XMLHttpRequest.prototype.send = originalSend;
};
}, []);
// Dragging event handlers
const handlePointerDown = (e: React.PointerEvent<HTMLButtonElement>) => {
e.currentTarget.setPointerCapture(e.pointerId);
setIsDragging(true);
const startX = position?.x ?? (window.innerWidth - 66);
const startY = position?.y ?? (window.innerHeight - 126);
dragStart.current = {
x: e.clientX,
y: e.clientY,
buttonX: startX,
buttonY: startY,
hasMoved: false,
};
};
const handlePointerMove = (e: React.PointerEvent<HTMLButtonElement>) => {
if (!isDragging) return;
const dx = e.clientX - dragStart.current.x;
const dy = e.clientY - dragStart.current.y;
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
dragStart.current.hasMoved = true;
}
const newX = Math.max(16, Math.min(window.innerWidth - 66, dragStart.current.buttonX + dx));
const newY = Math.max(16, Math.min(window.innerHeight - 126, dragStart.current.buttonY + dy));
setPosition({ x: newX, y: newY });
};
const handlePointerUp = (e: React.PointerEvent<HTMLButtonElement>) => {
if (!isDragging) return;
e.currentTarget.releasePointerCapture(e.pointerId);
setIsDragging(false);
};
const handleInspectClick = () => {
if (!dragStart.current.hasMoved) {
setIsInspecting(!isInspecting);
}
};
const [isSendingStorage, setIsSendingStorage] = useState(false);
const [sendSuccess, setSendSuccess] = useState(false);
const handleSendStorageClick = async () => {
if (dragStart.current.hasMoved) return;
if (isSendingStorage) return;
setIsSendingStorage(true);
setSendSuccess(false);
try {
const data: Record<string, any> = {};
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i);
if (key) {
const val = window.localStorage.getItem(key);
try {
data[key] = JSON.parse(val || "");
} catch {
data[key] = val;
}
}
}
const response = await fetch("/api/dev-storage", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (response.ok) {
setSendSuccess(true);
setTimeout(() => setSendSuccess(false), 2000);
}
} catch (err) {
console.error("[DevClickToComponent] Failed to send localStorage values:", err);
} finally {
setIsSendingStorage(false);
}
};
return (
<div
style={{
position: "fixed",
left: position ? `${position.x}px` : "auto",
top: position ? `${position.y}px` : "auto",
bottom: position ? "auto" : "16px",
right: position ? "auto" : "16px",
display: "flex",
flexDirection: "column",
gap: "10px",
zIndex: 99998,
touchAction: "none",
}}
>
{/* Send LocalStorage Button */}
<button
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onClick={handleSendStorageClick}
style={{
width: "50px",
height: "50px",
borderRadius: "25px",
backgroundColor: sendSuccess ? "#10b981" : "#a855f7",
color: "#ffffff",
border: "none",
boxShadow: sendSuccess
? "0 0 15px rgba(16, 185, 129, 0.6)"
: "0 4px 12px rgba(0,0,0,0.35)",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: isDragging ? "grabbing" : "pointer",
transition: isDragging ? "none" : "background-color 0.25s, transform 0.25s, box-shadow 0.25s",
outline: "none",
transform: isSendingStorage ? "scale(0.95)" : "scale(1)",
}}
title="Send LocalStorage to Terminal API"
>
{isSendingStorage ? (
<svg className="animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" style={{ width: "22px", height: "22px", pointerEvents: "none" }}>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
) : sendSuccess ? (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2.5} stroke="currentColor" style={{ width: "22px", height: "22px", pointerEvents: "none" }}>
<path strokeLinecap="round" strokeLinejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" style={{ width: "22px", height: "22px", pointerEvents: "none" }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 16.5V9.75m0 0 3 3m-3-3-3 3M6.75 19.5a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.233-2.33 3 3 0 0 1 3.758 3.848A3.752 3.752 0 0 1 18 19.5H6.75Z" />
</svg>
)}
</button>
{/* Floating Action Draggable Button (Inspect Mode) */}
{isMobile && (
<button
className="dev-inspect-btn"
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onClick={handleInspectClick}
style={{
width: "50px",
height: "50px",
borderRadius: "25px",
backgroundColor: isInspecting ? "#3b82f6" : "#1f2937",
color: "#ffffff",
border: "none",
boxShadow: isInspecting
? "0 0 15px rgba(59, 130, 246, 0.6)"
: "0 4px 12px rgba(0,0,0,0.35)",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: isDragging ? "grabbing" : "pointer",
transition: isDragging ? "none" : "background-color 0.25s, transform 0.25s, box-shadow 0.25s",
outline: "none",
transform: isInspecting ? "scale(1.1)" : "scale(1)",
}}
title="Toggle Dev Inspect Mode (Drag to move)"
>
{isInspecting ? (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2.5} stroke="currentColor" style={{ width: "22px", height: "22px", pointerEvents: "none" }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" style={{ width: "22px", height: "22px", pointerEvents: "none" }}>
<path strokeLinecap="round" strokeLinejoin="round" d="m15.75 15.75-2.489-2.489m0 0a3.375 3.375 0 1 0-4.773-4.773 3.375 3.375 0 0 0 4.774 4.774ZM21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
)}
</button>
)}
</div>
);
}
export default DevClickToComponent;

2
src/config/navigation.ts

@ -15,7 +15,7 @@ export interface NavGroupConfig {
export const navigationConfig: NavGroupConfig[] = [ export const navigationConfig: NavGroupConfig[] = [
{ {
title: 'پنل مدیریت عقیله',
title: '',
items: [ items: [
{ title: 'داشبورد', to: '/', icon: 'home' }, { title: 'داشبورد', to: '/', icon: 'home' },
], ],

8
src/config/site.ts

@ -8,10 +8,10 @@ export interface SiteConfig {
} }
export const siteConfig: SiteConfig = { export const siteConfig: SiteConfig = {
name: 'پنل مدیریت',
shortName: 'داشبورد',
description: 'قالب پیشرفته و مدرن داشبورد مدیریتی با React 19، Tailwind CSS v4 و Shadcn UI',
name: 'پنل مدیریت عقیله',
shortName: 'عقیله',
description: 'پنل مدیریت و داشبورد جامع سامانه عقیله',
version: '1.0.0', version: '1.0.0',
logoText: 'DASHBOARD',
logoText: 'عقیله',
defaultTheme: 'dark', defaultTheme: 'dark',
} }

3
src/features/auth/LoginPage.tsx

@ -7,6 +7,7 @@ import { Input } from '@/components/ui/input'
import { useAuth, useLoginMutation } from '@/features/auth/use-auth' import { useAuth, useLoginMutation } from '@/features/auth/use-auth'
import { ApiError } from '@/services/http' import { ApiError } from '@/services/http'
import { siteConfig } from '@/config/site' import { siteConfig } from '@/config/site'
import { AqilaLogo } from '@/components/AqilaLogo'
function Field({ label, children }: { label: string; children: React.ReactNode }) { function Field({ label, children }: { label: string; children: React.ReactNode }) {
return ( return (
@ -62,7 +63,7 @@ export default function LoginPage() {
{/* لوگو و نام برند */} {/* لوگو و نام برند */}
<div className="mb-6 flex flex-col items-center gap-2.5 text-center"> <div className="mb-6 flex flex-col items-center gap-2.5 text-center">
<div className="flex size-14 items-center justify-center rounded-2xl bg-primary text-white shadow-lg"> <div className="flex size-14 items-center justify-center rounded-2xl bg-primary text-white shadow-lg">
<Ic name="sparkles" className="size-8" />
<AqilaLogo className="size-8 text-white" />
</div> </div>
<div> <div>
<h1 className="text-xl font-black text-foreground">{siteConfig.name}</h1> <h1 className="text-xl font-black text-foreground">{siteConfig.name}</h1>

165
src/features/dashboard/DashboardHome.tsx

@ -1,47 +1,9 @@
import { useEffect, useState } from 'react'
import { Ic } from '@/icons' import { Ic } from '@/icons'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { apiFetch } from '@/services/http'
interface BackendHealth {
status: 'checking' | 'connected' | 'error'
message?: string
latencyMs?: number
}
export default function DashboardHome() { export default function DashboardHome() {
const user = useAuthStore((s) => s.user) const user = useAuthStore((s) => s.user)
const [health, setHealth] = useState<BackendHealth>({ status: 'checking' })
const checkConnection = async (isSilent = false) => {
if (!isSilent) setHealth((prev) => ({ ...prev, status: 'checking' }))
const start = performance.now()
try {
await apiFetch('about-us/', { skipAuth: true })
const latency = Math.round(performance.now() - start)
setHealth({
status: 'connected',
latencyMs: latency,
message: 'ارتباط مستقیم با بک‌اند جنگو (aqila_backend) برقرار است',
})
} catch {
setHealth({
status: 'error',
message: 'عدم دریافت پاسخ از سرور (بررسی کنید که سرور جنگو فعال باشد)',
})
}
}
useEffect(() => {
checkConnection(false)
// بررسی خودکار سلامت ارتباط هر ۱۰ ثانیه یک‌بار
const timer = setInterval(() => {
checkConnection(true)
}, 10000)
return () => clearInterval(timer)
}, [])
return ( return (
<div className="space-y-6 pt-2"> <div className="space-y-6 pt-2">
@ -57,136 +19,13 @@ export default function DashboardHome() {
</Badge> </Badge>
</div> </div>
<p className="text-xs text-grey-3"> <p className="text-xs text-grey-3">
پنل مدیریت عقیله پاکسازی شد و آماده اضافه کردن ویجتها، جداول و ماژولهای جدید شماست.
پنل مدیریت عقیله آماده اضافه کردن ویجتها، جداول و ماژولهای جدید شماست.
</p> </p>
</div> </div>
{/* نشانگر وضعیت خودکار رنگی (سبز / قرمز) */}
<div className="flex items-center">
<div
className={`inline-flex items-center gap-2.5 rounded-full border px-3.5 py-1.5 text-xs font-medium transition-colors ${
health.status === 'connected'
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400'
: health.status === 'checking'
? 'border-amber-500/30 bg-amber-500/10 text-amber-400'
: 'border-rose-500/30 bg-rose-500/10 text-rose-400'
}`}
>
<span className="relative flex size-2.5">
{health.status === 'connected' && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
)}
{health.status === 'error' && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-rose-400 opacity-75" />
)}
<span
className={`relative inline-flex size-2.5 rounded-full ${
health.status === 'connected'
? 'bg-emerald-500 shadow-[0_0_8px_#10b981]'
: health.status === 'checking'
? 'bg-amber-400 animate-pulse'
: 'bg-rose-500 shadow-[0_0_8px_#f43f5e]'
}`}
/>
</span>
<span>
{health.status === 'connected'
? 'ارتباط آنلاین با سرور'
: health.status === 'checking'
? 'در حال بررسی...'
: 'قطع ارتباط'}
</span>
{health.latencyMs !== undefined && health.status === 'connected' && (
<span className="font-mono text-[11px] opacity-75" dir="ltr">
({health.latencyMs}ms)
</span>
)}
</div>
</div>
</div>
{/* کارت‌های وضعیت سیستم */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{/* کارت وضعیت بک‌اند جنگو */}
<div className="rounded-xl border border-border-soft bg-card/60 p-5 shadow-sm">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-grey-3">وضعیت سرور جنگو</span>
<div
className={`size-2.5 rounded-full ${
health.status === 'connected'
? 'bg-emerald-500 shadow-[0_0_8px_#10b981]'
: health.status === 'checking'
? 'bg-amber-400 animate-pulse'
: 'bg-rose-500 shadow-[0_0_8px_#f43f5e]'
}`}
/>
</div>
<div className="mt-3 flex items-baseline gap-2">
<span className="text-base font-bold text-foreground">
{health.status === 'connected'
? 'متصل'
: health.status === 'checking'
? 'در حال بررسی...'
: 'قطع ارتباط'}
</span>
{health.latencyMs !== undefined && (
<span className="text-[11px] text-grey-3 font-mono" dir="ltr">
({health.latencyMs} ms)
</span>
)}
</div>
<p className="mt-2 text-[11px] text-grey-3">{health.message}</p>
</div>
{/* کارت مشخصات اتصال بک‌اند */}
<div className="rounded-xl border border-border-soft bg-card/60 p-5 shadow-sm">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-grey-3">پیکربندی API و سرور</span>
<Ic name="code" className="size-4 text-grey-3" />
</div>
<div className="mt-3 space-y-1.5 font-mono text-[11px]" dir="ltr">
<div className="flex justify-between text-grey-2">
<span className="text-grey-3">Target Server:</span>
<span>http://127.0.0.1:8000</span>
</div>
<div className="flex justify-between text-grey-2">
<span className="text-grey-3">API Prefix:</span>
<span>/api</span>
</div>
<div className="flex justify-between text-grey-2">
<span className="text-grey-3">Auth Header:</span>
<span>Token (DRF)</span>
</div>
</div>
</div>
{/* کارت حساب کاربری */}
<div className="rounded-xl border border-border-soft bg-card/60 p-5 shadow-sm sm:col-span-2 lg:col-span-1">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-grey-3">اطلاعات حساب جاری</span>
<Ic name="users" className="size-4 text-grey-3" />
</div>
<div className="mt-3 space-y-1.5 text-xs">
<div className="flex justify-between">
<span className="text-grey-3">نام کاربر:</span>
<span className="font-semibold text-foreground">{user?.name || 'مدیر'}</span>
</div>
<div className="flex justify-between">
<span className="text-grey-3">ایمیل:</span>
<span dir="ltr" className="font-mono text-[11px] text-grey-2">
{user?.email || '-'}
</span>
</div>
<div className="flex justify-between">
<span className="text-grey-3">نقش:</span>
<span className="text-primary font-medium">{user?.role || 'admin'}</span>
</div>
</div>
</div>
</div> </div>
{/* بوم خالی آماده توسعه */} {/* بوم خالی آماده توسعه */}
<div className="rounded-2xl border border-dashed border-border-soft bg-card/30 p-8 text-center">
<div className="rounded-2xl border border-dashed border-border-soft bg-card/30 p-12 text-center">
<div className="mx-auto flex size-12 items-center justify-center rounded-2xl bg-primary/10 text-primary"> <div className="mx-auto flex size-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
<Ic name="sparkles" className="size-6" /> <Ic name="sparkles" className="size-6" />
</div> </div>

32
src/features/global-search/GlobalSearchTrigger.tsx

@ -1,44 +1,18 @@
import { Ic } from '@/icons' import { Ic } from '@/icons'
import { useGlobalSearchStore } from '@/stores/global-search' import { useGlobalSearchStore } from '@/stores/global-search'
const isMac =
typeof navigator !== 'undefined' &&
/mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent)
export default function GlobalSearchTrigger() { export default function GlobalSearchTrigger() {
const setOpen = useGlobalSearchStore((s) => s.setOpen) const setOpen = useGlobalSearchStore((s) => s.setOpen)
return ( return (
<>
{/* دسکتاپ: پیلِ شبیه اینپوت با میانبر صفحه‌کلید */}
<div className="hidden h-10 w-[300px] items-center gap-1.5 rounded-full border border-border-soft bg-white/5 ps-4 pe-2 transition-colors focus-within:border-ring hover:bg-white/8 md:flex">
<button
type="button"
onClick={() => setOpen(true)}
aria-label="باز کردن جستجو"
className="flex min-w-0 flex-1 cursor-text items-center gap-2.5 bg-transparent text-right outline-none [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-grey-3"
>
<Ic name="search" />
<span className="min-w-0 flex-1 truncate text-xs text-grey-3">
جستجو یا اجرای فرمان...
</span>
<kbd className="flex shrink-0 items-center gap-0.5 rounded-md border border-border-soft bg-white/5 px-1.5 py-0.5 text-[10px] font-sans text-grey-3">
{isMac ? '⌘' : 'Ctrl'} K
</kbd>
</button>
</div>
{/* موبایل: دکمهٔ آیکونی کوچک */}
<div className="flex items-center gap-1.5 md:hidden">
<button <button
type="button" type="button"
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
aria-label="باز کردن جستجو"
className="flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-border-soft bg-white/5 text-grey-3 transition-colors hover:bg-white/8 [&_svg]:size-4"
title="جستجو (Ctrl+K)"
aria-label="جستجو"
className="flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-xl border border-border-soft bg-card/60 text-grey-2 transition-all hover:bg-card hover:text-foreground hover:border-primary/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-4 shadow-sm"
> >
<Ic name="search" /> <Ic name="search" />
</button> </button>
</div>
</>
) )
} }

5
src/icons.tsx

@ -24,6 +24,7 @@ const PATHS: Record<string, string> = {
volume: '<path d="M11 5 6 9H2v6h4l5 4Z"/><path d="M15.5 8.5a5 5 0 0 1 0 7"/>', volume: '<path d="M11 5 6 9H2v6h4l5 4Z"/><path d="M15.5 8.5a5 5 0 0 1 0 7"/>',
list: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><circle cx="4" cy="6" r="1"/><circle cx="4" cy="12" r="1"/><circle cx="4" cy="18" r="1"/>', list: '<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><circle cx="4" cy="6" r="1"/><circle cx="4" cy="12" r="1"/><circle cx="4" cy="18" r="1"/>',
grid: '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>', grid: '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>',
layoutDashboard: '<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/>',
sparkles: '<path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2 2M16.4 16.4l2 2M18.4 5.6l-2 2M7.6 16.4l-2 2"/><circle cx="12" cy="12" r="3.5"/>', sparkles: '<path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2 2M16.4 16.4l2 2M18.4 5.6l-2 2M7.6 16.4l-2 2"/><circle cx="12" cy="12" r="3.5"/>',
share: '<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.6" y1="10.5" x2="15.4" y2="6.5"/><line x1="8.6" y1="13.5" x2="15.4" y2="17.5"/>', share: '<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.6" y1="10.5" x2="15.4" y2="6.5"/><line x1="8.6" y1="13.5" x2="15.4" y2="17.5"/>',
users: '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.9"/><path d="M16 3.1a4 4 0 0 1 0 7.8"/>', users: '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.9"/><path d="M16 3.1a4 4 0 0 1 0 7.8"/>',
@ -65,6 +66,10 @@ const PATHS: Record<string, string> = {
chart: '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>', chart: '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>',
shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>', shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
folder: '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>', folder: '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>',
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
server: '<rect width="20" height="8" x="2" y="2" rx="2" ry="2"/><rect width="20" height="8" x="2" y="14" rx="2" ry="2"/><line x1="6" x2="6.01" y1="6" y2="6"/><line x1="6" x2="6.01" y1="18" y2="18"/>',
code: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
} }
export type IconName = keyof typeof PATHS export type IconName = keyof typeof PATHS

47
src/index.css

@ -9,7 +9,7 @@
--card-foreground: #ffffff; --card-foreground: #ffffff;
--popover: #1e2128; --popover: #1e2128;
--popover-foreground: #ffffff; --popover-foreground: #ffffff;
--primary: #6366f1; /* رنگ اصلی تم (قابل تغییر برای هر برند) */
--primary: #d09460; /* رنگ اصلی برند عقیله (برنز هویت بصری) */
--primary-foreground: #ffffff; --primary-foreground: #ffffff;
--secondary: #2c303b; --secondary: #2c303b;
--secondary-foreground: #ebebeb; --secondary-foreground: #ebebeb;
@ -20,18 +20,18 @@
--destructive: #ef4444; --destructive: #ef4444;
--border: rgba(255, 255, 255, 0.12); --border: rgba(255, 255, 255, 0.12);
--input: rgba(255, 255, 255, 0.06); --input: rgba(255, 255, 255, 0.06);
--ring: rgba(99, 102, 241, 0.55);
--ring: rgba(208, 148, 96, 0.55);
--radius: 14px; --radius: 14px;
/* پالت سایدبار و پنل کناری */ /* پالت سایدبار و پنل کناری */
--sidebar: #131417; --sidebar: #131417;
--sidebar-foreground: #ebebeb; --sidebar-foreground: #ebebeb;
--sidebar-primary: #6366f1;
--sidebar-primary: #d09460;
--sidebar-primary-foreground: #ffffff; --sidebar-primary-foreground: #ffffff;
--sidebar-accent: rgba(255, 255, 255, 0.06); --sidebar-accent: rgba(255, 255, 255, 0.06);
--sidebar-accent-foreground: #ffffff; --sidebar-accent-foreground: #ffffff;
--sidebar-border: rgba(255, 255, 255, 0.07); --sidebar-border: rgba(255, 255, 255, 0.07);
--sidebar-ring: rgba(99, 102, 241, 0.55);
--sidebar-ring: rgba(208, 148, 96, 0.55);
--sidebar-width: 240px; --sidebar-width: 240px;
--panel-width: 384px; --panel-width: 384px;
@ -43,7 +43,44 @@
--grey-3: #8b8b8b; --grey-3: #8b8b8b;
--grey-4: #4d4d4d; --grey-4: #4d4d4d;
--border-soft: rgba(255, 255, 255, 0.07); --border-soft: rgba(255, 255, 255, 0.07);
--g-primary: linear-gradient(135deg, #6366f1, #4338ca);
--g-primary: linear-gradient(135deg, #d09460, #a86e3d);
}
/* ─── Light Mode Tokens ─── */
html.light {
--background: #f8fafc;
--foreground: #0f172a;
--card: #ffffff;
--card-foreground: #0f172a;
--popover: #ffffff;
--popover-foreground: #0f172a;
--primary: #d09460;
--primary-foreground: #ffffff;
--secondary: #f1f5f9;
--secondary-foreground: #1e293b;
--muted: #f1f5f9;
--muted-foreground: #64748b;
--accent: #f1f5f9;
--accent-foreground: #0f172a;
--destructive: #ef4444;
--border: rgba(15, 23, 42, 0.12);
--input: rgba(15, 23, 42, 0.06);
--ring: rgba(208, 148, 96, 0.55);
--sidebar: #ffffff;
--sidebar-foreground: #1e293b;
--sidebar-primary: #d09460;
--sidebar-primary-foreground: #ffffff;
--sidebar-accent: rgba(15, 23, 42, 0.05);
--sidebar-accent-foreground: #0f172a;
--sidebar-border: rgba(15, 23, 42, 0.08);
--sidebar-ring: rgba(208, 148, 96, 0.55);
--grey-1: #0f172a;
--grey-2: #334155;
--grey-3: #64748b;
--grey-4: #94a3b8;
--border-soft: rgba(15, 23, 42, 0.08);
} }
@theme inline { @theme inline {

3
src/main.tsx

@ -2,8 +2,11 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import Providers from '@/app/providers' import Providers from '@/app/providers'
import App from '@/app/App' import App from '@/app/App'
import { useThemeStore, applyTheme } from '@/stores/theme'
import './index.css' import './index.css'
applyTheme(useThemeStore.getState().isDark)
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<Providers> <Providers>

55
src/services/auth.ts

@ -10,8 +10,31 @@ import type { AuthUser, LoginInput } from '@/types'
* */ * */
export async function login(input: LoginInput): Promise<{ token: string; user: AuthUser }> { export async function login(input: LoginInput): Promise<{ token: string; user: AuthUser }> {
// لاگین آزمایشی پیش‌فرض برای اجرای مستقیم تمپلیت
try {
const res = await apiFetch<any>('account/login/', {
method: 'POST',
body: { email: input.email, password: input.password },
skipAuth: true,
})
const token = res.token ?? res.access_token ?? res.accessToken ?? res.data?.token
const userData = res.user ?? res.data?.user ?? res
return {
token,
user: {
id: userData.id ?? userData.user_id ?? 1,
email: userData.email ?? input.email,
name: userData.fullname ?? userData.name ?? 'کاربر',
role: userData.role ?? (userData.is_superuser ? 'superadmin' : userData.is_staff ? 'admin' : 'user'),
isStaff: userData.is_staff ?? userData.isStaff ?? true,
isSuperuser: userData.is_superuser ?? userData.isSuperuser ?? false,
},
}
} catch (err: any) {
// در صورت عدم دسترسی به سرور (status === 0)، اجازه تست دمو داده می‌شود
if ( if (
err?.status === 0 &&
(input.email === 'admin@gmail.com' || input.email === 'admin@example.com') && (input.email === 'admin@gmail.com' || input.email === 'admin@example.com') &&
input.password === 'admin' input.password === 'admin'
) { ) {
@ -20,33 +43,25 @@ export async function login(input: LoginInput): Promise<{ token: string; user: A
user: { user: {
id: 1, id: 1,
email: input.email, email: input.email,
name: 'مدیر کل سیستم',
name: 'مدیر کل سیستم (آزمایشی)',
role: 'admin', role: 'admin',
isStaff: true, isStaff: true,
isSuperuser: true, isSuperuser: true,
}, },
} }
} }
const res = await apiFetch<any>('auth/login', {
method: 'POST',
body: { email: input.email, password: input.password },
skipAuth: true,
})
return {
token: res.token ?? res.access_token ?? res.accessToken ?? res.data?.token,
user: {
id: res.user?.id ?? res.user_id ?? res.data?.user?.id ?? 1,
email: res.user?.email ?? res.email ?? res.data?.user?.email ?? input.email,
name: res.user?.name ?? res.name ?? res.data?.user?.name ?? 'کاربر',
role: res.user?.role ?? res.role ?? 'admin',
isStaff: res.user?.isStaff ?? res.is_staff ?? true,
isSuperuser: res.user?.isSuperuser ?? res.is_superuser ?? false,
},
throw err
} }
} }
export async function getProfile(): Promise<AuthUser> { export async function getProfile(): Promise<AuthUser> {
return apiFetch<AuthUser>('auth/me')
const res = await apiFetch<any>('account/profile/')
return {
id: res.id,
email: res.email,
name: res.fullname ?? res.name ?? 'کاربر',
role: res.is_superuser ? 'superadmin' : res.is_staff ? 'admin' : 'user',
isStaff: res.is_staff ?? false,
isSuperuser: res.is_superuser ?? false,
}
} }

36
src/stores/server-health.ts

@ -0,0 +1,36 @@
import { create } from 'zustand'
import { apiFetch } from '@/services/http'
export interface HealthState {
status: 'checking' | 'connected' | 'error'
message?: string
latencyMs?: number
lastChecked?: Date
checkHealth: (isSilent?: boolean) => Promise<void>
}
export const useServerHealthStore = create<HealthState>((set) => ({
status: 'checking',
latencyMs: undefined,
message: 'در حال بررسی اتصال به سرور...',
checkHealth: async (isSilent = false) => {
if (!isSilent) set({ status: 'checking' })
const start = performance.now()
try {
await apiFetch('about-us/', { skipAuth: true })
const latency = Math.round(performance.now() - start)
set({
status: 'connected',
latencyMs: latency,
message: 'ارتباط مستقیم با بک‌اند جنگو (aqila_backend) برقرار است',
lastChecked: new Date(),
})
} catch {
set({
status: 'error',
message: 'عدم دریافت پاسخ از سرور (بررسی کنید که سرور جنگو فعال باشد)',
lastChecked: new Date(),
})
}
},
}))

48
src/stores/theme.ts

@ -0,0 +1,48 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface ThemeState {
isDark: boolean
toggleTheme: () => void
setTheme: (isDark: boolean) => void
}
export const useThemeStore = create<ThemeState>()(
persist(
(set, get) => ({
isDark: true,
toggleTheme: () => {
const next = !get().isDark
set({ isDark: next })
applyTheme(next)
},
setTheme: (isDark: boolean) => {
set({ isDark })
applyTheme(isDark)
},
}),
{
name: 'dashboard-theme',
onRehydrateStorage: () => (state) => {
if (state) {
applyTheme(state.isDark)
} else {
applyTheme(true)
}
},
},
),
)
export function applyTheme(isDark: boolean) {
if (typeof document !== 'undefined') {
const root = document.documentElement
if (isDark) {
root.classList.add('dark')
root.classList.remove('light')
} else {
root.classList.add('light')
root.classList.remove('dark')
}
}
}

115
vite.config.ts

@ -1,8 +1,112 @@
/// <reference types="vitest/config" /> /// <reference types="vitest/config" />
import path from 'node:path' import path from 'node:path'
import { defineConfig, loadEnv } from 'vite'
import { exec } from 'node:child_process'
import os from 'node:os'
import { defineConfig, loadEnv, type Plugin } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import babel from '@babel/core'
import addDataLocator from './plugins/add-data-locator.cjs'
function openInIdePlugin(): Plugin {
return {
name: 'open-in-ide-plugin',
apply: 'serve',
configureServer(server) {
server.middlewares.use((req, res, next) => {
const url = req.url?.split('?')[0]
if (url === '/api/open-in-ide' && req.method === 'POST') {
let body = ''
req.on('data', (chunk) => {
body += chunk
})
req.on('end', () => {
try {
const { locator } = JSON.parse(body)
if (!locator) {
res.statusCode = 400
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ message: 'Locator is required' }))
return
}
const isWindows = os.platform() === 'win32'
const bin = isWindows ? 'antigravity-ide.cmd' : 'antigravity-ide'
const cmd = `${bin} -r -g "${locator}"`
exec(cmd, (error) => {
if (error) {
console.error(`[open-in-ide] Error: ${error.message}`)
res.statusCode = 500
res.setHeader('Content-Type', 'application/json')
res.end(
JSON.stringify({
message: 'Failed to open file in IDE',
error: error.message,
})
)
return
}
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ success: true }))
})
} catch (err) {
console.error('[open-in-ide] Error parsing body:', err, 'raw body:', body)
res.statusCode = 500
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ message: 'Internal server error', error: String(err), rawBody: body }))
}
})
return
}
if (
(url === '/api/remote-logs' ||
url === '/api/remote-network' ||
url === '/api/dev-storage') &&
req.method === 'POST'
) {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ ok: true }))
return
}
next()
})
},
}
}
function addDataLocatorPlugin(): Plugin {
return {
name: 'add-data-locator-plugin',
enforce: 'pre',
apply: 'serve',
transform(code, id) {
const cleanId = id.split('?')[0]
if (cleanId.includes('node_modules') || !/\.[jt]sx$/.test(cleanId)) {
return null
}
try {
const result = babel.transformSync(code, {
filename: cleanId,
configFile: false,
babelrc: false,
parserOpts: { plugins: ['jsx', 'typescript'] },
plugins: [addDataLocator],
})
if (result?.code) {
return { code: result.code, map: result.map }
}
} catch (err) {
console.error('[add-data-locator-plugin] Transform error:', err)
}
return null
},
}
}
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '') const env = loadEnv(mode, process.cwd(), '')
@ -10,7 +114,12 @@ export default defineConfig(({ mode }) => {
const apiTarget = env.VITE_API_TARGET ?? 'http://localhost:8000' const apiTarget = env.VITE_API_TARGET ?? 'http://localhost:8000'
return { return {
plugins: [react(), tailwindcss()],
plugins: [
openInIdePlugin(),
addDataLocatorPlugin(),
react(),
tailwindcss(),
],
resolve: { resolve: {
alias: { '@': path.resolve(__dirname, './src') }, alias: { '@': path.resolve(__dirname, './src') },
}, },
@ -19,8 +128,6 @@ export default defineConfig(({ mode }) => {
'/api': { '/api': {
target: apiTarget, target: apiTarget,
changeOrigin: true, changeOrigin: true,
/* بک‌اند پیشوند '/api' ندارد؛ آن را حذف می‌کنیم تا به ریشه برسد. */
rewrite: (p) => p.replace(/^\/api/, ''),
}, },
}, },
}, },

Loading…
Cancel
Save