gmoonc 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/index.cjs +102 -63
- package/package.json +1 -1
- package/scripts/test-browserrouter-import.mjs +259 -0
- package/src/templates/shared/src/gmoonc/app/menu/defaultMenu.ts +28 -7
- package/src/templates/shared/src/gmoonc/styles/gmoonc.css +30 -27
- package/src/templates/shared/src/gmoonc/styles/theme.css +8 -0
- package/src/templates/shared/src/gmoonc/ui/styles.css +51 -11
package/README.md
CHANGED
|
@@ -48,6 +48,11 @@ The dashboard code is in `src/gmoonc/` and is independent. You can remove `gmoon
|
|
|
48
48
|
|
|
49
49
|
## Changelog
|
|
50
50
|
|
|
51
|
+
### 0.0.10
|
|
52
|
+
- Fix: ensure BrowserRouter import when patched into App.tsx (definitive fix)
|
|
53
|
+
- Fix: prevent re-installation if src/gmoonc or marker file exists
|
|
54
|
+
- Fix: create marker file (.gmoonc-installed.json) after successful installation
|
|
55
|
+
|
|
51
56
|
### 0.0.9
|
|
52
57
|
- Fix: ensure BrowserRouter import when patched into App.tsx
|
|
53
58
|
|
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
var import_commander = require("commander");
|
|
6
6
|
var import_process = require("process");
|
|
7
7
|
var import_path6 = require("path");
|
|
8
|
+
var import_fs9 = require("fs");
|
|
8
9
|
|
|
9
10
|
// src/cli/lib/detect.ts
|
|
10
11
|
var import_fs = require("fs");
|
|
@@ -504,6 +505,78 @@ ${routeElements.join(",\n")}
|
|
|
504
505
|
logSuccess("Generated src/gmoonc/router/AppRoutes.tsx");
|
|
505
506
|
return { success: true };
|
|
506
507
|
}
|
|
508
|
+
function ensureBrowserRouterImport(content) {
|
|
509
|
+
if (!content.includes("<BrowserRouter")) {
|
|
510
|
+
return content;
|
|
511
|
+
}
|
|
512
|
+
const lines = content.split("\n");
|
|
513
|
+
let hasBrowserRouterImport = false;
|
|
514
|
+
let reactRouterImportIndex = -1;
|
|
515
|
+
let reactRouterImportLine = null;
|
|
516
|
+
let quoteStyle = '"';
|
|
517
|
+
for (let i = 0; i < lines.length; i++) {
|
|
518
|
+
const line = lines[i];
|
|
519
|
+
const trimmed = line.trim();
|
|
520
|
+
if (trimmed.startsWith("import ") && (trimmed.includes("'") || trimmed.includes('"'))) {
|
|
521
|
+
if (trimmed.includes("'")) quoteStyle = "'";
|
|
522
|
+
else if (trimmed.includes('"')) quoteStyle = '"';
|
|
523
|
+
}
|
|
524
|
+
if (trimmed.includes("from") && trimmed.includes("react-router-dom")) {
|
|
525
|
+
reactRouterImportIndex = i;
|
|
526
|
+
reactRouterImportLine = line;
|
|
527
|
+
const browserRouterPattern = /\bBrowserRouter\b/;
|
|
528
|
+
if (browserRouterPattern.test(trimmed)) {
|
|
529
|
+
hasBrowserRouterImport = true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (hasBrowserRouterImport) {
|
|
534
|
+
return content;
|
|
535
|
+
}
|
|
536
|
+
if (reactRouterImportIndex >= 0 && reactRouterImportLine) {
|
|
537
|
+
const existingLine = reactRouterImportLine;
|
|
538
|
+
const indent = existingLine.match(/^(\s*)/)?.[1] || "";
|
|
539
|
+
const namedMatch = existingLine.match(/^(\s*)import\s+\{([^}]+)\}\s+from\s+(["'])react-router-dom\2/);
|
|
540
|
+
if (namedMatch) {
|
|
541
|
+
const importListStr = namedMatch[2];
|
|
542
|
+
const quote = namedMatch[3];
|
|
543
|
+
const importItems = importListStr.split(",").map((s) => s.trim()).filter(Boolean);
|
|
544
|
+
const hasBrowserRouter = importItems.some(
|
|
545
|
+
(item) => item === "BrowserRouter" || item === "type BrowserRouter" || item.includes("BrowserRouter")
|
|
546
|
+
);
|
|
547
|
+
if (!hasBrowserRouter) {
|
|
548
|
+
const typeImports = importItems.filter((item) => item.startsWith("type "));
|
|
549
|
+
const regularImports = importItems.filter((item) => !item.startsWith("type "));
|
|
550
|
+
regularImports.push("BrowserRouter");
|
|
551
|
+
const sortedImports = [...regularImports, ...typeImports];
|
|
552
|
+
const separator = importListStr.includes(",\n") ? ",\n" : ", ";
|
|
553
|
+
const formatted = sortedImports.join(separator);
|
|
554
|
+
const updatedLine = `${indent}import { ${formatted} } from ${quote}react-router-dom${quote};`;
|
|
555
|
+
lines[reactRouterImportIndex] = updatedLine;
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
const newImportLine = `${indent}import { BrowserRouter } from ${quoteStyle}react-router-dom${quoteStyle};`;
|
|
559
|
+
lines.splice(reactRouterImportIndex + 1, 0, newImportLine);
|
|
560
|
+
}
|
|
561
|
+
} else {
|
|
562
|
+
const importLine = `import { BrowserRouter } from ${quoteStyle}react-router-dom${quoteStyle};`;
|
|
563
|
+
let lastImportIndex = -1;
|
|
564
|
+
for (let i = 0; i < lines.length; i++) {
|
|
565
|
+
const line = lines[i].trim();
|
|
566
|
+
if (line.startsWith("import ") || line.startsWith("import{") || line.startsWith("import ")) {
|
|
567
|
+
lastImportIndex = i;
|
|
568
|
+
} else if (line && lastImportIndex >= 0 && !line.startsWith("//") && !line.startsWith("/*")) {
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (lastImportIndex >= 0) {
|
|
573
|
+
lines.splice(lastImportIndex + 1, 0, importLine);
|
|
574
|
+
} else {
|
|
575
|
+
lines.unshift(importLine);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return lines.join("\n");
|
|
579
|
+
}
|
|
507
580
|
function patchBrowserRouter(consumerDir, basePath, dryRun) {
|
|
508
581
|
const appCandidates = ["src/App.tsx", "src/App.jsx", "src/App.ts", "src/App.js"];
|
|
509
582
|
let appPath = null;
|
|
@@ -562,68 +635,6 @@ function patchBrowserRouter(consumerDir, basePath, dryRun) {
|
|
|
562
635
|
generateAppRoutes(consumerDir, basePath, routes, appPath, false);
|
|
563
636
|
const backupPath = writeFileSafe(appPath, "");
|
|
564
637
|
const lines = appContent.split("\n");
|
|
565
|
-
let hasBrowserRouterImport = false;
|
|
566
|
-
let reactRouterImportIndex = -1;
|
|
567
|
-
let reactRouterImportLine = null;
|
|
568
|
-
let quoteStyle = '"';
|
|
569
|
-
for (let i = 0; i < lines.length; i++) {
|
|
570
|
-
const line = lines[i];
|
|
571
|
-
const trimmed = line.trim();
|
|
572
|
-
if (trimmed.startsWith("import ") && (trimmed.includes("'") || trimmed.includes('"'))) {
|
|
573
|
-
if (trimmed.includes("'")) quoteStyle = "'";
|
|
574
|
-
else if (trimmed.includes('"')) quoteStyle = '"';
|
|
575
|
-
}
|
|
576
|
-
if (trimmed.includes("from") && trimmed.includes("react-router-dom")) {
|
|
577
|
-
reactRouterImportIndex = i;
|
|
578
|
-
reactRouterImportLine = line;
|
|
579
|
-
const browserRouterPattern = /\bBrowserRouter\b/;
|
|
580
|
-
if (browserRouterPattern.test(trimmed)) {
|
|
581
|
-
hasBrowserRouterImport = true;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
if (!hasBrowserRouterImport) {
|
|
586
|
-
if (reactRouterImportIndex >= 0 && reactRouterImportLine) {
|
|
587
|
-
const existingLine = reactRouterImportLine;
|
|
588
|
-
const indent2 = existingLine.match(/^(\s*)/)?.[1] || "";
|
|
589
|
-
const namedMatch = existingLine.match(/^(\s*)import\s+\{([^}]+)\}\s+from\s+(["'])react-router-dom\2/);
|
|
590
|
-
if (namedMatch) {
|
|
591
|
-
const importListStr = namedMatch[2];
|
|
592
|
-
const quote = namedMatch[3];
|
|
593
|
-
const importItems = importListStr.split(",").map((s) => s.trim()).filter(Boolean);
|
|
594
|
-
const hasBrowserRouter = importItems.some(
|
|
595
|
-
(item) => item === "BrowserRouter" || item === "type BrowserRouter" || item.includes("BrowserRouter")
|
|
596
|
-
);
|
|
597
|
-
if (!hasBrowserRouter) {
|
|
598
|
-
importItems.push("BrowserRouter");
|
|
599
|
-
const hasTrailingComma = importListStr.trim().endsWith(",");
|
|
600
|
-
const separator = importListStr.includes(",\n") ? ",\n" : ", ";
|
|
601
|
-
const formatted = importItems.join(separator) + (hasTrailingComma ? "," : "");
|
|
602
|
-
const updatedLine = `${indent2}import { ${formatted} } from ${quote}react-router-dom${quote};`;
|
|
603
|
-
lines[reactRouterImportIndex] = updatedLine;
|
|
604
|
-
}
|
|
605
|
-
} else {
|
|
606
|
-
const newImportLine = `${indent2}import { BrowserRouter } from ${quoteStyle}react-router-dom${quoteStyle};`;
|
|
607
|
-
lines.splice(reactRouterImportIndex + 1, 0, newImportLine);
|
|
608
|
-
}
|
|
609
|
-
} else {
|
|
610
|
-
const importLine2 = `import { BrowserRouter } from ${quoteStyle}react-router-dom${quoteStyle};`;
|
|
611
|
-
let lastImportIndex2 = -1;
|
|
612
|
-
for (let i = 0; i < lines.length; i++) {
|
|
613
|
-
const line = lines[i].trim();
|
|
614
|
-
if (line.startsWith("import ") || line.startsWith("import{") || line.startsWith("import ")) {
|
|
615
|
-
lastImportIndex2 = i;
|
|
616
|
-
} else if (line && lastImportIndex2 >= 0 && !line.startsWith("//") && !line.startsWith("/*")) {
|
|
617
|
-
break;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
if (lastImportIndex2 >= 0) {
|
|
621
|
-
lines.splice(lastImportIndex2 + 1, 0, importLine2);
|
|
622
|
-
} else {
|
|
623
|
-
lines.unshift(importLine2);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
638
|
const importLine = `import { GMooncAppRoutes } from "./gmoonc/router/AppRoutes";`;
|
|
628
639
|
let lastImportIndex = -1;
|
|
629
640
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -682,6 +693,7 @@ function patchBrowserRouter(consumerDir, basePath, dryRun) {
|
|
|
682
693
|
newContent = newContent.replace(/import\s+{[^}]*Routes[^}]*}\s+from\s+["']react-router-dom["'];?\n?/g, "");
|
|
683
694
|
newContent = newContent.replace(/import\s+{[^}]*Route[^}]*}\s+from\s+["']react-router-dom["'];?\n?/g, "");
|
|
684
695
|
}
|
|
696
|
+
newContent = ensureBrowserRouterImport(newContent);
|
|
685
697
|
writeFileSafe(appPath, newContent);
|
|
686
698
|
logSuccess(`Patched ${appPath} (backup: ${backupPath})`);
|
|
687
699
|
return { success: true, backupPath };
|
|
@@ -689,7 +701,7 @@ function patchBrowserRouter(consumerDir, basePath, dryRun) {
|
|
|
689
701
|
|
|
690
702
|
// src/cli/index.ts
|
|
691
703
|
var program = new import_commander.Command();
|
|
692
|
-
program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete dashboard into your React project").version("0.0.
|
|
704
|
+
program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete dashboard into your React project").version("0.0.11").option("--base <path>", "Base path for dashboard routes", "/app").option("--skip-router-patch", "Skip automatic router integration (only copy files and inject CSS)").option("--dry-run", "Show what would be done without making changes").action(async (options) => {
|
|
693
705
|
try {
|
|
694
706
|
logInfo("\u{1F680} Starting gmoonc installer...");
|
|
695
707
|
logInfo("\u{1F4E6} Installing complete dashboard into your React project\n");
|
|
@@ -697,6 +709,13 @@ program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete das
|
|
|
697
709
|
const basePath = options.base || "/app";
|
|
698
710
|
const dryRun = options.dryRun || false;
|
|
699
711
|
const skipRouterPatch = options.skipRouterPatch || false;
|
|
712
|
+
const gmooncDir = (0, import_path6.join)(projectDir, "src/gmoonc");
|
|
713
|
+
const markerFile = (0, import_path6.join)(gmooncDir, ".gmoonc-installed.json");
|
|
714
|
+
if ((0, import_fs9.existsSync)(gmooncDir) || (0, import_fs9.existsSync)(markerFile)) {
|
|
715
|
+
logError("gmoonc already installed (src/gmoonc exists or marker file found).");
|
|
716
|
+
logError("Remove src/gmoonc and restore backups to reinstall.");
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
700
719
|
const safeBasePath = basePath === "/" ? "/app" : basePath;
|
|
701
720
|
logInfo("\u{1F50D} Detecting React project...");
|
|
702
721
|
const project = detectProject(projectDir);
|
|
@@ -753,6 +772,26 @@ program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete das
|
|
|
753
772
|
} else {
|
|
754
773
|
logInfo("\n\u23ED\uFE0F Skipping router patch (router not detected)");
|
|
755
774
|
}
|
|
775
|
+
if (!dryRun) {
|
|
776
|
+
ensureDirectoryExists(markerFile);
|
|
777
|
+
const packageJsonPath = (0, import_path6.join)(projectDir, "package.json");
|
|
778
|
+
let version = "0.0.10";
|
|
779
|
+
try {
|
|
780
|
+
if ((0, import_fs9.existsSync)(packageJsonPath)) {
|
|
781
|
+
const packageJson = JSON.parse((0, import_fs9.readFileSync)(packageJsonPath, "utf-8"));
|
|
782
|
+
const gmooncVersion = packageJson.dependencies?.gmoonc || packageJson.devDependencies?.gmoonc;
|
|
783
|
+
if (gmooncVersion) {
|
|
784
|
+
version = gmooncVersion.replace(/^[\^~]/, "");
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
} catch {
|
|
788
|
+
}
|
|
789
|
+
const markerContent = JSON.stringify({
|
|
790
|
+
version,
|
|
791
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
792
|
+
}, null, 2);
|
|
793
|
+
(0, import_fs9.writeFileSync)(markerFile, markerContent, "utf-8");
|
|
794
|
+
}
|
|
756
795
|
logSuccess("\n\u2705 Installation complete!");
|
|
757
796
|
logInfo("\nYour dashboard is now available at:");
|
|
758
797
|
logInfo(` - Home: ${safeBasePath}`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Regression test for BrowserRouter import patch
|
|
4
|
+
* Tests ensureBrowserRouterImport function with various fixtures
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Import the ensureBrowserRouterImport function
|
|
15
|
+
// Since it's not exported, we'll test it indirectly through patchBrowserRouter
|
|
16
|
+
// For now, we'll create a test version of the function
|
|
17
|
+
|
|
18
|
+
function ensureBrowserRouterImport(content) {
|
|
19
|
+
// Check if <BrowserRouter> is used in the content
|
|
20
|
+
if (!content.includes('<BrowserRouter')) {
|
|
21
|
+
return content; // No BrowserRouter used, no need to add import
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const lines = content.split('\n');
|
|
25
|
+
let hasBrowserRouterImport = false;
|
|
26
|
+
let reactRouterImportIndex = -1;
|
|
27
|
+
let reactRouterImportLine = null;
|
|
28
|
+
let quoteStyle = '"'; // Default to double quotes
|
|
29
|
+
|
|
30
|
+
// First pass: detect existing imports and quote style
|
|
31
|
+
for (let i = 0; i < lines.length; i++) {
|
|
32
|
+
const line = lines[i];
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
|
|
35
|
+
// Detect quote style from existing imports
|
|
36
|
+
if (trimmed.startsWith('import ') && (trimmed.includes("'") || trimmed.includes('"'))) {
|
|
37
|
+
if (trimmed.includes("'")) quoteStyle = "'";
|
|
38
|
+
else if (trimmed.includes('"')) quoteStyle = '"';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check for react-router-dom imports
|
|
42
|
+
if (trimmed.includes('from') && trimmed.includes('react-router-dom')) {
|
|
43
|
+
reactRouterImportIndex = i;
|
|
44
|
+
reactRouterImportLine = line;
|
|
45
|
+
|
|
46
|
+
// Check if BrowserRouter is already imported
|
|
47
|
+
const browserRouterPattern = /\bBrowserRouter\b/;
|
|
48
|
+
if (browserRouterPattern.test(trimmed)) {
|
|
49
|
+
hasBrowserRouterImport = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// If BrowserRouter is already imported, return content as-is
|
|
55
|
+
if (hasBrowserRouterImport) {
|
|
56
|
+
return content;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Add or update BrowserRouter import
|
|
60
|
+
if (reactRouterImportIndex >= 0 && reactRouterImportLine) {
|
|
61
|
+
// Update existing react-router-dom import to include BrowserRouter
|
|
62
|
+
const existingLine = reactRouterImportLine;
|
|
63
|
+
const indent = existingLine.match(/^(\s*)/)?.[1] || '';
|
|
64
|
+
|
|
65
|
+
// Match: import { X, Y } from "react-router-dom" or import { type X } from "react-router-dom"
|
|
66
|
+
const namedMatch = existingLine.match(/^(\s*)import\s+\{([^}]+)\}\s+from\s+(["'])react-router-dom\2/);
|
|
67
|
+
if (namedMatch) {
|
|
68
|
+
const importListStr = namedMatch[2];
|
|
69
|
+
const quote = namedMatch[3];
|
|
70
|
+
|
|
71
|
+
// Parse imports, handling "type" keywords
|
|
72
|
+
const importItems = importListStr.split(',').map(s => s.trim()).filter(Boolean);
|
|
73
|
+
const hasBrowserRouter = importItems.some(item =>
|
|
74
|
+
item === 'BrowserRouter' ||
|
|
75
|
+
item === 'type BrowserRouter' ||
|
|
76
|
+
item.includes('BrowserRouter')
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (!hasBrowserRouter) {
|
|
80
|
+
// Add BrowserRouter to the list (before type imports if any)
|
|
81
|
+
const typeImports = importItems.filter(item => item.startsWith('type '));
|
|
82
|
+
const regularImports = importItems.filter(item => !item.startsWith('type '));
|
|
83
|
+
|
|
84
|
+
regularImports.push('BrowserRouter');
|
|
85
|
+
const sortedImports = [...regularImports, ...typeImports];
|
|
86
|
+
|
|
87
|
+
// Preserve original formatting
|
|
88
|
+
const separator = importListStr.includes(',\n') ? ',\n' : ', ';
|
|
89
|
+
const formatted = sortedImports.join(separator);
|
|
90
|
+
const updatedLine = `${indent}import { ${formatted} } from ${quote}react-router-dom${quote};`;
|
|
91
|
+
lines[reactRouterImportIndex] = updatedLine;
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
// Namespace import or default import
|
|
95
|
+
// Add separate named import for BrowserRouter
|
|
96
|
+
const newImportLine = `${indent}import { BrowserRouter } from ${quoteStyle}react-router-dom${quoteStyle};`;
|
|
97
|
+
lines.splice(reactRouterImportIndex + 1, 0, newImportLine);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
// No react-router-dom import exists, add it
|
|
101
|
+
const importLine = `import { BrowserRouter } from ${quoteStyle}react-router-dom${quoteStyle};`;
|
|
102
|
+
let lastImportIndex = -1;
|
|
103
|
+
for (let i = 0; i < lines.length; i++) {
|
|
104
|
+
const line = lines[i].trim();
|
|
105
|
+
if (line.startsWith('import ') || line.startsWith('import{') || line.startsWith('import\t')) {
|
|
106
|
+
lastImportIndex = i;
|
|
107
|
+
} else if (line && lastImportIndex >= 0 && !line.startsWith('//') && !line.startsWith('/*')) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (lastImportIndex >= 0) {
|
|
112
|
+
lines.splice(lastImportIndex + 1, 0, importLine);
|
|
113
|
+
} else {
|
|
114
|
+
lines.unshift(importLine);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return lines.join('\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Test fixtures
|
|
122
|
+
const fixtures = [
|
|
123
|
+
{
|
|
124
|
+
name: 'Fixture 1: No react-router-dom import',
|
|
125
|
+
input: `import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
126
|
+
import { Toaster } from 'sonner';
|
|
127
|
+
import Index from './pages/Index';
|
|
128
|
+
|
|
129
|
+
function App() {
|
|
130
|
+
return (
|
|
131
|
+
<QueryClientProvider client={new QueryClient()}>
|
|
132
|
+
<Toaster />
|
|
133
|
+
<BrowserRouter>
|
|
134
|
+
<Routes>
|
|
135
|
+
<Route path="/" element={<Index />} />
|
|
136
|
+
</Routes>
|
|
137
|
+
</BrowserRouter>
|
|
138
|
+
</QueryClientProvider>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default App;`,
|
|
143
|
+
expected: (output) => {
|
|
144
|
+
return output.includes('import { BrowserRouter }') &&
|
|
145
|
+
output.includes('react-router-dom') &&
|
|
146
|
+
output.includes('<BrowserRouter>');
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'Fixture 2: Existing Routes, Route import',
|
|
151
|
+
input: `import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
152
|
+
import { Routes, Route } from "react-router-dom";
|
|
153
|
+
import Index from './pages/Index';
|
|
154
|
+
|
|
155
|
+
function App() {
|
|
156
|
+
return (
|
|
157
|
+
<QueryClientProvider client={new QueryClient()}>
|
|
158
|
+
<BrowserRouter>
|
|
159
|
+
<Routes>
|
|
160
|
+
<Route path="/" element={<Index />} />
|
|
161
|
+
</Routes>
|
|
162
|
+
</BrowserRouter>
|
|
163
|
+
</QueryClientProvider>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export default App;`,
|
|
168
|
+
expected: (output) => {
|
|
169
|
+
return output.includes('import { BrowserRouter, Routes, Route }') ||
|
|
170
|
+
(output.includes('import { BrowserRouter }') && output.includes('import { Routes, Route }'));
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'Fixture 3: Type import only',
|
|
175
|
+
input: `import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
176
|
+
import { type RouteObject } from "react-router-dom";
|
|
177
|
+
import Index from './pages/Index';
|
|
178
|
+
|
|
179
|
+
function App() {
|
|
180
|
+
return (
|
|
181
|
+
<QueryClientProvider client={new QueryClient()}>
|
|
182
|
+
<BrowserRouter>
|
|
183
|
+
<Routes>
|
|
184
|
+
<Route path="/" element={<Index />} />
|
|
185
|
+
</Routes>
|
|
186
|
+
</BrowserRouter>
|
|
187
|
+
</QueryClientProvider>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default App;`,
|
|
192
|
+
expected: (output) => {
|
|
193
|
+
return output.includes('BrowserRouter') &&
|
|
194
|
+
output.includes('type RouteObject') &&
|
|
195
|
+
output.includes('react-router-dom');
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'Fixture 4: Namespace import',
|
|
200
|
+
input: `import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
201
|
+
import * as ReactRouterDOM from "react-router-dom";
|
|
202
|
+
import Index from './pages/Index';
|
|
203
|
+
|
|
204
|
+
function App() {
|
|
205
|
+
return (
|
|
206
|
+
<QueryClientProvider client={new QueryClient()}>
|
|
207
|
+
<BrowserRouter>
|
|
208
|
+
<Routes>
|
|
209
|
+
<Route path="/" element={<Index />} />
|
|
210
|
+
</Routes>
|
|
211
|
+
</BrowserRouter>
|
|
212
|
+
</QueryClientProvider>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export default App;`,
|
|
217
|
+
expected: (output) => {
|
|
218
|
+
// Should have both namespace import and named BrowserRouter import
|
|
219
|
+
const hasNamespace = output.includes('import * as ReactRouterDOM');
|
|
220
|
+
const hasNamed = output.includes('import { BrowserRouter }');
|
|
221
|
+
return hasNamespace && hasNamed;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
let passed = 0;
|
|
227
|
+
let failed = 0;
|
|
228
|
+
|
|
229
|
+
console.log('🧪 Running BrowserRouter import regression tests...\n');
|
|
230
|
+
|
|
231
|
+
for (const fixture of fixtures) {
|
|
232
|
+
try {
|
|
233
|
+
const output = ensureBrowserRouterImport(fixture.input);
|
|
234
|
+
const result = fixture.expected(output);
|
|
235
|
+
|
|
236
|
+
if (result) {
|
|
237
|
+
console.log(`✅ ${fixture.name}`);
|
|
238
|
+
passed++;
|
|
239
|
+
} else {
|
|
240
|
+
console.error(`❌ ${fixture.name}`);
|
|
241
|
+
console.error(' Output:', output.split('\n').slice(0, 5).join('\n'));
|
|
242
|
+
failed++;
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error(`❌ ${fixture.name}`);
|
|
246
|
+
console.error(` Error: ${error.message}`);
|
|
247
|
+
failed++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log(`\n📊 Results: ${passed} passed, ${failed} failed`);
|
|
252
|
+
|
|
253
|
+
if (failed > 0) {
|
|
254
|
+
console.error('\n❌ Some tests failed!');
|
|
255
|
+
process.exit(1);
|
|
256
|
+
} else {
|
|
257
|
+
console.log('\n✅ All tests passed!');
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { type MenuItem as CoreMenuItem } from '../../core/types';
|
|
2
|
+
import { Home, Users, Shield, MessageSquare, Settings, ChevronRight, ChevronDown } from 'lucide-react';
|
|
3
|
+
import type React from 'react';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Extended MenuItem interface with submenu support
|
|
5
7
|
*/
|
|
6
8
|
export interface MenuItemWithSubmenu extends CoreMenuItem {
|
|
7
9
|
submenu?: MenuItemWithSubmenu[];
|
|
10
|
+
icon?: React.ReactNode;
|
|
11
|
+
expandIcon?: React.ReactNode;
|
|
12
|
+
collapseIcon?: React.ReactNode;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
/**
|
|
@@ -25,37 +30,45 @@ export function createDefaultMenu(basePath: string = '/app'): MenuItemWithSubmen
|
|
|
25
30
|
id: 'home',
|
|
26
31
|
label: 'Home',
|
|
27
32
|
path: basePath,
|
|
28
|
-
roles: []
|
|
33
|
+
roles: [],
|
|
34
|
+
icon: <Home size={18} strokeWidth={2.5} />
|
|
29
35
|
},
|
|
30
36
|
{
|
|
31
37
|
id: 'admin',
|
|
32
38
|
label: 'Admin',
|
|
33
39
|
path: `${basePath}/admin`,
|
|
34
40
|
roles: ['admin'],
|
|
41
|
+
icon: <Shield size={18} strokeWidth={2.5} />,
|
|
42
|
+
expandIcon: <ChevronRight size={16} strokeWidth={2.5} />,
|
|
43
|
+
collapseIcon: <ChevronDown size={16} strokeWidth={2.5} />,
|
|
35
44
|
submenu: [
|
|
36
45
|
{
|
|
37
46
|
id: 'admin-users',
|
|
38
47
|
label: 'Users',
|
|
39
48
|
path: `${basePath}/admin/users`,
|
|
40
|
-
roles: ['admin']
|
|
49
|
+
roles: ['admin'],
|
|
50
|
+
icon: <Users size={16} strokeWidth={2.5} />
|
|
41
51
|
},
|
|
42
52
|
{
|
|
43
53
|
id: 'admin-permissions',
|
|
44
54
|
label: 'Permissions',
|
|
45
55
|
path: `${basePath}/admin/permissions`,
|
|
46
|
-
roles: ['admin']
|
|
56
|
+
roles: ['admin'],
|
|
57
|
+
icon: <Shield size={16} strokeWidth={2.5} />
|
|
47
58
|
},
|
|
48
59
|
{
|
|
49
60
|
id: 'admin-authorizations',
|
|
50
61
|
label: 'Authorization Management',
|
|
51
62
|
path: `${basePath}/admin/authorizations`,
|
|
52
|
-
roles: ['admin']
|
|
63
|
+
roles: ['admin'],
|
|
64
|
+
icon: <Settings size={16} strokeWidth={2.5} />
|
|
53
65
|
},
|
|
54
66
|
{
|
|
55
67
|
id: 'admin-notifications',
|
|
56
68
|
label: 'Notification Management',
|
|
57
69
|
path: `${basePath}/admin/notifications`,
|
|
58
|
-
roles: ['admin']
|
|
70
|
+
roles: ['admin'],
|
|
71
|
+
icon: <MessageSquare size={16} strokeWidth={2.5} />
|
|
59
72
|
}
|
|
60
73
|
]
|
|
61
74
|
},
|
|
@@ -64,12 +77,16 @@ export function createDefaultMenu(basePath: string = '/app'): MenuItemWithSubmen
|
|
|
64
77
|
label: 'Technical',
|
|
65
78
|
path: `${basePath}/technical`,
|
|
66
79
|
roles: [],
|
|
80
|
+
icon: <Settings size={18} strokeWidth={2.5} />,
|
|
81
|
+
expandIcon: <ChevronRight size={16} strokeWidth={2.5} />,
|
|
82
|
+
collapseIcon: <ChevronDown size={16} strokeWidth={2.5} />,
|
|
67
83
|
submenu: [
|
|
68
84
|
{
|
|
69
85
|
id: 'technical-messages',
|
|
70
86
|
label: 'Messages',
|
|
71
87
|
path: `${basePath}/technical/messages`,
|
|
72
|
-
roles: []
|
|
88
|
+
roles: [],
|
|
89
|
+
icon: <MessageSquare size={16} strokeWidth={2.5} />
|
|
73
90
|
}
|
|
74
91
|
]
|
|
75
92
|
},
|
|
@@ -78,12 +95,16 @@ export function createDefaultMenu(basePath: string = '/app'): MenuItemWithSubmen
|
|
|
78
95
|
label: 'Customer',
|
|
79
96
|
path: `${basePath}/customer`,
|
|
80
97
|
roles: [],
|
|
98
|
+
icon: <Users size={18} strokeWidth={2.5} />,
|
|
99
|
+
expandIcon: <ChevronRight size={16} strokeWidth={2.5} />,
|
|
100
|
+
collapseIcon: <ChevronDown size={16} strokeWidth={2.5} />,
|
|
81
101
|
submenu: [
|
|
82
102
|
{
|
|
83
103
|
id: 'customer-messages',
|
|
84
104
|
label: 'Messages',
|
|
85
105
|
path: `${basePath}/customer/messages`,
|
|
86
|
-
roles: []
|
|
106
|
+
roles: [],
|
|
107
|
+
icon: <MessageSquare size={16} strokeWidth={2.5} />
|
|
87
108
|
}
|
|
88
109
|
]
|
|
89
110
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/* Import Montserrat font from Google Fonts (ported from sicoop-app) */
|
|
2
|
+
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap');
|
|
3
|
+
|
|
1
4
|
/* App-specific styles (ported from legacy sicoop-app) */
|
|
2
5
|
|
|
3
6
|
/* Auth pages */
|
|
@@ -25,15 +28,15 @@
|
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
.gmoonc-auth-header h1 {
|
|
28
|
-
color:
|
|
31
|
+
color: var(--gmoonc-color-text-primary);
|
|
29
32
|
font-size: 28px;
|
|
30
33
|
font-weight: bold;
|
|
31
34
|
margin: 0 0 10px 0;
|
|
32
|
-
font-family:
|
|
35
|
+
font-family: var(--gmoonc-font-family);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
.gmoonc-auth-header p {
|
|
36
|
-
color:
|
|
39
|
+
color: var(--gmoonc-color-text-secondary);
|
|
37
40
|
font-size: 16px;
|
|
38
41
|
margin: 0;
|
|
39
42
|
}
|
|
@@ -70,10 +73,10 @@
|
|
|
70
73
|
|
|
71
74
|
.gmoonc-form-group label {
|
|
72
75
|
display: block;
|
|
73
|
-
color:
|
|
76
|
+
color: var(--gmoonc-color-text-primary);
|
|
74
77
|
font-weight: 600;
|
|
75
78
|
margin-bottom: 8px;
|
|
76
|
-
font-family:
|
|
79
|
+
font-family: var(--gmoonc-font-family);
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
.gmoonc-form-group input,
|
|
@@ -84,28 +87,28 @@
|
|
|
84
87
|
border-radius: 8px;
|
|
85
88
|
font-size: 16px;
|
|
86
89
|
transition: border-color 0.3s ease;
|
|
87
|
-
font-family:
|
|
90
|
+
font-family: var(--gmoonc-font-family);
|
|
88
91
|
background-color: white;
|
|
89
|
-
color:
|
|
92
|
+
color: var(--gmoonc-color-text-primary);
|
|
90
93
|
box-sizing: border-box;
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
.gmoonc-form-group input::placeholder {
|
|
94
|
-
color:
|
|
97
|
+
color: var(--gmoonc-color-primary);
|
|
95
98
|
opacity: 0.7;
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
.gmoonc-form-group input:focus,
|
|
99
102
|
.gmoonc-form-group select:focus {
|
|
100
103
|
outline: none;
|
|
101
|
-
border-color:
|
|
104
|
+
border-color: var(--gmoonc-color-primary-2);
|
|
102
105
|
background-color: white;
|
|
103
|
-
color:
|
|
106
|
+
color: var(--gmoonc-color-text-primary);
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
.gmoonc-form-group input:hover,
|
|
107
110
|
.gmoonc-form-group select:hover {
|
|
108
|
-
border-color:
|
|
111
|
+
border-color: var(--gmoonc-color-primary);
|
|
109
112
|
background-color: #f8f9fa;
|
|
110
113
|
}
|
|
111
114
|
|
|
@@ -113,7 +116,7 @@
|
|
|
113
116
|
.gmoonc-form-group select:disabled {
|
|
114
117
|
background-color: #f8f9fa;
|
|
115
118
|
cursor: not-allowed;
|
|
116
|
-
color:
|
|
119
|
+
color: var(--gmoonc-color-text-secondary);
|
|
117
120
|
border-color: #dee2e6;
|
|
118
121
|
}
|
|
119
122
|
|
|
@@ -128,7 +131,7 @@
|
|
|
128
131
|
font-weight: 600;
|
|
129
132
|
cursor: pointer;
|
|
130
133
|
transition: all 0.3s ease;
|
|
131
|
-
font-family:
|
|
134
|
+
font-family: var(--gmoonc-font-family);
|
|
132
135
|
margin-bottom: 20px;
|
|
133
136
|
}
|
|
134
137
|
|
|
@@ -149,7 +152,7 @@
|
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
.gmoonc-auth-link {
|
|
152
|
-
color:
|
|
155
|
+
color: var(--gmoonc-link);
|
|
153
156
|
text-decoration: none;
|
|
154
157
|
font-size: 14px;
|
|
155
158
|
transition: color 0.3s ease;
|
|
@@ -158,7 +161,7 @@
|
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
.gmoonc-auth-link:hover {
|
|
161
|
-
color:
|
|
164
|
+
color: var(--gmoonc-color-text-primary);
|
|
162
165
|
text-decoration: underline;
|
|
163
166
|
}
|
|
164
167
|
|
|
@@ -179,10 +182,10 @@
|
|
|
179
182
|
|
|
180
183
|
.gmoonc-content-header h2 {
|
|
181
184
|
margin: 0;
|
|
182
|
-
color:
|
|
185
|
+
color: var(--gmoonc-color-text-primary);
|
|
183
186
|
font-size: 24px;
|
|
184
187
|
font-weight: 600;
|
|
185
|
-
font-family:
|
|
188
|
+
font-family: var(--gmoonc-font-family);
|
|
186
189
|
}
|
|
187
190
|
|
|
188
191
|
.gmoonc-content-body {
|
|
@@ -198,15 +201,15 @@
|
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
.gmoonc-welcome-content h2 {
|
|
201
|
-
color:
|
|
204
|
+
color: var(--gmoonc-color-text-primary);
|
|
202
205
|
font-size: 32px;
|
|
203
206
|
margin-bottom: 20px;
|
|
204
207
|
font-weight: bold;
|
|
205
|
-
font-family:
|
|
208
|
+
font-family: var(--gmoonc-font-family);
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
.gmoonc-welcome-content p {
|
|
209
|
-
color:
|
|
212
|
+
color: var(--gmoonc-color-text-secondary);
|
|
210
213
|
font-size: 18px;
|
|
211
214
|
margin-bottom: 30px;
|
|
212
215
|
line-height: 1.6;
|
|
@@ -332,24 +335,24 @@
|
|
|
332
335
|
.user-name {
|
|
333
336
|
font-size: 16px;
|
|
334
337
|
font-weight: 600;
|
|
335
|
-
color:
|
|
338
|
+
color: var(--gmoonc-color-text-primary);
|
|
336
339
|
margin-bottom: 4px;
|
|
337
|
-
font-family:
|
|
340
|
+
font-family: var(--gmoonc-font-family);
|
|
338
341
|
}
|
|
339
342
|
|
|
340
343
|
.user-email {
|
|
341
344
|
font-size: 14px;
|
|
342
345
|
color: #6c757d;
|
|
343
346
|
margin-bottom: 4px;
|
|
344
|
-
font-family:
|
|
347
|
+
font-family: var(--gmoonc-font-family);
|
|
345
348
|
}
|
|
346
349
|
|
|
347
350
|
.user-role {
|
|
348
351
|
font-size: 12px;
|
|
349
|
-
color:
|
|
352
|
+
color: var(--gmoonc-color-primary);
|
|
350
353
|
text-transform: capitalize;
|
|
351
354
|
font-weight: 500;
|
|
352
|
-
font-family:
|
|
355
|
+
font-family: var(--gmoonc-font-family);
|
|
353
356
|
}
|
|
354
357
|
|
|
355
358
|
.dropdown-divider {
|
|
@@ -373,9 +376,9 @@
|
|
|
373
376
|
align-items: center;
|
|
374
377
|
gap: 12px;
|
|
375
378
|
font-size: 14px;
|
|
376
|
-
color: #374161;
|
|
379
|
+
color: var(--gmoonc-color-text-primary, #374161);
|
|
377
380
|
transition: background-color 0.2s ease;
|
|
378
|
-
font-family:
|
|
381
|
+
font-family: var(--gmoonc-font-family);
|
|
379
382
|
}
|
|
380
383
|
|
|
381
384
|
.dropdown-option:hover {
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
--gmoonc-color-text-muted: #dbe2ea;
|
|
29
29
|
--gmoonc-color-text-white: #ffffff;
|
|
30
30
|
|
|
31
|
+
/* Colors - Text (Menu & Header specific) */
|
|
32
|
+
--gmoonc-sidebar-text: #ffffff;
|
|
33
|
+
--gmoonc-sidebar-text-active: #293047;
|
|
34
|
+
--gmoonc-sidebar-muted: #ffffff;
|
|
35
|
+
--gmoonc-header-text: #ffffff;
|
|
36
|
+
--gmoonc-surface-text: #374161;
|
|
37
|
+
--gmoonc-link: #6374AD;
|
|
38
|
+
|
|
31
39
|
/* Colors - Primary */
|
|
32
40
|
--gmoonc-color-primary: #879FED;
|
|
33
41
|
--gmoonc-color-primary-2: #6374AD;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
/* Import Montserrat font from Google Fonts (ported from sicoop-app) */
|
|
2
|
+
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap');
|
|
3
|
+
|
|
1
4
|
/* Gmoonc Menu Styles - Base for all devices */
|
|
2
5
|
.gmoonc-menu {
|
|
3
6
|
width: 250px;
|
|
4
7
|
background-color: #eaf0f5;
|
|
5
|
-
font-family: 'Montserrat', Arial, Helvetica, sans-serif;
|
|
8
|
+
font-family: var(--gmoonc-font-family, 'Montserrat', Arial, Helvetica, sans-serif);
|
|
6
9
|
border-radius: 12px;
|
|
7
10
|
box-shadow: 0 4px 6px rgba(55, 65, 97, 0.1);
|
|
8
11
|
overflow: hidden;
|
|
@@ -73,7 +76,7 @@
|
|
|
73
76
|
height: 3.375rem;
|
|
74
77
|
padding: 0 1.25rem;
|
|
75
78
|
background-color: #3F4A6E;
|
|
76
|
-
color: #ffffff;
|
|
79
|
+
color: var(--gmoonc-sidebar-text, #ffffff);
|
|
77
80
|
cursor: pointer;
|
|
78
81
|
transition: all 0.3s ease;
|
|
79
82
|
font-weight: 500;
|
|
@@ -82,20 +85,37 @@
|
|
|
82
85
|
border: none;
|
|
83
86
|
width: 100%;
|
|
84
87
|
text-align: left;
|
|
85
|
-
font-family:
|
|
88
|
+
font-family: var(--gmoonc-font-family, 'Montserrat', Arial, Helvetica, sans-serif);
|
|
89
|
+
gap: 0.75rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.gmoonc-menu-icon {
|
|
93
|
+
display: inline-flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
flex-shrink: 0;
|
|
97
|
+
width: 18px;
|
|
98
|
+
height: 18px;
|
|
99
|
+
color: inherit;
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
.gmoonc-menu-link:hover {
|
|
89
103
|
background-color: #6374AD;
|
|
90
104
|
transform: translateX(5px);
|
|
105
|
+
color: var(--gmoonc-sidebar-text, #ffffff);
|
|
91
106
|
}
|
|
92
107
|
|
|
93
108
|
.gmoonc-menu-link.active {
|
|
94
109
|
background-color: #879FED;
|
|
95
|
-
color: #293047;
|
|
110
|
+
color: var(--gmoonc-sidebar-text-active, #293047);
|
|
96
111
|
font-weight: 600;
|
|
97
112
|
}
|
|
98
113
|
|
|
114
|
+
.gmoonc-menu-label {
|
|
115
|
+
flex: 1;
|
|
116
|
+
color: inherit;
|
|
117
|
+
}
|
|
118
|
+
|
|
99
119
|
.gmoonc-menu-link.has-submenu {
|
|
100
120
|
position: relative;
|
|
101
121
|
}
|
|
@@ -125,7 +145,7 @@
|
|
|
125
145
|
width: 100%;
|
|
126
146
|
min-height: 44px;
|
|
127
147
|
padding: 0.8rem 1.25rem 0.8rem 2.5rem;
|
|
128
|
-
color: #374161;
|
|
148
|
+
color: var(--gmoonc-surface-text, #374161);
|
|
129
149
|
text-decoration: none;
|
|
130
150
|
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
|
131
151
|
font-size: 0.9rem;
|
|
@@ -134,13 +154,24 @@
|
|
|
134
154
|
border-radius: 0 10px 10px 0;
|
|
135
155
|
border: none;
|
|
136
156
|
cursor: pointer;
|
|
137
|
-
font-family:
|
|
157
|
+
font-family: var(--gmoonc-font-family, 'Montserrat', Arial, Helvetica, sans-serif);
|
|
138
158
|
text-align: left;
|
|
159
|
+
gap: 0.75rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.gmoonc-submenu-link .gmoonc-menu-icon {
|
|
163
|
+
width: 16px;
|
|
164
|
+
height: 16px;
|
|
139
165
|
}
|
|
140
166
|
|
|
141
167
|
.gmoonc-submenu-link:hover {
|
|
142
168
|
background-color: rgba(219, 226, 234, 0.5);
|
|
143
|
-
color: #374161;
|
|
169
|
+
color: var(--gmoonc-surface-text, #374161);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.gmoonc-submenu-link span:not(.gmoonc-menu-icon) {
|
|
173
|
+
flex: 1;
|
|
174
|
+
color: inherit;
|
|
144
175
|
}
|
|
145
176
|
|
|
146
177
|
.gmoonc-submenu-link:focus-visible {
|
|
@@ -151,9 +182,9 @@
|
|
|
151
182
|
|
|
152
183
|
.gmoonc-submenu-link.active {
|
|
153
184
|
background-color: #dbe2ea;
|
|
154
|
-
color: #374161;
|
|
185
|
+
color: var(--gmoonc-surface-text, #374161);
|
|
155
186
|
font-weight: 600;
|
|
156
|
-
border-left-color: #374161;
|
|
187
|
+
border-left-color: var(--gmoonc-color-text-primary, #374161);
|
|
157
188
|
box-shadow: inset 0 0 0 1px rgba(55, 65, 97, 0.12);
|
|
158
189
|
}
|
|
159
190
|
|
|
@@ -173,7 +204,7 @@
|
|
|
173
204
|
align-items: center;
|
|
174
205
|
padding: 20px 30px;
|
|
175
206
|
background: linear-gradient(135deg, #374161, #3F4A6E);
|
|
176
|
-
color:
|
|
207
|
+
color: var(--gmoonc-header-text, #ffffff);
|
|
177
208
|
box-shadow: 0 4px 20px rgba(55, 65, 97, 0.3);
|
|
178
209
|
position: fixed;
|
|
179
210
|
top: 0;
|
|
@@ -187,7 +218,8 @@
|
|
|
187
218
|
margin: 0;
|
|
188
219
|
font-size: 28px;
|
|
189
220
|
font-weight: bold;
|
|
190
|
-
font-family: 'Montserrat', Arial, Helvetica, sans-serif;
|
|
221
|
+
font-family: var(--gmoonc-font-family, 'Montserrat', Arial, Helvetica, sans-serif);
|
|
222
|
+
color: var(--gmoonc-header-text, #ffffff);
|
|
191
223
|
position: absolute;
|
|
192
224
|
left: 50%;
|
|
193
225
|
transform: translateX(-50%);
|
|
@@ -203,6 +235,13 @@
|
|
|
203
235
|
margin-left: auto;
|
|
204
236
|
}
|
|
205
237
|
|
|
238
|
+
/* Ensure profile is on the right side on all breakpoints */
|
|
239
|
+
@media (max-width: 1366px) {
|
|
240
|
+
.gmoonc-header-right {
|
|
241
|
+
margin-left: auto;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
206
245
|
.gmoonc-content {
|
|
207
246
|
display: flex;
|
|
208
247
|
min-height: calc(100vh - 80px);
|
|
@@ -340,6 +379,7 @@
|
|
|
340
379
|
text-align: center;
|
|
341
380
|
margin: 0;
|
|
342
381
|
white-space: nowrap;
|
|
382
|
+
color: var(--gmoonc-header-text, #ffffff);
|
|
343
383
|
}
|
|
344
384
|
|
|
345
385
|
.gmoonc-sidebar {
|