@elsapiens/cli 0.1.0 → 0.1.2
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 +115 -54
- package/dist/index.js +109 -36
- package/dist/index.js.map +1 -1
- package/dist/templates/app/app.ejs +295 -13
- package/dist/templates/app/env-example.ejs +3 -0
- package/dist/templates/app/eslint-config.ejs +47 -0
- package/dist/templates/app/help-topics.ejs +135 -0
- package/dist/templates/app/husky-pre-commit.ejs +8 -0
- package/dist/templates/app/index-css.ejs +536 -1
- package/dist/templates/app/main.ejs +81 -4
- package/dist/templates/app/package.ejs +28 -3
- package/dist/templates/app/page-dashboard.ejs +99 -60
- package/dist/templates/app/page-settings.ejs +268 -91
- package/dist/templates/app/postcss-config.ejs +6 -0
- package/dist/templates/app/services-setup.ejs +158 -0
- package/dist/templates/app/tailwind-config.ejs +105 -0
- package/dist/templates/app/test-setup.ejs +1 -0
- package/dist/templates/app/translations-common-en.ejs +23 -0
- package/dist/templates/app/translations-common-index.ejs +18 -0
- package/dist/templates/app/translations-common.ejs +94 -0
- package/dist/templates/app/translations-settings-en.ejs +49 -0
- package/dist/templates/app/translations-settings-index.ejs +18 -0
- package/dist/templates/app/tsconfig-node.ejs +10 -0
- package/dist/templates/app/vite-env.ejs +13 -0
- package/dist/templates/app/vitest-config.ejs +30 -0
- package/dist/templates/pages/list.ejs +636 -165
- package/dist/templates/pages/settings.ejs +208 -136
- package/package.json +4 -2
|
@@ -1,15 +1,92 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import ReactDOM from 'react-dom/client';
|
|
3
3
|
import { BrowserRouter } from 'react-router-dom';
|
|
4
|
+
import { HelpProvider, ThemeProvider, LocaleProvider, AuthProvider, useAuth } from '@elsapiens/providers';
|
|
5
|
+
import { AuthGuard } from '@elsapiens/shell';
|
|
4
6
|
import App from './App';
|
|
5
|
-
import '
|
|
6
|
-
import '
|
|
7
|
+
import { helpTopics } from './help-topics';
|
|
8
|
+
import { loadCommon } from './translations/common/index';
|
|
9
|
+
import { initializeServices } from './services';
|
|
7
10
|
import './index.css';
|
|
8
11
|
|
|
12
|
+
// Configuration from environment
|
|
13
|
+
const AUTH_URL = import.meta.env.VITE_AUTH_URL || 'https://auth.example.com';
|
|
14
|
+
const ROOT_DOMAIN = import.meta.env.VITE_ROOT_DOMAIN ? `.${import.meta.env.VITE_ROOT_DOMAIN}` : '.example.com';
|
|
15
|
+
|
|
16
|
+
// Initialize services
|
|
17
|
+
initializeServices();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Loading screen component
|
|
21
|
+
*/
|
|
22
|
+
function LoadingScreen() {
|
|
23
|
+
return (
|
|
24
|
+
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
25
|
+
<div className="text-center el-space-y-md">
|
|
26
|
+
<div className="w-12 h-12 mx-auto rounded-lg bg-primary-500 flex items-center justify-center text-white font-bold text-xl animate-pulse">
|
|
27
|
+
E
|
|
28
|
+
</div>
|
|
29
|
+
<p className="text-muted-foreground">Loading...</p>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Inner app wrapper that uses the auth context for AuthGuard
|
|
37
|
+
*
|
|
38
|
+
* In development mode, auth is bypassed for easier testing.
|
|
39
|
+
* In production, set VITE_REQUIRE_AUTH=true to enable auth checks.
|
|
40
|
+
*/
|
|
41
|
+
function AuthenticatedApp() {
|
|
42
|
+
const { isAuthenticated, isLoading, hasAllPermissions, hasAnyRole } = useAuth();
|
|
43
|
+
|
|
44
|
+
// Wait for auth to be determined before rendering AuthGuard
|
|
45
|
+
if (isLoading) {
|
|
46
|
+
return <LoadingScreen />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// In development, bypass auth by default unless VITE_REQUIRE_AUTH is set
|
|
50
|
+
const requireAuth = import.meta.env.VITE_REQUIRE_AUTH === 'true';
|
|
51
|
+
const isAuthed = requireAuth ? isAuthenticated : true;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<AuthGuard
|
|
55
|
+
authUrl={AUTH_URL}
|
|
56
|
+
returnUrlParam="return_url"
|
|
57
|
+
checkAuth={() => isAuthed}
|
|
58
|
+
checkPermissions={(perms) => hasAllPermissions(perms)}
|
|
59
|
+
checkRoles={(roles) => hasAnyRole(roles)}
|
|
60
|
+
loadingComponent={<LoadingScreen />}
|
|
61
|
+
>
|
|
62
|
+
<App />
|
|
63
|
+
</AuthGuard>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
9
67
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
10
68
|
<React.StrictMode>
|
|
11
|
-
<BrowserRouter>
|
|
12
|
-
<
|
|
69
|
+
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
|
|
70
|
+
<ThemeProvider defaultTheme="system" rootDomain={ROOT_DOMAIN}>
|
|
71
|
+
<AuthProvider
|
|
72
|
+
authUrl={AUTH_URL}
|
|
73
|
+
returnUrlParam="return_url"
|
|
74
|
+
>
|
|
75
|
+
<LocaleProvider initialLoader={loadCommon} rootDomain={ROOT_DOMAIN}>
|
|
76
|
+
<HelpProvider
|
|
77
|
+
initialTopics={helpTopics}
|
|
78
|
+
config={{
|
|
79
|
+
enabled: true,
|
|
80
|
+
showHelpIcons: true,
|
|
81
|
+
helpPanelPosition: 'modal',
|
|
82
|
+
searchEnabled: true,
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<AuthenticatedApp />
|
|
86
|
+
</HelpProvider>
|
|
87
|
+
</LocaleProvider>
|
|
88
|
+
</AuthProvider>
|
|
89
|
+
</ThemeProvider>
|
|
13
90
|
</BrowserRouter>
|
|
14
91
|
</React.StrictMode>
|
|
15
92
|
);
|
|
@@ -7,25 +7,50 @@
|
|
|
7
7
|
"dev": "vite",
|
|
8
8
|
"build": "tsc && vite build",
|
|
9
9
|
"preview": "vite preview",
|
|
10
|
-
"lint": "eslint .
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"lint:fix": "eslint . --fix",
|
|
12
|
+
"test": "vitest",
|
|
13
|
+
"test:run": "vitest run",
|
|
14
|
+
"test:coverage": "vitest run --coverage",
|
|
15
|
+
"precommit": "pnpm test:coverage && pnpm lint",
|
|
16
|
+
"prepare": "husky"
|
|
11
17
|
},
|
|
12
18
|
"dependencies": {
|
|
13
19
|
"@elsapiens/ui": "latest",
|
|
14
20
|
"@elsapiens/utils": "latest",
|
|
15
21
|
"@elsapiens/styles": "latest",
|
|
16
|
-
"
|
|
22
|
+
"@elsapiens/providers": "latest",
|
|
23
|
+
"@elsapiens/layout": "latest",
|
|
24
|
+
"@elsapiens/shell": "latest",
|
|
25
|
+
"@elsapiens/help": "latest",
|
|
26
|
+
"@elsapiens/hooks": "latest",
|
|
27
|
+
"@elsapiens/services": "latest",
|
|
28
|
+
"@elsapiens/sdk": "latest",
|
|
29
|
+
"lucide-react": "^0.303.0",
|
|
17
30
|
"react": "^18.2.0",
|
|
18
31
|
"react-dom": "^18.2.0",
|
|
19
32
|
"react-router-dom": "^6.21.0"
|
|
20
33
|
},
|
|
21
34
|
"devDependencies": {
|
|
35
|
+
"@eslint/js": "^9.0.0",
|
|
36
|
+
"@testing-library/react": "^14.0.0",
|
|
37
|
+
"@testing-library/jest-dom": "^6.0.0",
|
|
22
38
|
"@types/react": "^18.2.0",
|
|
23
39
|
"@types/react-dom": "^18.2.0",
|
|
24
40
|
"@vitejs/plugin-react": "^4.2.0",
|
|
41
|
+
"@vitest/coverage-v8": "^1.0.0",
|
|
25
42
|
"autoprefixer": "^10.4.16",
|
|
43
|
+
"eslint": "^9.0.0",
|
|
44
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
45
|
+
"eslint-plugin-react-refresh": "^0.4.0",
|
|
46
|
+
"globals": "^15.0.0",
|
|
47
|
+
"husky": "^9.0.0",
|
|
48
|
+
"jsdom": "^24.0.0",
|
|
26
49
|
"postcss": "^8.4.32",
|
|
27
50
|
"tailwindcss": "^3.4.0",
|
|
28
51
|
"typescript": "^5.3.0",
|
|
29
|
-
"
|
|
52
|
+
"typescript-eslint": "^8.0.0",
|
|
53
|
+
"vite": "^5.0.0",
|
|
54
|
+
"vitest": "^1.0.0"
|
|
30
55
|
}
|
|
31
56
|
}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
import {
|
|
2
3
|
Button,
|
|
3
4
|
Card,
|
|
4
5
|
CardHeader,
|
|
5
6
|
CardTitle,
|
|
6
7
|
CardContent,
|
|
8
|
+
Modal,
|
|
9
|
+
Input,
|
|
10
|
+
FormField,
|
|
7
11
|
} from '@elsapiens/ui';
|
|
12
|
+
import { PageHeaderWithBreadcrumbs } from '@elsapiens/layout';
|
|
8
13
|
import { Plus, TrendingUp, TrendingDown, Users, FileText, DollarSign } from 'lucide-react';
|
|
14
|
+
import { Link } from 'react-router-dom';
|
|
15
|
+
import { useLocale, usePageHeader } from '@elsapiens/providers';
|
|
9
16
|
|
|
10
17
|
interface StatCardProps {
|
|
11
18
|
title: string;
|
|
@@ -19,13 +26,13 @@ function StatCard({ title, value, change, changeType, icon: Icon }: StatCardProp
|
|
|
19
26
|
const isPositive = changeType === 'positive';
|
|
20
27
|
return (
|
|
21
28
|
<Card padding="none">
|
|
22
|
-
<CardContent className="p-
|
|
29
|
+
<CardContent className="el-p-lg">
|
|
23
30
|
<div className="flex items-start justify-between">
|
|
24
31
|
<div className="flex-1">
|
|
25
32
|
<p className="text-sm text-muted-foreground">{title}</p>
|
|
26
|
-
<p className="text-2xl font-bold text-foreground mt-
|
|
33
|
+
<p className="text-2xl font-bold text-foreground el-mt-xs">{value}</p>
|
|
27
34
|
{change && (
|
|
28
|
-
<p className={`text-sm mt-
|
|
35
|
+
<p className={`text-sm el-mt-xs flex items-center el-gap-xs ${isPositive ? 'text-success' : 'text-error'}`}>
|
|
29
36
|
{isPositive ? <TrendingUp className="w-4 h-4" /> : <TrendingDown className="w-4 h-4" />}
|
|
30
37
|
{change}
|
|
31
38
|
</p>
|
|
@@ -41,68 +48,100 @@ function StatCard({ title, value, change, changeType, icon: Icon }: StatCardProp
|
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
export default function Dashboard() {
|
|
51
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
52
|
+
const { t } = useLocale();
|
|
53
|
+
|
|
54
|
+
// Set page header info
|
|
55
|
+
usePageHeader({
|
|
56
|
+
title: 'Dashboard',
|
|
57
|
+
description: "Welcome back! Here's what's happening.",
|
|
58
|
+
breadcrumbs: [
|
|
59
|
+
{ label: t('nav.home'), href: '/', as: Link },
|
|
60
|
+
{ label: 'Dashboard' },
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
|
|
44
64
|
return (
|
|
45
|
-
<div className="
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
<div className="flex flex-col">
|
|
66
|
+
<PageHeaderWithBreadcrumbs variant="card" linkComponent={Link}>
|
|
67
|
+
<Button onClick={() => setIsModalOpen(true)} className="w-full sm:w-auto">
|
|
68
|
+
<Plus className="w-4 h-4 el-mr-sm" />
|
|
69
|
+
New Item
|
|
70
|
+
</Button>
|
|
71
|
+
</PageHeaderWithBreadcrumbs>
|
|
72
|
+
|
|
73
|
+
<div className="el-p-lg" style={{ overflowX: 'clip' }}>
|
|
74
|
+
{/* Stats Grid */}
|
|
75
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 el-gap-lg el-mb-xl">
|
|
76
|
+
<StatCard
|
|
77
|
+
title="Total Revenue"
|
|
78
|
+
value="$45,231"
|
|
79
|
+
change="+20.1% from last month"
|
|
80
|
+
changeType="positive"
|
|
81
|
+
icon={DollarSign}
|
|
82
|
+
/>
|
|
83
|
+
<StatCard
|
|
84
|
+
title="Documents"
|
|
85
|
+
value="2,345"
|
|
86
|
+
change="+15% from last month"
|
|
87
|
+
changeType="positive"
|
|
88
|
+
icon={FileText}
|
|
89
|
+
/>
|
|
90
|
+
<StatCard
|
|
91
|
+
title="Active Users"
|
|
92
|
+
value="1,234"
|
|
93
|
+
change="+5.2% from last month"
|
|
94
|
+
changeType="positive"
|
|
95
|
+
icon={Users}
|
|
96
|
+
/>
|
|
97
|
+
<StatCard
|
|
98
|
+
title="Bounce Rate"
|
|
99
|
+
value="24.5%"
|
|
100
|
+
change="-3.2% from last month"
|
|
101
|
+
changeType="negative"
|
|
102
|
+
icon={TrendingDown}
|
|
103
|
+
/>
|
|
57
104
|
</div>
|
|
58
|
-
</div>
|
|
59
105
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
icon={FileText}
|
|
75
|
-
/>
|
|
76
|
-
<StatCard
|
|
77
|
-
title="Active Users"
|
|
78
|
-
value="1,234"
|
|
79
|
-
change="+5.2% from last month"
|
|
80
|
-
changeType="positive"
|
|
81
|
-
icon={Users}
|
|
82
|
-
/>
|
|
83
|
-
<StatCard
|
|
84
|
-
title="Bounce Rate"
|
|
85
|
-
value="24.5%"
|
|
86
|
-
change="-3.2% from last month"
|
|
87
|
-
changeType="negative"
|
|
88
|
-
icon={TrendingDown}
|
|
89
|
-
/>
|
|
106
|
+
{/* Content Area */}
|
|
107
|
+
<div>
|
|
108
|
+
<Card padding="none">
|
|
109
|
+
<CardHeader className="el-p-lg">
|
|
110
|
+
<CardTitle>Getting Started</CardTitle>
|
|
111
|
+
</CardHeader>
|
|
112
|
+
<CardContent className="el-px-lg el-pb-lg">
|
|
113
|
+
<p className="text-muted-foreground">
|
|
114
|
+
Welcome to your new elSapiens project! Edit this page at{' '}
|
|
115
|
+
<code className="text-sm bg-muted el-px-xs el-py-xs rounded">src/pages/Dashboard.tsx</code>
|
|
116
|
+
</p>
|
|
117
|
+
</CardContent>
|
|
118
|
+
</Card>
|
|
119
|
+
</div>
|
|
90
120
|
</div>
|
|
91
121
|
|
|
92
|
-
{/*
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
{/* Modal Example */}
|
|
123
|
+
<Modal
|
|
124
|
+
isOpen={isModalOpen}
|
|
125
|
+
onClose={() => setIsModalOpen(false)}
|
|
126
|
+
title="Create New Item"
|
|
127
|
+
>
|
|
128
|
+
<div className="flex flex-col el-gap-field">
|
|
129
|
+
<FormField label="Item Name">
|
|
130
|
+
<Input placeholder="Enter item name..." />
|
|
131
|
+
</FormField>
|
|
132
|
+
<FormField label="Description">
|
|
133
|
+
<Input placeholder="Enter description..." />
|
|
134
|
+
</FormField>
|
|
135
|
+
<div className="flex justify-end el-gap-field el-pt-sm">
|
|
136
|
+
<Button variant="outline" onClick={() => setIsModalOpen(false)}>
|
|
137
|
+
Cancel
|
|
138
|
+
</Button>
|
|
139
|
+
<Button onClick={() => setIsModalOpen(false)}>
|
|
140
|
+
Create
|
|
141
|
+
</Button>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</Modal>
|
|
106
145
|
</div>
|
|
107
146
|
);
|
|
108
147
|
}
|