gmoonc 0.0.17 → 0.0.19
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 +15 -0
- package/dist/index.cjs +199 -47
- package/package.json +1 -1
- package/src/templates/shared/src/gmoonc/layout/GMooncAppLayout.tsx +25 -2
package/README.md
CHANGED
|
@@ -63,6 +63,21 @@ The logo is installed at `src/gmoonc/assets/gmoonc-logo.png`. You can replace it
|
|
|
63
63
|
|
|
64
64
|
## Changelog
|
|
65
65
|
|
|
66
|
+
### 0.0.19
|
|
67
|
+
- Fix: Added Navigate import to GMooncAppLayout.tsx template
|
|
68
|
+
- Fix: Corrected route protection order - isLoading checked before isAuthenticated
|
|
69
|
+
- Fix: Automatic update of all session context imports to use GMooncSupabaseSessionProvider
|
|
70
|
+
- Fix: All components now use Supabase provider instead of mock context
|
|
71
|
+
|
|
72
|
+
### 0.0.18
|
|
73
|
+
- Fix: env.ts no longer throws when .env is missing - only logs warning
|
|
74
|
+
- Fix: client.ts import corrected - hasSupabaseEnv imported from env.ts
|
|
75
|
+
- Fix: GMooncSupabaseSessionProvider imports corrected - hasSupabaseEnv from env.ts
|
|
76
|
+
- Fix: createMissingEnvClient improved - more robust mock client
|
|
77
|
+
- Fix: GMooncAppLayout route protection added - redirects to /login when not authenticated
|
|
78
|
+
- Fix: All imports in generated files now without .js suffix
|
|
79
|
+
- Fix: client.ts syntax error fixed - removed extra closing brace
|
|
80
|
+
|
|
66
81
|
### 0.0.17
|
|
67
82
|
- Feature: New `supabase --vite` command to setup Supabase integration
|
|
68
83
|
- Feature: Supabase Auth + RBAC integration with session provider
|
package/dist/index.cjs
CHANGED
|
@@ -388,8 +388,8 @@ function resolveImportToFile(importPath, fromDir) {
|
|
|
388
388
|
const extensions = [".tsx", ".ts", ".jsx", ".js"];
|
|
389
389
|
const indexFiles = ["index.tsx", "index.ts", "index.jsx", "index.js"];
|
|
390
390
|
if ((0, import_fs8.existsSync)(basePath)) {
|
|
391
|
-
const { statSync:
|
|
392
|
-
const stats =
|
|
391
|
+
const { statSync: statSync3 } = require("fs");
|
|
392
|
+
const stats = statSync3(basePath);
|
|
393
393
|
if (stats.isFile()) {
|
|
394
394
|
return basePath;
|
|
395
395
|
}
|
|
@@ -405,16 +405,16 @@ function resolveImportToFile(importPath, fromDir) {
|
|
|
405
405
|
for (const ext of extensions) {
|
|
406
406
|
const fullPath = basePath + ext;
|
|
407
407
|
if ((0, import_fs8.existsSync)(fullPath)) {
|
|
408
|
-
const { statSync:
|
|
409
|
-
const stats =
|
|
408
|
+
const { statSync: statSync3 } = require("fs");
|
|
409
|
+
const stats = statSync3(fullPath);
|
|
410
410
|
if (stats.isFile()) {
|
|
411
411
|
return fullPath;
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
415
|
if ((0, import_fs8.existsSync)(basePath)) {
|
|
416
|
-
const { statSync:
|
|
417
|
-
const stats =
|
|
416
|
+
const { statSync: statSync3 } = require("fs");
|
|
417
|
+
const stats = statSync3(basePath);
|
|
418
418
|
if (stats.isDirectory()) {
|
|
419
419
|
for (const indexFile of indexFiles) {
|
|
420
420
|
const indexPath = (0, import_path5.join)(basePath, indexFile);
|
|
@@ -787,30 +787,35 @@ function generateEnvFile(projectDir, supabaseDir, platform) {
|
|
|
787
787
|
* Supabase environment variables for Vite
|
|
788
788
|
*
|
|
789
789
|
* This file reads environment variables from import.meta.env (Vite)
|
|
790
|
-
* and validates their presence.
|
|
790
|
+
* and validates their presence. Never throws - only logs warnings.
|
|
791
791
|
*/
|
|
792
792
|
|
|
793
793
|
const supabaseUrlValue = import.meta.env.VITE_SUPABASE_URL;
|
|
794
794
|
const supabaseAnonKeyValue = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
|
795
795
|
|
|
796
|
-
|
|
796
|
+
// Safe trim with fallback to empty string
|
|
797
|
+
const url = typeof supabaseUrlValue === 'string' ? supabaseUrlValue.trim() : '';
|
|
798
|
+
const anonKey = typeof supabaseAnonKeyValue === 'string' ? supabaseAnonKeyValue.trim() : '';
|
|
797
799
|
|
|
798
|
-
|
|
800
|
+
export const supabaseUrl = url;
|
|
801
|
+
export const supabaseAnonKey = anonKey;
|
|
802
|
+
export const hasSupabaseEnv = Boolean(url && anonKey);
|
|
803
|
+
|
|
804
|
+
// Log warning once if env vars are missing (only in browser, only in dev)
|
|
805
|
+
if (!hasSupabaseEnv && typeof window !== 'undefined' && import.meta.env.DEV) {
|
|
799
806
|
const missingVars: string[] = [];
|
|
800
|
-
if (!
|
|
801
|
-
if (!
|
|
802
|
-
|
|
803
|
-
const errorMessage = \`Missing Supabase environment variables: \${missingVars.join(', ')}.\\n\\nPlease:\\n1. Copy .env.example to .env\\n2. Fill VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY\\n3. Restart dev server\`;
|
|
804
|
-
|
|
805
|
-
if (typeof window !== 'undefined') {
|
|
806
|
-
console.error('\u274C', errorMessage);
|
|
807
|
-
}
|
|
807
|
+
if (!url) missingVars.push('VITE_SUPABASE_URL');
|
|
808
|
+
if (!anonKey) missingVars.push('VITE_SUPABASE_ANON_KEY');
|
|
808
809
|
|
|
809
|
-
|
|
810
|
+
console.warn(
|
|
811
|
+
\`\u26A0\uFE0F Supabase environment variables missing: \${missingVars.join(', ')}.\\n\` +
|
|
812
|
+
\`Please:\\n\` +
|
|
813
|
+
\`1. Copy .env.example to .env\\n\` +
|
|
814
|
+
\`2. Fill VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY\\n\` +
|
|
815
|
+
\`3. Restart dev server\\n\` +
|
|
816
|
+
\`The app will continue without authentication until configured.\`
|
|
817
|
+
);
|
|
810
818
|
}
|
|
811
|
-
|
|
812
|
-
export const supabaseUrl = supabaseUrlValue!;
|
|
813
|
-
export const supabaseAnonKey = supabaseAnonKeyValue!;
|
|
814
819
|
`;
|
|
815
820
|
writeFileSafe(envFilePath, content);
|
|
816
821
|
logSuccess("Generated src/gmoonc/supabase/env.ts");
|
|
@@ -818,7 +823,7 @@ export const supabaseAnonKey = supabaseAnonKeyValue!;
|
|
|
818
823
|
function generateClientFile(projectDir, supabaseDir) {
|
|
819
824
|
const clientFilePath = (0, import_path6.join)(supabaseDir, "client.ts");
|
|
820
825
|
const content = `import { createClient, type SupabaseClient } from '@supabase/supabase-js';
|
|
821
|
-
import { supabaseUrl, supabaseAnonKey, hasSupabaseEnv } from './env
|
|
826
|
+
import { supabaseUrl, supabaseAnonKey, hasSupabaseEnv } from './env';
|
|
822
827
|
|
|
823
828
|
// Database types (minimal - extend as needed)
|
|
824
829
|
export interface Database {
|
|
@@ -1118,37 +1123,58 @@ export interface Database {
|
|
|
1118
1123
|
// Singleton pattern to avoid multiple instances
|
|
1119
1124
|
let supabaseInstance: SupabaseClient<Database> | null = null;
|
|
1120
1125
|
|
|
1121
|
-
|
|
1126
|
+
let warningLogged = false;
|
|
1127
|
+
|
|
1128
|
+
const createMissingEnvClient = (): SupabaseClient<Database> => {
|
|
1122
1129
|
const missingEnvMessage = 'Supabase environment variables are missing or empty.';
|
|
1123
1130
|
|
|
1131
|
+
// Log warning once
|
|
1132
|
+
if (!warningLogged && typeof window !== 'undefined') {
|
|
1133
|
+
console.warn('\u26A0\uFE0F Supabase env not configured. Authentication features will not work.');
|
|
1134
|
+
warningLogged = true;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1124
1137
|
const notConfigured = () => Promise.reject(new Error(missingEnvMessage));
|
|
1138
|
+
const emptyResult = () => Promise.resolve({ data: null, error: new Error(missingEnvMessage) });
|
|
1139
|
+
const emptyArray = () => Promise.resolve({ data: [], error: new Error(missingEnvMessage) });
|
|
1125
1140
|
|
|
1126
1141
|
return {
|
|
1127
1142
|
auth: {
|
|
1128
|
-
getSession: async () => ({ data: { session: null }, error:
|
|
1143
|
+
getSession: async () => ({ data: { session: null }, error: null }),
|
|
1129
1144
|
signInWithPassword: notConfigured,
|
|
1130
1145
|
signUp: notConfigured,
|
|
1131
|
-
signOut:
|
|
1146
|
+
signOut: async () => ({ error: null }),
|
|
1132
1147
|
resend: notConfigured,
|
|
1133
|
-
onAuthStateChange: () => ({
|
|
1148
|
+
onAuthStateChange: () => ({
|
|
1149
|
+
data: { subscription: { unsubscribe: () => {} } },
|
|
1150
|
+
error: null
|
|
1151
|
+
}),
|
|
1134
1152
|
startAutoRefresh: () => Promise.resolve(),
|
|
1135
1153
|
stopAutoRefresh: () => {},
|
|
1136
1154
|
},
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
eq: () => ({
|
|
1155
|
+
from: (table: string) => ({
|
|
1156
|
+
select: () => ({
|
|
1157
|
+
eq: () => ({
|
|
1158
|
+
single: () => emptyResult(),
|
|
1159
|
+
maybeSingle: () => emptyResult(),
|
|
1160
|
+
}),
|
|
1161
|
+
order: () => ({
|
|
1162
|
+
eq: () => emptyArray(),
|
|
1163
|
+
}),
|
|
1140
1164
|
}),
|
|
1141
|
-
|
|
1142
|
-
|
|
1165
|
+
insert: () => notConfigured(),
|
|
1166
|
+
update: () => notConfigured(),
|
|
1167
|
+
delete: () => notConfigured(),
|
|
1168
|
+
upsert: () => notConfigured(),
|
|
1169
|
+
}),
|
|
1170
|
+
rpc: () => Promise.resolve({ data: null, error: new Error(missingEnvMessage) }),
|
|
1171
|
+
} as unknown as SupabaseClient<Database>;
|
|
1143
1172
|
};
|
|
1144
1173
|
|
|
1145
1174
|
export const supabase = (() => {
|
|
1146
1175
|
if (supabaseInstance) return supabaseInstance;
|
|
1147
1176
|
|
|
1148
1177
|
if (!hasSupabaseEnv) {
|
|
1149
|
-
if (typeof window !== 'undefined' && import.meta.env.DEV) {
|
|
1150
|
-
console.warn('\u26A0\uFE0F Supabase env vars missing. App will continue without authentication.');
|
|
1151
|
-
}
|
|
1152
1178
|
supabaseInstance = createMissingEnvClient();
|
|
1153
1179
|
return supabaseInstance;
|
|
1154
1180
|
}
|
|
@@ -1196,7 +1222,8 @@ function generateSessionProvider(projectDir, supabaseDir) {
|
|
|
1196
1222
|
const content = `import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
|
|
1197
1223
|
import { useNavigate } from 'react-router-dom';
|
|
1198
1224
|
import { User } from '@supabase/supabase-js';
|
|
1199
|
-
import { supabase
|
|
1225
|
+
import { supabase } from '../client';
|
|
1226
|
+
import { hasSupabaseEnv } from '../env';
|
|
1200
1227
|
|
|
1201
1228
|
export interface GMooncUser {
|
|
1202
1229
|
id: string;
|
|
@@ -1418,8 +1445,8 @@ export function useGMooncSession(): GMooncSessionContextType {
|
|
|
1418
1445
|
}
|
|
1419
1446
|
function generateRbacHelpers(projectDir, supabaseDir) {
|
|
1420
1447
|
const getMyProfilePath = (0, import_path6.join)(supabaseDir, "rbac/getMyProfile.ts");
|
|
1421
|
-
const getMyProfileContent = `import { supabase } from '../client
|
|
1422
|
-
import type { Database } from '../client
|
|
1448
|
+
const getMyProfileContent = `import { supabase } from '../client';
|
|
1449
|
+
import type { Database } from '../client';
|
|
1423
1450
|
|
|
1424
1451
|
type Profile = Database['public']['Tables']['profiles']['Row'];
|
|
1425
1452
|
|
|
@@ -1456,7 +1483,7 @@ export async function getMyProfile(): Promise<Profile | null> {
|
|
|
1456
1483
|
writeFileSafe(getMyProfilePath, getMyProfileContent);
|
|
1457
1484
|
logSuccess("Generated src/gmoonc/supabase/rbac/getMyProfile.ts");
|
|
1458
1485
|
const hasPermissionPath = (0, import_path6.join)(supabaseDir, "rbac/hasPermission.ts");
|
|
1459
|
-
const hasPermissionContent = `import { supabase } from '../client
|
|
1486
|
+
const hasPermissionContent = `import { supabase } from '../client';
|
|
1460
1487
|
|
|
1461
1488
|
/**
|
|
1462
1489
|
* Check if the current user has a specific permission
|
|
@@ -1545,7 +1572,7 @@ export function hasPermissionSync(
|
|
|
1545
1572
|
writeFileSafe(hasPermissionPath, hasPermissionContent);
|
|
1546
1573
|
logSuccess("Generated src/gmoonc/supabase/rbac/hasPermission.ts");
|
|
1547
1574
|
const getUserPermissionsPath = (0, import_path6.join)(supabaseDir, "rbac/getUserPermissions.ts");
|
|
1548
|
-
const getUserPermissionsContent = `import { supabase } from '../client
|
|
1575
|
+
const getUserPermissionsContent = `import { supabase } from '../client';
|
|
1549
1576
|
|
|
1550
1577
|
export interface UserPermission {
|
|
1551
1578
|
module_name: string;
|
|
@@ -1589,10 +1616,10 @@ export async function getUserPermissions(): Promise<UserPermission[]> {
|
|
|
1589
1616
|
writeFileSafe(getUserPermissionsPath, getUserPermissionsContent);
|
|
1590
1617
|
logSuccess("Generated src/gmoonc/supabase/rbac/getUserPermissions.ts");
|
|
1591
1618
|
const rbacIndexPath = (0, import_path6.join)(supabaseDir, "rbac/index.ts");
|
|
1592
|
-
const rbacIndexContent = `export { getMyProfile } from './getMyProfile
|
|
1593
|
-
export { hasPermission, hasPermissionSync } from './hasPermission
|
|
1594
|
-
export { getUserPermissions } from './getUserPermissions
|
|
1595
|
-
export type { UserPermission } from './getUserPermissions
|
|
1619
|
+
const rbacIndexContent = `export { getMyProfile } from './getMyProfile';
|
|
1620
|
+
export { hasPermission, hasPermissionSync } from './hasPermission';
|
|
1621
|
+
export { getUserPermissions } from './getUserPermissions';
|
|
1622
|
+
export type { UserPermission } from './getUserPermissions';
|
|
1596
1623
|
`;
|
|
1597
1624
|
writeFileSafe(rbacIndexPath, rbacIndexContent);
|
|
1598
1625
|
logSuccess("Generated src/gmoonc/supabase/rbac/index.ts");
|
|
@@ -1638,18 +1665,143 @@ function patchExistingCode(projectDir, gmooncDir) {
|
|
|
1638
1665
|
);
|
|
1639
1666
|
content = content.replace(
|
|
1640
1667
|
/import\s+{\s*GMooncSessionProvider[^}]*}\s+from\s+['"][^'"]*session\/GMooncSessionContext['"]/g,
|
|
1641
|
-
"import { GMooncSupabaseSessionProvider } from '../supabase/auth/GMooncSupabaseSessionProvider'"
|
|
1668
|
+
"import { GMooncSupabaseSessionProvider, useGMooncSession } from '../supabase/auth/GMooncSupabaseSessionProvider'"
|
|
1642
1669
|
);
|
|
1670
|
+
if (!content.includes("from 'react-router-dom'") || !content.includes("Navigate")) {
|
|
1671
|
+
content = content.replace(
|
|
1672
|
+
/import\s+{\s*([^}]*)\s*}\s+from\s+['"]react-router-dom['"]/,
|
|
1673
|
+
(match, imports) => {
|
|
1674
|
+
if (!imports.includes("Navigate")) {
|
|
1675
|
+
return `import { ${imports}, Navigate } from 'react-router-dom'`;
|
|
1676
|
+
}
|
|
1677
|
+
return match;
|
|
1678
|
+
}
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1643
1681
|
content = content.replace(/GMooncSessionProvider/g, "GMooncSupabaseSessionProvider");
|
|
1682
|
+
if (!content.includes("!isAuthenticated")) {
|
|
1683
|
+
content = content.replace(
|
|
1684
|
+
/const\s+{\s*roles,\s*logout\s*}\s+=\s+useGMooncSession\(\);/,
|
|
1685
|
+
"const { roles, logout, isLoading, isAuthenticated } = useGMooncSession();"
|
|
1686
|
+
);
|
|
1687
|
+
content = content.replace(
|
|
1688
|
+
/(\s+}, \[navigate, getBasePath\]\);)\s+(\s+return\s+\()/,
|
|
1689
|
+
`$1
|
|
1690
|
+
|
|
1691
|
+
// Show loading state while checking authentication (verificar primeiro)
|
|
1692
|
+
if (isLoading) {
|
|
1693
|
+
return (
|
|
1694
|
+
<div className="gmoonc-root">
|
|
1695
|
+
<div style={{
|
|
1696
|
+
display: 'flex',
|
|
1697
|
+
justifyContent: 'center',
|
|
1698
|
+
alignItems: 'center',
|
|
1699
|
+
height: '100vh',
|
|
1700
|
+
fontSize: 'var(--gmoonc-font-size-base, 16px)',
|
|
1701
|
+
color: 'var(--gmoonc-color-text, #333)'
|
|
1702
|
+
}}>
|
|
1703
|
+
Loading...
|
|
1704
|
+
</div>
|
|
1705
|
+
</div>
|
|
1706
|
+
);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// Route protection: redirect to login if not authenticated
|
|
1710
|
+
if (!isAuthenticated) {
|
|
1711
|
+
return <Navigate to="/login" replace />;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
$2`
|
|
1715
|
+
);
|
|
1716
|
+
} else {
|
|
1717
|
+
content = content.replace(
|
|
1718
|
+
/\/\/ Route protection: redirect to login if not authenticated\s+if\s+\(!isLoading\s+&&\s+!isAuthenticated\)\s+\{[\s\S]*?return\s+<Navigate[^>]+>;[\s\S]*?\}\s+(\/\/ Show loading state)/,
|
|
1719
|
+
`// Show loading state while checking authentication (verificar primeiro)
|
|
1720
|
+
if (isLoading) {
|
|
1721
|
+
return (
|
|
1722
|
+
<div className="gmoonc-root">
|
|
1723
|
+
<div style={{
|
|
1724
|
+
display: 'flex',
|
|
1725
|
+
justifyContent: 'center',
|
|
1726
|
+
alignItems: 'center',
|
|
1727
|
+
height: '100vh',
|
|
1728
|
+
fontSize: 'var(--gmoonc-font-size-base, 16px)',
|
|
1729
|
+
color: 'var(--gmoonc-color-text, #333)'
|
|
1730
|
+
}}>
|
|
1731
|
+
Loading...
|
|
1732
|
+
</div>
|
|
1733
|
+
</div>
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// Route protection: redirect to login if not authenticated
|
|
1738
|
+
if (!isAuthenticated) {
|
|
1739
|
+
return <Navigate to="/login" replace />;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
$1`
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1644
1745
|
writeFileSafe(layoutPath, content);
|
|
1645
|
-
logSuccess("Patched layout/GMooncAppLayout.tsx to use GMooncSupabaseSessionProvider");
|
|
1746
|
+
logSuccess("Patched layout/GMooncAppLayout.tsx to use GMooncSupabaseSessionProvider and add route protection");
|
|
1747
|
+
}
|
|
1748
|
+
updateAllSessionImports(gmooncDir);
|
|
1749
|
+
}
|
|
1750
|
+
function updateAllSessionImports(gmooncDir) {
|
|
1751
|
+
const filesToUpdate = [];
|
|
1752
|
+
function findFiles(dir) {
|
|
1753
|
+
if (!(0, import_fs9.existsSync)(dir)) return;
|
|
1754
|
+
const entries = (0, import_fs9.readdirSync)(dir, { withFileTypes: true });
|
|
1755
|
+
for (const entry of entries) {
|
|
1756
|
+
const fullPath = (0, import_path6.join)(dir, entry.name);
|
|
1757
|
+
if (entry.name.includes(".bak-") || entry.name === "supabase") {
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
if (entry.isDirectory()) {
|
|
1761
|
+
findFiles(fullPath);
|
|
1762
|
+
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx"))) {
|
|
1763
|
+
filesToUpdate.push(fullPath);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
findFiles(gmooncDir);
|
|
1768
|
+
let updatedCount = 0;
|
|
1769
|
+
for (const filePath of filesToUpdate) {
|
|
1770
|
+
let content = (0, import_fs9.readFileSync)(filePath, "utf-8");
|
|
1771
|
+
let modified = false;
|
|
1772
|
+
const fileDir = (0, import_path6.dirname)(filePath);
|
|
1773
|
+
const supabaseProviderPath = (0, import_path6.join)(gmooncDir, "supabase", "auth", "GMooncSupabaseSessionProvider");
|
|
1774
|
+
let relativePath = (0, import_path6.relative)(fileDir, supabaseProviderPath).replace(/\\/g, "/");
|
|
1775
|
+
if (!relativePath.startsWith(".")) {
|
|
1776
|
+
relativePath = "./" + relativePath;
|
|
1777
|
+
}
|
|
1778
|
+
const oldImportPattern = /from\s+['"](\.\.?\/)+session\/GMooncSessionContext['"]/g;
|
|
1779
|
+
if (oldImportPattern.test(content)) {
|
|
1780
|
+
content = content.replace(
|
|
1781
|
+
/from\s+['"](\.\.?\/)+session\/GMooncSessionContext['"]/g,
|
|
1782
|
+
`from '${relativePath}'`
|
|
1783
|
+
);
|
|
1784
|
+
modified = true;
|
|
1785
|
+
}
|
|
1786
|
+
if (content.includes("GMooncSessionProvider") && !content.includes("GMooncSupabaseSessionProvider")) {
|
|
1787
|
+
content = content.replace(/GMooncSessionProvider/g, "GMooncSupabaseSessionProvider");
|
|
1788
|
+
modified = true;
|
|
1789
|
+
}
|
|
1790
|
+
if (modified) {
|
|
1791
|
+
writeFileSafe(filePath, content);
|
|
1792
|
+
updatedCount++;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (updatedCount > 0) {
|
|
1796
|
+
logSuccess(`Updated ${updatedCount} file(s) to use GMooncSupabaseSessionProvider`);
|
|
1797
|
+
} else {
|
|
1798
|
+
logInfo("All files already use GMooncSupabaseSessionProvider");
|
|
1646
1799
|
}
|
|
1647
|
-
logInfo("Auth pages will use Supabase via GMooncSupabaseSessionProvider");
|
|
1648
1800
|
}
|
|
1649
1801
|
|
|
1650
1802
|
// src/cli/index.ts
|
|
1651
1803
|
var program = new import_commander.Command();
|
|
1652
|
-
program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete dashboard into your React project").version("0.0.
|
|
1804
|
+
program.name("gmoonc").description("Goalmoon Ctrl (gmoonc): Install complete dashboard into your React project").version("0.0.19").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) => {
|
|
1653
1805
|
try {
|
|
1654
1806
|
logInfo("\u{1F680} Starting gmoonc installer...");
|
|
1655
1807
|
logInfo("\u{1F4E6} Installing complete dashboard into your React project\n");
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
|
-
import { Outlet, useLocation, useNavigate, Link } from 'react-router-dom';
|
|
2
|
+
import { Outlet, useLocation, useNavigate, Link, Navigate } from 'react-router-dom';
|
|
3
3
|
import { GmooncShell } from '../ui/shell';
|
|
4
4
|
import { defaultConfig } from '../config/defaultConfig';
|
|
5
5
|
import { GMooncSessionProvider, useGMooncSession } from '../session/GMooncSessionContext';
|
|
@@ -9,7 +9,7 @@ import logoUrl from '../assets/gmoonc-logo.png';
|
|
|
9
9
|
function GMooncAppLayoutInner() {
|
|
10
10
|
const location = useLocation();
|
|
11
11
|
const navigate = useNavigate();
|
|
12
|
-
const { roles, logout } = useGMooncSession();
|
|
12
|
+
const { roles, logout, isLoading, isAuthenticated } = useGMooncSession();
|
|
13
13
|
|
|
14
14
|
// Determine basePath from current location
|
|
15
15
|
// If path is /app/..., basePath is /app
|
|
@@ -47,6 +47,29 @@ function GMooncAppLayoutInner() {
|
|
|
47
47
|
navigate(normalizedBasePath);
|
|
48
48
|
}, [navigate, getBasePath]);
|
|
49
49
|
|
|
50
|
+
// Show loading state while checking authentication (verificar primeiro)
|
|
51
|
+
if (isLoading) {
|
|
52
|
+
return (
|
|
53
|
+
<div className="gmoonc-root">
|
|
54
|
+
<div style={{
|
|
55
|
+
display: 'flex',
|
|
56
|
+
justifyContent: 'center',
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
height: '100vh',
|
|
59
|
+
fontSize: 'var(--gmoonc-font-size-base, 16px)',
|
|
60
|
+
color: 'var(--gmoonc-color-text, #333)'
|
|
61
|
+
}}>
|
|
62
|
+
Loading...
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Route protection: redirect to login if not authenticated
|
|
69
|
+
if (!isAuthenticated) {
|
|
70
|
+
return <Navigate to="/login" replace />;
|
|
71
|
+
}
|
|
72
|
+
|
|
50
73
|
return (
|
|
51
74
|
<div className="gmoonc-root">
|
|
52
75
|
<GmooncShell
|