gmoonc 0.0.3 → 0.0.5
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 +21 -3
- package/dist/index.cjs +112 -19
- package/package.json +3 -3
- package/src/templates/shared/src/gmoonc/app/menu/defaultMenu.ts +1 -1
- package/src/templates/shared/src/gmoonc/config/defaultConfig.ts +1 -1
- package/src/templates/shared/src/gmoonc/layout/GMooncAppLayout.tsx +31 -28
- package/src/templates/shared/src/gmoonc/styles/theme.css +101 -0
- package/src/templates/shared/src/gmoonc/ui/menu.tsx +29 -14
- package/src/templates/shared/src/gmoonc/ui/shell.tsx +1 -1
- package/src/templates/vite/src/gmoonc/router/createGmooncRoutes.tsx +26 -17
package/README.md
CHANGED
|
@@ -16,10 +16,28 @@ Run the installer in your React project root:
|
|
|
16
16
|
npx gmoonc
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Or
|
|
19
|
+
Or skip confirmations (recommended):
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npx gmoonc
|
|
22
|
+
npx gmoonc -y
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Note for Windows/npm 11+:** If you encounter issues with `--yes`, use the short flag `-y` instead:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx gmoonc -y
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or use the pass-through syntax:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx gmoonc -- --yes
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
With custom base path:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx gmoonc -y --base /dashboard
|
|
23
41
|
```
|
|
24
42
|
|
|
25
43
|
## What it does
|
|
@@ -31,7 +49,7 @@ npx gmoonc --yes --base /app
|
|
|
31
49
|
|
|
32
50
|
## Options
|
|
33
51
|
|
|
34
|
-
- `--yes
|
|
52
|
+
- `-y`, `--yes`, `--auto`: Skip confirmations and install automatically (recommended: use `-y`)
|
|
35
53
|
- `--base <path>`: Base path for dashboard routes (default: `/app`)
|
|
36
54
|
- `--skip-router-patch`: Skip automatic router integration
|
|
37
55
|
- `--dry-run`: Show what would be done without making changes
|
package/dist/index.cjs
CHANGED
|
@@ -244,20 +244,29 @@ function patchEntryCss(entrypointPath, dryRun) {
|
|
|
244
244
|
return { success: true, backupPath: null };
|
|
245
245
|
}
|
|
246
246
|
const content = readFile(entrypointPath);
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
const hasThemeCss = content.includes("./gmoonc/styles/theme.css") || content.includes("gmoonc/styles/theme.css");
|
|
248
|
+
const hasGmooncCss = content.includes("./gmoonc/styles/gmoonc.css") || content.includes("gmoonc/styles/gmoonc.css");
|
|
249
|
+
if (hasThemeCss && hasGmooncCss) {
|
|
250
|
+
logSuccess("CSS imports already exist");
|
|
249
251
|
return { success: true, backupPath: null };
|
|
250
252
|
}
|
|
251
253
|
const importRegex = /^import\s+.*$/gm;
|
|
252
254
|
const imports = content.match(importRegex) || [];
|
|
253
255
|
let newContent = content;
|
|
256
|
+
const cssImports = [];
|
|
257
|
+
if (!hasThemeCss) {
|
|
258
|
+
cssImports.push('import "./gmoonc/styles/theme.css";');
|
|
259
|
+
}
|
|
260
|
+
if (!hasGmooncCss) {
|
|
261
|
+
cssImports.push('import "./gmoonc/styles/gmoonc.css";');
|
|
262
|
+
}
|
|
254
263
|
if (imports.length > 0) {
|
|
255
264
|
const lastImport = imports[imports.length - 1];
|
|
256
265
|
const lastImportIndex = content.lastIndexOf(lastImport);
|
|
257
266
|
const insertIndex = lastImportIndex + lastImport.length;
|
|
258
|
-
newContent = content.slice(0, insertIndex) +
|
|
267
|
+
newContent = content.slice(0, insertIndex) + "\n" + cssImports.join("\n") + content.slice(insertIndex);
|
|
259
268
|
} else {
|
|
260
|
-
newContent =
|
|
269
|
+
newContent = cssImports.join("\n") + "\n" + content;
|
|
261
270
|
}
|
|
262
271
|
const backupPath = writeFileSafe(entrypointPath, newContent);
|
|
263
272
|
if (backupPath) {
|
|
@@ -281,23 +290,58 @@ function extractRouteComponents(appContent) {
|
|
|
281
290
|
const lines = appContent.split("\n");
|
|
282
291
|
if (indexComponent) {
|
|
283
292
|
for (const line of lines) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
293
|
+
const trimmed = line.trim();
|
|
294
|
+
if (trimmed.startsWith("import")) {
|
|
295
|
+
const defaultImportMatch = trimmed.match(/import\s+(\w+)\s+from\s+["']([^"']+)["']/);
|
|
296
|
+
if (defaultImportMatch && defaultImportMatch[1] === indexComponent) {
|
|
297
|
+
indexImport = trimmed;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
const namedImportMatch = trimmed.match(/import\s+\{[^}]*\b(\w+)\b[^}]*\}\s+from\s+["']([^"']+)["']/);
|
|
301
|
+
if (namedImportMatch && namedImportMatch[1] === indexComponent) {
|
|
302
|
+
indexImport = trimmed;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
287
305
|
}
|
|
288
306
|
}
|
|
289
307
|
}
|
|
290
308
|
if (notFoundComponent) {
|
|
291
309
|
for (const line of lines) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
310
|
+
const trimmed = line.trim();
|
|
311
|
+
if (trimmed.startsWith("import")) {
|
|
312
|
+
const defaultImportMatch = trimmed.match(/import\s+(\w+)\s+from\s+["']([^"']+)["']/);
|
|
313
|
+
if (defaultImportMatch && defaultImportMatch[1] === notFoundComponent) {
|
|
314
|
+
notFoundImport = trimmed;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
const namedImportMatch = trimmed.match(/import\s+\{[^}]*\b(\w+)\b[^}]*\}\s+from\s+["']([^"']+)["']/);
|
|
318
|
+
if (namedImportMatch && namedImportMatch[1] === notFoundComponent) {
|
|
319
|
+
notFoundImport = trimmed;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
295
322
|
}
|
|
296
323
|
}
|
|
297
324
|
}
|
|
298
325
|
return { indexImport, notFoundImport, indexComponent, notFoundComponent };
|
|
299
326
|
}
|
|
300
|
-
function
|
|
327
|
+
function convertImportToRelative(importLine, appRoutesPath, appPath) {
|
|
328
|
+
const pathMatch = importLine.match(/from\s+["']([^"']+)["']/);
|
|
329
|
+
if (!pathMatch) {
|
|
330
|
+
return importLine;
|
|
331
|
+
}
|
|
332
|
+
const originalPath = pathMatch[1];
|
|
333
|
+
if (!originalPath.startsWith("./") && !originalPath.startsWith("../")) {
|
|
334
|
+
return importLine;
|
|
335
|
+
}
|
|
336
|
+
const appDir = appPath.substring(0, appPath.lastIndexOf("/"));
|
|
337
|
+
const resolvedPath = (0, import_path5.join)(appDir, originalPath);
|
|
338
|
+
const appRoutesDir = appRoutesPath.substring(0, appRoutesPath.lastIndexOf("/"));
|
|
339
|
+
const relativePath = (0, import_path5.relative)(appRoutesDir, resolvedPath);
|
|
340
|
+
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
341
|
+
const finalPath = normalizedPath.startsWith(".") ? normalizedPath : "./" + normalizedPath;
|
|
342
|
+
return importLine.replace(pathMatch[1], finalPath);
|
|
343
|
+
}
|
|
344
|
+
function generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, appPath, dryRun) {
|
|
301
345
|
const appRoutesPath = (0, import_path5.join)(consumerDir, "src/gmoonc/router/AppRoutes.tsx");
|
|
302
346
|
if (dryRun) {
|
|
303
347
|
logInfo(`[DRY RUN] Would generate ${appRoutesPath}`);
|
|
@@ -308,24 +352,26 @@ function generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, i
|
|
|
308
352
|
if (indexComponent) {
|
|
309
353
|
routes.push(` { path: "/", element: <${indexComponent} /> }`);
|
|
310
354
|
}
|
|
311
|
-
routes.push(` ...createGmooncRoutes({ basePath
|
|
355
|
+
routes.push(` ...createGmooncRoutes({ basePath })`);
|
|
312
356
|
if (notFoundComponent) {
|
|
313
357
|
routes.push(` { path: "*", element: <${notFoundComponent} /> }`);
|
|
314
358
|
}
|
|
315
359
|
const imports = [
|
|
316
|
-
"import { useRoutes } from 'react-router-dom';",
|
|
360
|
+
"import { useRoutes, type RouteObject } from 'react-router-dom';",
|
|
317
361
|
"import { createGmooncRoutes } from './createGmooncRoutes';"
|
|
318
362
|
];
|
|
319
363
|
if (indexImport) {
|
|
320
|
-
|
|
364
|
+
const convertedImport = convertImportToRelative(indexImport, appRoutesPath, appPath);
|
|
365
|
+
imports.push(convertedImport);
|
|
321
366
|
}
|
|
322
367
|
if (notFoundImport) {
|
|
323
|
-
|
|
368
|
+
const convertedImport = convertImportToRelative(notFoundImport, appRoutesPath, appPath);
|
|
369
|
+
imports.push(convertedImport);
|
|
324
370
|
}
|
|
325
371
|
const content = `${imports.join("\n")}
|
|
326
372
|
|
|
327
373
|
export function GMooncAppRoutes({ basePath = "${basePath}" }: { basePath?: string }) {
|
|
328
|
-
const allRoutes = [
|
|
374
|
+
const allRoutes: RouteObject[] = [
|
|
329
375
|
${routes.join(",\n")}
|
|
330
376
|
];
|
|
331
377
|
|
|
@@ -380,10 +426,54 @@ function patchBrowserRouter(consumerDir, basePath, dryRun) {
|
|
|
380
426
|
logInfo(`[DRY RUN] Would patch ${appPath}`);
|
|
381
427
|
return { success: true, backupPath: null };
|
|
382
428
|
}
|
|
383
|
-
generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, false);
|
|
429
|
+
generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, appPath, false);
|
|
384
430
|
const backupPath = writeFileSafe(appPath, "");
|
|
385
|
-
const importLine = `import { GMooncAppRoutes } from "./gmoonc/router/AppRoutes";`;
|
|
386
431
|
const lines = appContent.split("\n");
|
|
432
|
+
let hasBrowserRouterImport = false;
|
|
433
|
+
let reactRouterImportIndex = -1;
|
|
434
|
+
for (let i = 0; i < lines.length; i++) {
|
|
435
|
+
const line = lines[i].trim();
|
|
436
|
+
if (line.includes("from") && line.includes("react-router-dom")) {
|
|
437
|
+
reactRouterImportIndex = i;
|
|
438
|
+
if (line.includes("BrowserRouter")) {
|
|
439
|
+
hasBrowserRouterImport = true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (!hasBrowserRouterImport) {
|
|
444
|
+
if (reactRouterImportIndex >= 0) {
|
|
445
|
+
const existingLine = lines[reactRouterImportIndex];
|
|
446
|
+
if (existingLine.includes("{") && existingLine.includes("}")) {
|
|
447
|
+
const updatedLine = existingLine.replace(/\{([^}]+)\}/, (match, imports) => {
|
|
448
|
+
const importList = imports.split(",").map((s) => s.trim());
|
|
449
|
+
if (!importList.includes("BrowserRouter")) {
|
|
450
|
+
importList.push("BrowserRouter");
|
|
451
|
+
}
|
|
452
|
+
return `{ ${importList.join(", ")} }`;
|
|
453
|
+
});
|
|
454
|
+
lines[reactRouterImportIndex] = updatedLine;
|
|
455
|
+
} else {
|
|
456
|
+
lines.splice(reactRouterImportIndex + 1, 0, `import { BrowserRouter } from "react-router-dom";`);
|
|
457
|
+
}
|
|
458
|
+
} else {
|
|
459
|
+
const importLine2 = `import { BrowserRouter } from "react-router-dom";`;
|
|
460
|
+
let lastImportIndex2 = -1;
|
|
461
|
+
for (let i = 0; i < lines.length; i++) {
|
|
462
|
+
const line = lines[i].trim();
|
|
463
|
+
if (line.startsWith("import ") || line.startsWith("import{") || line.startsWith("import ")) {
|
|
464
|
+
lastImportIndex2 = i;
|
|
465
|
+
} else if (line && lastImportIndex2 >= 0 && !line.startsWith("//") && !line.startsWith("/*")) {
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (lastImportIndex2 >= 0) {
|
|
470
|
+
lines.splice(lastImportIndex2 + 1, 0, importLine2);
|
|
471
|
+
} else {
|
|
472
|
+
lines.unshift(importLine2);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const importLine = `import { GMooncAppRoutes } from "./gmoonc/router/AppRoutes";`;
|
|
387
477
|
let lastImportIndex = -1;
|
|
388
478
|
for (let i = 0; i < lines.length; i++) {
|
|
389
479
|
const line = lines[i].trim();
|
|
@@ -448,12 +538,15 @@ function patchBrowserRouter(consumerDir, basePath, dryRun) {
|
|
|
448
538
|
|
|
449
539
|
// src/cli/index.ts
|
|
450
540
|
var program = new import_commander.Command();
|
|
451
|
-
program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete dashboard into your React project").version("0.0.
|
|
541
|
+
program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete dashboard into your React project").version("0.0.5").option("-y, --yes, --auto", "Skip confirmations and install automatically").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) => {
|
|
452
542
|
try {
|
|
543
|
+
logInfo("\u{1F680} Starting gmoonc installer...");
|
|
544
|
+
logInfo("\u{1F4E6} Installing complete dashboard into your React project\n");
|
|
453
545
|
const projectDir = (0, import_process.cwd)();
|
|
454
546
|
const basePath = options.base || "/app";
|
|
455
547
|
const dryRun = options.dryRun || false;
|
|
456
548
|
const skipRouterPatch = options.skipRouterPatch || false;
|
|
549
|
+
const autoInstall = options.yes || options.auto || false;
|
|
457
550
|
const safeBasePath = basePath === "/" ? "/app" : basePath;
|
|
458
551
|
logInfo("\u{1F50D} Detecting React project...");
|
|
459
552
|
const project = detectProject(projectDir);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gmoonc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Goalmoon Ctrl (gmoonc): Complete dashboard installer for React projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://gmoonc.com",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
16
|
"bin": {
|
|
17
|
-
"gmoonc": "./dist/
|
|
17
|
+
"gmoonc": "./dist/index.cjs"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"sync:templates": "node scripts/sync-templates.mjs",
|
|
28
|
-
"build": "tsup src/cli/index.ts --format cjs --no-splitting --out-dir dist && node -e \"const fs=require('fs');const
|
|
28
|
+
"build": "tsup src/cli/index.ts --format cjs --no-splitting --out-dir dist && node -e \"const fs=require('fs');const path=require('path');const distDir='dist';const files=fs.readdirSync(distDir);const indexFile=files.find(f=>f.startsWith('index')&&f.endsWith('.js'));if(indexFile){const oldPath=path.join(distDir,indexFile);const newPath=path.join(distDir,'index.cjs');fs.renameSync(oldPath,newPath);const content=fs.readFileSync(newPath,'utf8');if(!content.startsWith('#!/usr/bin/env node')){fs.writeFileSync(newPath,'#!/usr/bin/env node\\n'+content)}}\"",
|
|
29
29
|
"clean": "rimraf dist",
|
|
30
30
|
"prepublishOnly": "npm run clean && npm run build",
|
|
31
31
|
"postinstall": "node scripts/block-global-install.cjs"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
2
|
import { Outlet, useLocation, useNavigate, Link } from 'react-router-dom';
|
|
3
|
-
import { GmooncShell } from '../ui/
|
|
3
|
+
import { GmooncShell } from '../ui/shell';
|
|
4
4
|
import { defaultConfig } from '../config/defaultConfig';
|
|
5
5
|
import { GMooncSessionProvider, useGMooncSession } from '../session/GMooncSessionContext';
|
|
6
6
|
import { GMooncUserProfile } from '../components/GMooncUserProfile';
|
|
@@ -24,33 +24,36 @@ function GMooncAppLayoutInner() {
|
|
|
24
24
|
}, [navigate]);
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
<div className="gmoonc-root">
|
|
28
|
+
<GmooncShell
|
|
29
|
+
config={defaultConfig}
|
|
30
|
+
roles={roles}
|
|
31
|
+
activePath={location.pathname}
|
|
32
|
+
onNavigate={(path) => navigate(path)}
|
|
33
|
+
headerRight={
|
|
34
|
+
<GMooncUserProfile
|
|
35
|
+
onAccount={handleAccount}
|
|
36
|
+
onAbout={handleAbout}
|
|
37
|
+
onLogoutRequest={handleLogout}
|
|
38
|
+
/>
|
|
39
|
+
}
|
|
40
|
+
renderLink={({ path, label, isActive, onClick, className, children }) => (
|
|
41
|
+
<Link
|
|
42
|
+
to={path}
|
|
43
|
+
onClick={onClick}
|
|
44
|
+
className={className}
|
|
45
|
+
style={{
|
|
46
|
+
textDecoration: isActive ? 'underline' : 'none',
|
|
47
|
+
color: 'inherit'
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{children || label}
|
|
51
|
+
</Link>
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
<Outlet />
|
|
55
|
+
</GmooncShell>
|
|
56
|
+
</div>
|
|
54
57
|
);
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Goalmoon Ctrl (gmoonc) Theme Variables
|
|
3
|
+
*
|
|
4
|
+
* This file contains CSS custom properties (variables) that define the visual theme
|
|
5
|
+
* of the gmoonc dashboard. You can customize these values to match your brand.
|
|
6
|
+
*
|
|
7
|
+
* All variables are scoped under .gmoonc-root to avoid conflicts with your app.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.gmoonc-root {
|
|
11
|
+
/* Colors - Background */
|
|
12
|
+
--gmoonc-color-bg: #293047;
|
|
13
|
+
--gmoonc-color-bg-light: #eaf0f5;
|
|
14
|
+
--gmoonc-color-bg-gradient-start: #eaf0f5;
|
|
15
|
+
--gmoonc-color-bg-gradient-end: #dbe2ea;
|
|
16
|
+
|
|
17
|
+
/* Colors - Surface */
|
|
18
|
+
--gmoonc-color-surface: #374161;
|
|
19
|
+
--gmoonc-color-surface-2: #3F4A6E;
|
|
20
|
+
--gmoonc-color-surface-3: #6374AD;
|
|
21
|
+
--gmoonc-color-surface-light: #f8f9fa;
|
|
22
|
+
--gmoonc-color-surface-white: #ffffff;
|
|
23
|
+
|
|
24
|
+
/* Colors - Text */
|
|
25
|
+
--gmoonc-color-text: #eaf0f5;
|
|
26
|
+
--gmoonc-color-text-primary: #374161;
|
|
27
|
+
--gmoonc-color-text-secondary: #6c757d;
|
|
28
|
+
--gmoonc-color-text-muted: #dbe2ea;
|
|
29
|
+
--gmoonc-color-text-white: #ffffff;
|
|
30
|
+
|
|
31
|
+
/* Colors - Primary */
|
|
32
|
+
--gmoonc-color-primary: #879FED;
|
|
33
|
+
--gmoonc-color-primary-2: #6374AD;
|
|
34
|
+
--gmoonc-color-primary-dark: #374161;
|
|
35
|
+
|
|
36
|
+
/* Colors - Accent */
|
|
37
|
+
--gmoonc-color-accent: #71b399;
|
|
38
|
+
|
|
39
|
+
/* Colors - Borders */
|
|
40
|
+
--gmoonc-color-border: #dbe2ea;
|
|
41
|
+
--gmoonc-color-border-light: #e9ecef;
|
|
42
|
+
--gmoonc-color-border-dark: #1d2436;
|
|
43
|
+
|
|
44
|
+
/* Colors - States */
|
|
45
|
+
--gmoonc-color-error: #dc2626;
|
|
46
|
+
--gmoonc-color-error-bg: #fef2f2;
|
|
47
|
+
--gmoonc-color-error-border: #fecaca;
|
|
48
|
+
--gmoonc-color-success: #166534;
|
|
49
|
+
--gmoonc-color-success-bg: #f0fdf4;
|
|
50
|
+
--gmoonc-color-success-border: #bbf7d0;
|
|
51
|
+
--gmoonc-color-disabled: #6c757d;
|
|
52
|
+
--gmoonc-color-disabled-bg: #f8f9fa;
|
|
53
|
+
|
|
54
|
+
/* Spacing */
|
|
55
|
+
--gmoonc-gap: 12px;
|
|
56
|
+
--gmoonc-gap-sm: 8px;
|
|
57
|
+
--gmoonc-gap-md: 16px;
|
|
58
|
+
--gmoonc-gap-lg: 20px;
|
|
59
|
+
--gmoonc-gap-xl: 30px;
|
|
60
|
+
|
|
61
|
+
/* Border Radius */
|
|
62
|
+
--gmoonc-radius: 12px;
|
|
63
|
+
--gmoonc-radius-sm: 8px;
|
|
64
|
+
--gmoonc-radius-md: 10px;
|
|
65
|
+
--gmoonc-radius-lg: 16px;
|
|
66
|
+
|
|
67
|
+
/* Typography */
|
|
68
|
+
--gmoonc-font-family: "Montserrat", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
|
|
69
|
+
--gmoonc-font-weight-title: 700;
|
|
70
|
+
--gmoonc-font-weight-heading: 600;
|
|
71
|
+
--gmoonc-font-weight-body: 400;
|
|
72
|
+
--gmoonc-font-weight-medium: 500;
|
|
73
|
+
|
|
74
|
+
/* Font Sizes */
|
|
75
|
+
--gmoonc-font-size-xs: 12px;
|
|
76
|
+
--gmoonc-font-size-sm: 14px;
|
|
77
|
+
--gmoonc-font-size-base: 16px;
|
|
78
|
+
--gmoonc-font-size-lg: 18px;
|
|
79
|
+
--gmoonc-font-size-xl: 24px;
|
|
80
|
+
--gmoonc-font-size-2xl: 28px;
|
|
81
|
+
--gmoonc-font-size-3xl: 32px;
|
|
82
|
+
|
|
83
|
+
/* Shadows */
|
|
84
|
+
--gmoonc-shadow-sm: 0 4px 6px rgba(55, 65, 97, 0.1);
|
|
85
|
+
--gmoonc-shadow-md: 0 4px 20px rgba(55, 65, 97, 0.1);
|
|
86
|
+
--gmoonc-shadow-lg: 0 8px 32px rgba(55, 65, 97, 0.15);
|
|
87
|
+
--gmoonc-shadow-header: 0 4px 20px rgba(55, 65, 97, 0.3);
|
|
88
|
+
--gmoonc-shadow-menu-header: 0 8px 16px rgba(0, 0, 0, 0.22);
|
|
89
|
+
|
|
90
|
+
/* Transitions */
|
|
91
|
+
--gmoonc-transition: 0.3s ease;
|
|
92
|
+
--gmoonc-transition-fast: 0.2s ease;
|
|
93
|
+
|
|
94
|
+
/* Z-index */
|
|
95
|
+
--gmoonc-z-header: 1000;
|
|
96
|
+
--gmoonc-z-profile: 1001;
|
|
97
|
+
--gmoonc-z-dropdown: 1002;
|
|
98
|
+
--gmoonc-z-hamburger: 1002;
|
|
99
|
+
--gmoonc-z-backdrop: 1050;
|
|
100
|
+
--gmoonc-z-menu: 1100;
|
|
101
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo, useEffect } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
|
-
import type { MenuItem as CoreMenuItem } from '
|
|
3
|
+
import type { MenuItem as CoreMenuItem } from '../core/types';
|
|
4
4
|
import './styles.css';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -36,6 +36,8 @@ export interface GmooncMenuProps {
|
|
|
36
36
|
label: string;
|
|
37
37
|
isActive: boolean;
|
|
38
38
|
onClick?: () => void;
|
|
39
|
+
className?: string;
|
|
40
|
+
children?: React.ReactNode;
|
|
39
41
|
}) => React.ReactNode;
|
|
40
42
|
/** Whether menu is open (for mobile/tablet) */
|
|
41
43
|
isOpen?: boolean;
|
|
@@ -387,30 +389,42 @@ export function GmooncMenu({
|
|
|
387
389
|
};
|
|
388
390
|
|
|
389
391
|
// Render main item link/button
|
|
390
|
-
|
|
392
|
+
// IMPORTANT: If item has submenu, always use button (never renderLink) to avoid navigation to non-existent routes
|
|
393
|
+
const mainItemContent = hasSubmenu ? (
|
|
394
|
+
// Item with submenu: always button (toggle only, no navigation)
|
|
395
|
+
<button
|
|
396
|
+
type="button"
|
|
397
|
+
className={`gmoonc-menu-link has-submenu ${isSubmenuOpen ? 'submenu-open' : ''} ${isItemActive ? 'active' : ''}`}
|
|
398
|
+
onClick={handleItemClick}
|
|
399
|
+
>
|
|
400
|
+
{item.icon && <span className="gmoonc-menu-icon">{item.icon}</span>}
|
|
401
|
+
<span className="gmoonc-menu-label">{item.label}</span>
|
|
402
|
+
<span className="gmoonc-submenu-arrow">
|
|
403
|
+
{isSubmenuOpen
|
|
404
|
+
? (item.collapseIcon || null)
|
|
405
|
+
: (item.expandIcon || null)
|
|
406
|
+
}
|
|
407
|
+
</span>
|
|
408
|
+
</button>
|
|
409
|
+
) : item.path && renderLink ? (
|
|
410
|
+
// Item without submenu and with path: use renderLink
|
|
391
411
|
renderLink({
|
|
392
412
|
path: item.path,
|
|
393
413
|
label: item.label,
|
|
394
414
|
isActive: isItemActive,
|
|
395
|
-
onClick: handleItemClick
|
|
415
|
+
onClick: handleItemClick,
|
|
416
|
+
className: `gmoonc-menu-link ${isItemActive ? 'active' : ''}`
|
|
396
417
|
})
|
|
397
418
|
) : (
|
|
419
|
+
// Item without submenu and without renderLink: use button
|
|
398
420
|
<button
|
|
399
421
|
type="button"
|
|
400
|
-
className={`gmoonc-menu-link ${
|
|
422
|
+
className={`gmoonc-menu-link ${isItemActive ? 'active' : ''}`}
|
|
401
423
|
onClick={handleItemClick}
|
|
402
|
-
disabled={!item.path && !
|
|
424
|
+
disabled={!item.path && !onNavigate}
|
|
403
425
|
>
|
|
404
426
|
{item.icon && <span className="gmoonc-menu-icon">{item.icon}</span>}
|
|
405
427
|
<span className="gmoonc-menu-label">{item.label}</span>
|
|
406
|
-
{hasSubmenu && (
|
|
407
|
-
<span className="gmoonc-submenu-arrow">
|
|
408
|
-
{isSubmenuOpen
|
|
409
|
-
? (item.collapseIcon || null)
|
|
410
|
-
: (item.expandIcon || null)
|
|
411
|
-
}
|
|
412
|
-
</span>
|
|
413
|
-
)}
|
|
414
428
|
</button>
|
|
415
429
|
);
|
|
416
430
|
|
|
@@ -439,7 +453,8 @@ export function GmooncMenu({
|
|
|
439
453
|
path: subItem.path,
|
|
440
454
|
label: subItem.label,
|
|
441
455
|
isActive: isSubActive,
|
|
442
|
-
onClick: handleSubItemClick
|
|
456
|
+
onClick: handleSubItemClick,
|
|
457
|
+
className: `gmoonc-submenu-link ${isSubActive ? 'active' : ''}`
|
|
443
458
|
}) : (
|
|
444
459
|
<button
|
|
445
460
|
type="button"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import type { GmooncConfig } from '
|
|
2
|
+
import type { GmooncConfig } from '../core/types';
|
|
3
3
|
import { GmooncHeader } from './header';
|
|
4
4
|
import { GmooncSidebar } from './sidebar';
|
|
5
5
|
import { GmooncMenu, type GmooncMenuItem } from './menu';
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { RouteObject } from 'react-router-dom';
|
|
3
|
-
import { GmooncAppLayout } from '
|
|
4
|
-
import { GMooncLoginPage } from '
|
|
5
|
-
import { GMooncRegisterPage } from '
|
|
6
|
-
import { GMooncForgotPasswordPage } from '
|
|
7
|
-
import { GMooncResetPasswordPage } from '
|
|
8
|
-
import { GMooncLogoutPage } from '
|
|
9
|
-
import { GMooncAppHomePage } from '
|
|
10
|
-
import { GMooncPermissionsPage } from '
|
|
11
|
-
import { GMooncAdminUsersPage } from '
|
|
12
|
-
import { GMooncAdminAuthorizationsPage } from '
|
|
13
|
-
import { GMooncAdminNotificationsPage } from '
|
|
14
|
-
import { GMooncTechnicalMessagesPage } from '
|
|
15
|
-
import { GMooncCustomerMessagesPage } from '
|
|
16
|
-
import { GMooncOfficeAccountPage } from '
|
|
17
|
-
import { GMooncOfficeAboutPage } from '
|
|
3
|
+
import { GmooncAppLayout } from '../layout/GMooncAppLayout';
|
|
4
|
+
import { GMooncLoginPage } from '../pages/auth/GMooncLoginPage';
|
|
5
|
+
import { GMooncRegisterPage } from '../pages/auth/GMooncRegisterPage';
|
|
6
|
+
import { GMooncForgotPasswordPage } from '../pages/auth/GMooncForgotPasswordPage';
|
|
7
|
+
import { GMooncResetPasswordPage } from '../pages/auth/GMooncResetPasswordPage';
|
|
8
|
+
import { GMooncLogoutPage } from '../pages/auth/GMooncLogoutPage';
|
|
9
|
+
import { GMooncAppHomePage } from '../pages/app/GMooncAppHomePage';
|
|
10
|
+
import { GMooncPermissionsPage } from '../pages/app/admin/GMooncPermissionsPage';
|
|
11
|
+
import { GMooncAdminUsersPage } from '../pages/app/admin/GMooncAdminUsersPage';
|
|
12
|
+
import { GMooncAdminAuthorizationsPage } from '../pages/app/admin/GMooncAdminAuthorizationsPage';
|
|
13
|
+
import { GMooncAdminNotificationsPage } from '../pages/app/admin/GMooncAdminNotificationsPage';
|
|
14
|
+
import { GMooncTechnicalMessagesPage } from '../pages/app/technical/GMooncTechnicalMessagesPage';
|
|
15
|
+
import { GMooncCustomerMessagesPage } from '../pages/app/customer/GMooncCustomerMessagesPage';
|
|
16
|
+
import { GMooncOfficeAccountPage } from '../pages/app/office/GMooncOfficeAccountPage';
|
|
17
|
+
import { GMooncOfficeAboutPage } from '../pages/app/office/GMooncOfficeAboutPage';
|
|
18
|
+
import { GMooncSessionProvider } from '../session/GMooncSessionContext';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Creates route objects for the gmoonc dashboard.
|
|
@@ -37,7 +38,11 @@ export function createGmooncRoutes(options?: { basePath?: string }): RouteObject
|
|
|
37
38
|
// Auth routes (outside basePath, but still avoid "/" to be safe)
|
|
38
39
|
{
|
|
39
40
|
path: '/login',
|
|
40
|
-
element:
|
|
41
|
+
element: (
|
|
42
|
+
<GMooncSessionProvider>
|
|
43
|
+
<GMooncLoginPage />
|
|
44
|
+
</GMooncSessionProvider>
|
|
45
|
+
)
|
|
41
46
|
},
|
|
42
47
|
{
|
|
43
48
|
path: '/register',
|
|
@@ -53,7 +58,11 @@ export function createGmooncRoutes(options?: { basePath?: string }): RouteObject
|
|
|
53
58
|
},
|
|
54
59
|
{
|
|
55
60
|
path: '/logout',
|
|
56
|
-
element:
|
|
61
|
+
element: (
|
|
62
|
+
<GMooncSessionProvider>
|
|
63
|
+
<GMooncLogoutPage />
|
|
64
|
+
</GMooncSessionProvider>
|
|
65
|
+
)
|
|
57
66
|
},
|
|
58
67
|
// Dashboard routes (always nested under basePath, never "/")
|
|
59
68
|
{
|