radix-cms 0.1.0 → 1.0.0

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.
Files changed (59) hide show
  1. package/.env.example +17 -0
  2. package/CHANGELOG.md +10 -0
  3. package/CONTRIBUTING.md +40 -0
  4. package/LICENSE +197 -0
  5. package/NOTICE +3 -0
  6. package/README.md +126 -56
  7. package/SECURITY.md +22 -0
  8. package/dist/app.js +80 -0
  9. package/dist/config/db.js +66 -0
  10. package/dist/init/dbInit.js +132 -0
  11. package/dist/init/dbInit2.js +234 -0
  12. package/dist/middleware/auth.js +48 -0
  13. package/dist/plugins/my-seo-plugin/index.js +29 -0
  14. package/dist/routes/admin.js +808 -0
  15. package/dist/routes/index.js +218 -0
  16. package/dist/sql/radix.sql +118 -0
  17. package/dist/types/plugin.js +10 -0
  18. package/dist/utils/PluginManager.js +112 -0
  19. package/dist/utils/settings.js +80 -0
  20. package/dist/utils/themeScanner.js +33 -0
  21. package/dist/utils/versionCheck.js +74 -0
  22. package/docs/CMS_CODE_GUIDE.md +372 -0
  23. package/docs/CMS_USER_GUIDE.md +344 -0
  24. package/docs/Publishing Guidelines.txt +14 -0
  25. package/package.json +62 -6
  26. package/public/css/admin.css +178 -0
  27. package/public/css/fallback.css +53 -0
  28. package/public/hello.html +1 -0
  29. package/views/admin/create-page.ejs +82 -0
  30. package/views/admin/dashboard.ejs +44 -0
  31. package/views/admin/edit-page.ejs +87 -0
  32. package/views/admin/edit-user.ejs +45 -0
  33. package/views/admin/login.ejs +39 -0
  34. package/views/admin/media.ejs +79 -0
  35. package/views/admin/pages.ejs +82 -0
  36. package/views/admin/partials/footer.ejs +4 -0
  37. package/views/admin/partials/header.ejs +57 -0
  38. package/views/admin/settings.ejs +91 -0
  39. package/views/admin/setup-admin.ejs +69 -0
  40. package/views/admin/setup-db.ejs +112 -0
  41. package/views/admin/themes.ejs +24 -0
  42. package/views/admin/updates.ejs +79 -0
  43. package/views/admin/users.ejs +107 -0
  44. package/views/defaults/404.ejs +20 -0
  45. package/views/defaults/500.ejs +20 -0
  46. package/views/layouts/admin-layout.ejs +18 -0
  47. package/views/pages/gallery.ejs +5 -0
  48. package/views/pages/hello.ejs +4 -0
  49. package/views/pages/services.ejs +3 -0
  50. package/views/themes/default/assets/css/style.css +199 -0
  51. package/views/themes/default/assets/js/main.js +8 -0
  52. package/views/themes/default/index.ejs +55 -0
  53. package/views/themes/landing/assets/css/style.css +6 -0
  54. package/views/themes/landing/assets/js/main.js +8 -0
  55. package/views/themes/landing/index.ejs +26 -0
  56. package/views/themes/test/default.ejs +0 -0
  57. package/views/themes/test/full-width.ejs +0 -0
  58. package/views/themes/test/landing-page.ejs +0 -0
  59. package/index.js +0 -15
@@ -0,0 +1,14 @@
1
+ For GitHub, create the repository, commit the source and documentation, tag v1.0.0, and create a GitHub release. Decide whether dist/ is committed; if not, configure GitHub Actions to build it before publishing to npm.
2
+
3
+ The npm package name radix-cms already exists at version 0.1.0, so verify that it is your package before publishing. Then use:
4
+
5
+ npm login
6
+ npm publish --access public
7
+
8
+ Before publishing, verify with:
9
+
10
+ npm run build
11
+ npm pack --dry-run
12
+
13
+ The dry-run must contain dist/app.js, views, required public assets, LICENSE, and README.md, but must not contain .env, node_modules, or user-uploaded files.
14
+
package/package.json CHANGED
@@ -1,11 +1,67 @@
1
1
  {
2
2
  "name": "radix-cms",
3
- "version": "0.1.0",
4
- "description": "Basic CMS for building custom web applications.",
5
- "main": "index.js",
3
+ "version": "1.0.0",
4
+ "main": "dist/app.js",
6
5
  "bin": {
7
- "radix-cms": "index.js"
6
+ "radix-cms": "dist/app.js"
7
+ },
8
+ "files": [
9
+ "dist/",
10
+ "views/",
11
+ "public/css/",
12
+ "public/hello.html",
13
+ ".env.example",
14
+ "LICENSE",
15
+ "NOTICE",
16
+ "README.md",
17
+ "CHANGELOG.md",
18
+ "CONTRIBUTING.md",
19
+ "SECURITY.md",
20
+ "docs/"
21
+ ],
22
+ "keywords": [],
23
+ "author": "Ultra Spark Software",
24
+ "license": "GPL-3.0-or-later",
25
+ "description": "Basic CMS for building custom web applications.",
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "scripts": {
30
+ "build": "npx tsc && node -e \"const fs=require('fs'),path=require('path'); const src=path.join(process.cwd(),'src','sql','radix.sql'); const destDir=path.join(process.cwd(),'dist','sql'); const dest=path.join(destDir,'radix.sql'); if(!fs.existsSync(src)){console.warn('copy-sql: src not found at',src); process.exit(0);} fs.mkdirSync(destDir,{recursive:true}); fs.copyFileSync(src,dest); console.log('copy-sql: copied', src, '->', dest);\"",
31
+ "start": "npx tsc && node dist/app.js",
32
+ "app": "node dist/app.js",
33
+ "dev": "npx tsx watch src/app.ts"
34
+ },
35
+ "devDependencies": {
36
+ "@types/adm-zip": "^0.5.8",
37
+ "@types/bcrypt": "^6.0.0",
38
+ "@types/ejs": "^3.1.5",
39
+ "@types/express": "^5.0.6",
40
+ "@types/express-session": "^1.19.0",
41
+ "@types/multer": "^2.2.0",
42
+ "@types/node": "^26.1.1",
43
+ "@types/sanitize-html": "^2.16.1",
44
+ "tsx": "^4.23.1",
45
+ "typescript": "^7.0.2"
46
+ },
47
+ "dependencies": {
48
+ "@types/nodemailer": "^8.0.1",
49
+ "adm-zip": "^0.6.0",
50
+ "bcrypt": "^6.0.0",
51
+ "dotenv": "^17.4.2",
52
+ "ejs": "^6.0.1",
53
+ "express": "^5.2.1",
54
+ "express-session": "^1.19.0",
55
+ "multer": "^2.2.0",
56
+ "mysql2": "^3.23.1",
57
+ "nodemailer": "^9.0.5",
58
+ "sanitize-html": "^2.17.6"
59
+ },
60
+ "allowScripts": {
61
+ "esbuild@0.28.1": true,
62
+ "bcrypt@6.0.0": true
8
63
  },
9
- "license": "MIT",
10
- "author": "Ultra Spark Software"
64
+ "overrides": {
65
+ "nanoid": "^3.3.18"
66
+ }
11
67
  }
@@ -0,0 +1,178 @@
1
+ /* Modern Admin Layout Shell */
2
+ :root {
3
+ --sidebar-width: 240px;
4
+ --header-height: 60px;
5
+ --bg-dark: #1e1e2d;
6
+ --bg-dark-hover: #2b2b40;
7
+ --accent-color: #3b82f6;
8
+ --text-light: #a2a3b7;
9
+ --text-white: #ffffff;
10
+ --bg-main: #f5f7fb;
11
+ --border-color: #e2e8f0;
12
+ }
13
+
14
+ * {
15
+ box-sizing: border-box;
16
+ margin: 0;
17
+ padding: 0;
18
+ }
19
+
20
+ body {
21
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
22
+ background-color: var(--bg-main);
23
+ color: #333;
24
+ display: flex;
25
+ height: 100vh;
26
+ overflow: hidden;
27
+ }
28
+
29
+ /* Sidebar Navigation */
30
+ .admin-sidebar {
31
+ width: var(--sidebar-width);
32
+ background-color: var(--bg-dark);
33
+ color: var(--text-light);
34
+ display: flex;
35
+ flex-direction: column;
36
+ flex-shrink: 0;
37
+ }
38
+
39
+ .admin-sidebar .brand {
40
+ height: var(--header-height);
41
+ display: flex;
42
+ align-items: center;
43
+ padding: 0 20px;
44
+ font-size: 1.2rem;
45
+ font-weight: bold;
46
+ color: var(--text-white);
47
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
48
+ }
49
+
50
+ .admin-sidebar .brand span {
51
+ color: var(--accent-color);
52
+ margin-left: 4px;
53
+ }
54
+
55
+ .admin-nav {
56
+ list-style: none;
57
+ padding: 15px 0;
58
+ flex-grow: 1;
59
+ }
60
+
61
+ .admin-nav li a {
62
+ display: flex;
63
+ align-items: center;
64
+ padding: 12px 20px;
65
+ color: var(--text-light);
66
+ text-decoration: none;
67
+ font-size: 0.95rem;
68
+ transition: all 0.2s ease;
69
+ }
70
+
71
+ .admin-nav li a:hover,
72
+ .admin-nav li.active a {
73
+ background-color: var(--bg-dark-hover);
74
+ color: var(--text-white);
75
+ border-left: 4px solid var(--accent-color);
76
+ }
77
+
78
+ .admin-nav li a .icon {
79
+ margin-right: 12px;
80
+ font-size: 1.1rem;
81
+ }
82
+
83
+ /* Main Content Area */
84
+ .admin-wrapper {
85
+ flex-grow: 1;
86
+ display: flex;
87
+ flex-direction: column;
88
+ overflow-y: auto;
89
+ }
90
+
91
+ /* Top Navigation Bar */
92
+ .admin-header {
93
+ height: var(--header-height);
94
+ background-color: #ffffff;
95
+ border-bottom: 1px solid var(--border-color);
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: space-between;
99
+ padding: 0 25px;
100
+ flex-shrink: 0;
101
+ }
102
+
103
+ .header-left {
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 15px;
107
+ }
108
+
109
+ .header-right {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 20px;
113
+ font-size: 0.9rem;
114
+ }
115
+
116
+ .user-badge {
117
+ background-color: #e0e7ff;
118
+ color: #3730a3;
119
+ padding: 4px 10px;
120
+ border-radius: 12px;
121
+ font-size: 0.8rem;
122
+ font-weight: 600;
123
+ }
124
+
125
+ /* Content Body */
126
+ .admin-content {
127
+ padding: 25px;
128
+ max-width: 1200px;
129
+ width: 100%;
130
+ margin: 0 auto;
131
+ }
132
+
133
+ /* Common UI Cards & Tables */
134
+ .card {
135
+ background: #ffffff;
136
+ border: 1px solid var(--border-color);
137
+ border-radius: 8px;
138
+ padding: 20px;
139
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
140
+ margin-bottom: 20px;
141
+ }
142
+
143
+ .data-table {
144
+ width: 100%;
145
+ border-collapse: collapse;
146
+ margin-top: 15px;
147
+ }
148
+
149
+ .data-table th,
150
+ .data-table td {
151
+ padding: 12px 15px;
152
+ text-align: left;
153
+ border-bottom: 1px solid var(--border-color);
154
+ }
155
+
156
+ .data-table th {
157
+ background-color: #f8fafc;
158
+ color: #64748b;
159
+ font-weight: 600;
160
+ font-size: 0.85rem;
161
+ text-transform: uppercase;
162
+ }
163
+
164
+ .btn {
165
+ display: inline-block;
166
+ padding: 8px 16px;
167
+ border-radius: 5px;
168
+ text-decoration: none;
169
+ font-size: 0.9rem;
170
+ font-weight: 500;
171
+ cursor: pointer;
172
+ border: none;
173
+ }
174
+
175
+ .btn-primary { background-color: var(--accent-color); color: #fff; }
176
+ .btn-primary:hover { background-color: #2563eb; }
177
+ .btn-danger { background-color: #ef4444; color: #fff; }
178
+ .btn-danger:hover { background-color: #dc2626; }
@@ -0,0 +1,53 @@
1
+ /* public/css/fallback.css */
2
+ body {
3
+ font-family: system-ui, -apple-system, sans-serif;
4
+ background: #f4f6f9;
5
+ color: #2d3748;
6
+ margin: 0;
7
+ padding: 40px;
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ min-height: 80vh;
12
+ }
13
+ .error-card {
14
+ background: white;
15
+ padding: 40px;
16
+ border-radius: 8px;
17
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
18
+ max-width: 500px;
19
+ width: 100%;
20
+ text-align: center;
21
+ }
22
+ .error-card h1 {
23
+ font-size: 4rem;
24
+ margin: 0;
25
+ }
26
+ .error-card.status-404 h1 {
27
+ color: #e53e3e;
28
+ }
29
+ .error-card.status-500 h1 {
30
+ color: #dd6b20;
31
+ }
32
+ .error-card h2 {
33
+ margin-top: 10px;
34
+ color: #4a5568;
35
+ }
36
+ .error-card p {
37
+ color: #718096;
38
+ line-height: 1.6;
39
+ }
40
+ .error-card nav {
41
+ margin-top: 25px;
42
+ border-top: 1px solid #e2e8f0;
43
+ padding-top: 20px;
44
+ }
45
+ .error-card nav a {
46
+ color: #3182ce;
47
+ text-decoration: none;
48
+ font-weight: 600;
49
+ margin: 0 10px;
50
+ }
51
+ .error-card nav a:hover {
52
+ text-decoration: underline;
53
+ }
@@ -0,0 +1 @@
1
+ hello world
@@ -0,0 +1,82 @@
1
+ <%- include('partials/header', { title: 'Create Page', activeNav: 'pages' }) %>
2
+
3
+ <div class="card" style="max-width: 900px; margin: 0 auto;">
4
+ <h1 style="font-size: 1.5rem; margin-bottom: 20px;">Create New Page</h1>
5
+
6
+ <form action="/admin/create" method="POST">
7
+ <div style="margin-bottom: 15px;">
8
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Title</label>
9
+ <input type="text" name="title" required style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
10
+ </div>
11
+
12
+ <div style="margin-bottom: 15px;">
13
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Slug</label>
14
+ <input type="text" name="slug" placeholder="e.g. my-page-title" required style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
15
+ </div>
16
+
17
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;">
18
+ <div>
19
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Status</label>
20
+ <select name="status" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
21
+ <option value="Draft">Draft</option>
22
+ <option value="Pending Review">Pending Review</option>
23
+ <option value="Published">Published</option>
24
+ <option value="Archived">Archived</option>
25
+ </select>
26
+ </div>
27
+
28
+ <div>
29
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Minimum Access Role</label>
30
+ <select name="min_role" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
31
+ <option value="Unregistered">Unregistered</option>
32
+ <option value="Registered">Registered</option>
33
+ <option value="Editor">Editor</option>
34
+ <option value="Manager">Manager</option>
35
+ <option value="Administrator">Administrator</option>
36
+ </select>
37
+ </div>
38
+ </div>
39
+
40
+ <%# Theme selector %>
41
+ <div>
42
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Theme / Template</label>
43
+ <select name="template_name" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
44
+ <% if (typeof templates !== 'undefined' && templates.length > 0) { %>
45
+ <% templates.forEach(tpl => { %>
46
+ <option value="<%= tpl %>" <%= tpl === 'default' ? 'selected' : '' %>>
47
+ <%= tpl.charAt(0).toUpperCase() + tpl.slice(1) %>
48
+ </option>
49
+ <% }) %>
50
+ <% } else { %>
51
+ <option value="default" selected>Default</option>
52
+ <% } %>
53
+ </select>
54
+ </div>
55
+
56
+ <div style="margin-bottom: 20px;">
57
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Content</label>
58
+ <textarea id="content" name="content" rows="12" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;"></textarea>
59
+ </div>
60
+
61
+ <div style="display: flex; gap: 10px;">
62
+ <button type="submit" class="btn btn-primary">Save Page</button>
63
+ <a href="/admin/pages" class="btn" style="background: #e2e8f0; color: #334155;">Cancel</a>
64
+ </div>
65
+ </form>
66
+ </div>
67
+
68
+ <!-- TinyMCE CDN & Initialization -->
69
+ <script src="https://cdn.jsdelivr.net/npm/tinymce@6/tinymce.min.js"></script>
70
+ <script>
71
+ tinymce.init({
72
+ selector: '#content',
73
+ height: 450,
74
+ menubar: true,
75
+ plugins: 'advlist autolink lists link image charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime media table code help wordcount',
76
+ toolbar: 'undo redo | blocks | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | image link code | help',
77
+ branding: false,
78
+ promotion: false
79
+ });
80
+ </script>
81
+
82
+ <%- include('partials/footer') %>
@@ -0,0 +1,44 @@
1
+ <%- include('partials/header', { title: 'Dashboard', activeNav: 'dashboard' }) %>
2
+
3
+ <h1 style="font-size: 1.5rem; margin-bottom: 20px;">System Overview</h1>
4
+
5
+ <!-- Analytics Cards Grid -->
6
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; margin-bottom: 25px;">
7
+
8
+ <div class="card" style="margin-bottom: 0;">
9
+ <h3 style="font-size: 0.85rem; color: #64748b; text-transform: uppercase;">Dynamic Pages</h3>
10
+ <p style="font-size: 2rem; font-weight: bold; margin-top: 8px; color: #1e293b;"><%= stats.totalPages %></p>
11
+ <small style="color: #64748b;"><%= stats.publishedPages %> Published / <%= stats.draftPages %> Drafts</small>
12
+ </div>
13
+
14
+ <div class="card" style="margin-bottom: 0;">
15
+ <h3 style="font-size: 0.85rem; color: #64748b; text-transform: uppercase;">Static EJS Views</h3>
16
+ <p style="font-size: 2rem; font-weight: bold; margin-top: 8px; color: #2563eb;"><%= stats.staticPagesCount %></p>
17
+ <small style="color: #64748b;">Located in <code>/views/pages</code></small>
18
+ </div>
19
+
20
+ <div class="card" style="margin-bottom: 0;">
21
+ <h3 style="font-size: 0.85rem; color: #64748b; text-transform: uppercase;">Media Library</h3>
22
+ <p style="font-size: 2rem; font-weight: bold; margin-top: 8px; color: #059669;"><%= stats.mediaCount %></p>
23
+ <small style="color: #64748b;">Uploaded files</small>
24
+ </div>
25
+
26
+ <div class="card" style="margin-bottom: 0;">
27
+ <h3 style="font-size: 0.85rem; color: #64748b; text-transform: uppercase;">User Accounts</h3>
28
+ <p style="font-size: 2rem; font-weight: bold; margin-top: 8px; color: #7c3aed;"><%= stats.totalUsers %></p>
29
+ <small style="color: #64748b;">Registered users & admins</small>
30
+ </div>
31
+
32
+ </div>
33
+
34
+ <!-- Quick Actions Card -->
35
+ <div class="card">
36
+ <h2 style="font-size: 1.1rem; margin-bottom: 12px;">Quick Management</h2>
37
+ <div style="display: flex; gap: 10px;">
38
+ <a href="/admin/pages" class="btn btn-primary">Manage Pages</a>
39
+ <a href="/admin/create" class="btn" style="background: #e2e8f0; color: #334155;">+ New Page</a>
40
+ <a href="/admin/media" class="btn" style="background: #e2e8f0; color: #334155;">Upload Media</a>
41
+ </div>
42
+ </div>
43
+
44
+ <%- include('partials/footer') %>
@@ -0,0 +1,87 @@
1
+ <%- include('partials/header', { title: 'Edit Page', activeNav: 'pages' }) %>
2
+
3
+ <div class="card" style="max-width: 900px; margin: 0 auto;">
4
+ <h1 style="font-size: 1.5rem; margin-bottom: 20px;">Edit Page</h1>
5
+
6
+ <form action="/admin/edit/<%= page.id %>" method="POST">
7
+ <div style="margin-bottom: 15px;">
8
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Title</label>
9
+ <input type="text" name="title" value="<%= page.title %>" required style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
10
+ </div>
11
+
12
+ <div style="margin-bottom: 15px;">
13
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Slug</label>
14
+ <input type="text" name="slug" value="<%= page.slug %>" required style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
15
+ </div>
16
+
17
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;">
18
+ <div>
19
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Status</label>
20
+ <select name="status" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
21
+ <option value="Draft" <%= page.status === 'Draft' ? 'selected' : '' %>>Draft</option>
22
+ <option value="Pending Review" <%= page.status === 'Pending Review' ? 'selected' : '' %>>Pending Review</option>
23
+ <option value="Published" <%= page.status === 'Published' ? 'selected' : '' %>>Published</option>
24
+ <option value="Archived" <%= page.status === 'Archived' ? 'selected' : '' %>>Archived</option>
25
+ </select>
26
+ </div>
27
+
28
+ <div>
29
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Minimum Access Role</label>
30
+ <select name="min_role" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
31
+ <option value="Unregistered" <%= page.min_role === 'Unregistered' ? 'selected' : '' %>>Unregistered</option>
32
+ <option value="Registered" <%= page.min_role === 'Registered' ? 'selected' : '' %>>Registered</option>
33
+ <option value="Editor" <%= page.min_role === 'Editor' ? 'selected' : '' %>>Editor</option>
34
+ <option value="Manager" <%= page.min_role === 'Manager' ? 'selected' : '' %>>Manager</option>
35
+ <option value="Administrator" <%= page.min_role === 'Administrator' ? 'selected' : '' %>>Administrator</option>
36
+ </select>
37
+ </div>
38
+ </div>
39
+
40
+ <%# Theme selector %>
41
+ <div>
42
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Theme / Template</label>
43
+ <select name="template_name" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
44
+ <%
45
+ // Fallback to 'default' if page.template_name is null or undefined
46
+ const currentTemplate = (page && page.template_name) ? String(page.template_name).toLowerCase() : 'default';
47
+ %>
48
+ <% if (typeof templates !== 'undefined' && Array.isArray(templates) && templates.length > 0) { %>
49
+ <% templates.forEach(tpl => { %>
50
+ <% const tplSlug = String(tpl).toLowerCase(); %>
51
+ <option value="<%= tplSlug %>" <%= currentTemplate === tplSlug ? 'selected' : '' %>>
52
+ <%= tplSlug.charAt(0).toUpperCase() + tplSlug.slice(1) %>
53
+ </option>
54
+ <% }) %>
55
+ <% } else { %>
56
+ <option value="default" selected>Default</option>
57
+ <% } %>
58
+ </select>
59
+ </div>
60
+
61
+ <div style="margin-bottom: 20px;">
62
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Content</label>
63
+ <textarea id="content" name="content" rows="12" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;"><%= page.content %></textarea>
64
+ </div>
65
+
66
+ <div style="display: flex; gap: 10px;">
67
+ <button type="submit" class="btn btn-primary">Update Page</button>
68
+ <a href="/admin/pages" class="btn" style="background: #e2e8f0; color: #334155;">Cancel</a>
69
+ </div>
70
+ </form>
71
+ </div>
72
+
73
+ <!-- TinyMCE CDN & Initialization -->
74
+ <script src="https://cdn.jsdelivr.net/npm/tinymce@6/tinymce.min.js"></script>
75
+ <script>
76
+ tinymce.init({
77
+ selector: '#content',
78
+ height: 450,
79
+ menubar: true,
80
+ plugins: 'advlist autolink lists link image charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime media table code help wordcount',
81
+ toolbar: 'undo redo | blocks | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | image link code | help',
82
+ branding: false,
83
+ promotion: false
84
+ });
85
+ </script>
86
+
87
+ <%- include('partials/footer') %>
@@ -0,0 +1,45 @@
1
+ <%- include('partials/header', { title: 'Edit User', activeNav: 'users' }) %>
2
+
3
+ <div class="card" style="max-width: 700px; margin: 0 auto;">
4
+ <h1 style="font-size: 1.5rem; margin-bottom: 20px;">Edit User</h1>
5
+
6
+ <form action="/admin/users/edit/<%= editUser.id %>" method="POST">
7
+ <div style="margin-bottom: 15px;">
8
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Full Name</label>
9
+ <input type="text" name="full_name" value="<%= editUser.full_name || '' %>" placeholder="John Doe" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
10
+ </div>
11
+
12
+ <div style="margin-bottom: 15px;">
13
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Username</label>
14
+ <input type="text" name="username" value="<%= editUser.username %>" required style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
15
+ </div>
16
+
17
+ <div style="margin-bottom: 15px;">
18
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Email (Optional)</label>
19
+ <input type="email" name="email" value="<%= editUser.email || '' %>" placeholder="john@example.com" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
20
+ </div>
21
+
22
+ <div style="margin-bottom: 15px;">
23
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">Role</label>
24
+ <select name="role" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
25
+ <option value="Administrator" <%= editUser.role === 'Administrator' ? 'selected' : '' %>>Administrator</option>
26
+ <option value="Manager" <%= editUser.role === 'Manager' ? 'selected' : '' %>>Manager</option>
27
+ <option value="Editor" <%= editUser.role === 'Editor' ? 'selected' : '' %>>Editor</option>
28
+ <option value="Registered" <%= editUser.role === 'Registered' ? 'selected' : '' %>>Registered</option>
29
+ <option value="Unregistered" <%= editUser.role === 'Unregistered' ? 'selected' : '' %>>Unregistered</option>
30
+ </select>
31
+ </div>
32
+
33
+ <div style="margin-bottom: 20px;">
34
+ <label style="display: block; font-weight: 600; margin-bottom: 5px;">New Password (Optional)</label>
35
+ <input type="password" name="password" placeholder="Leave blank to keep current password" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
36
+ </div>
37
+
38
+ <div style="display: flex; gap: 10px;">
39
+ <button type="submit" class="btn btn-primary">Update User</button>
40
+ <a href="/admin/users" class="btn" style="background: #e2e8f0; color: #334155;">Cancel</a>
41
+ </div>
42
+ </form>
43
+ </div>
44
+
45
+ <%- include('partials/footer') %>
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Radix Admin Login</title>
6
+ <style>
7
+ body { font-family: system-ui, sans-serif; background: #e2e8f0; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
8
+ .login-card { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); width: 100%; max-width: 400px; }
9
+ h1 { margin-top: 0; font-size: 1.75rem; text-align: center; color: #2d3748; }
10
+ .form-group { margin-bottom: 20px; }
11
+ label { display: block; font-weight: 600; margin-bottom: 6px; }
12
+ input[type="text"], input[type="password"] { width: 100%; padding: 10px; border: 1px solid #cbd5e0; border-radius: 4px; box-sizing: border-box; }
13
+ .btn-login { width: 100%; background: #3182ce; color: white; border: none; padding: 12px; border-radius: 4px; font-size: 1rem; font-weight: bold; cursor: pointer; }
14
+ .btn-login:hover { background: #2b6cb0; }
15
+ .error-msg { background: #fed7d7; color: #9b2c2c; padding: 10px; border-radius: 4px; margin-bottom: 20px; text-align: center; font-size: 0.9rem; }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <div class="login-card">
20
+ <h1>Radix Engine Gate</h1>
21
+
22
+ <% if (error) { %>
23
+ <div class="error-msg"><%= error %></div>
24
+ <% } %>
25
+
26
+ <form action="/admin/login" method="POST">
27
+ <div class="form-group">
28
+ <label for="username">Username</label>
29
+ <input type="text" id="username" name="username" required autocomplete="off">
30
+ </div>
31
+ <div class="form-group">
32
+ <label for="password">Password</label>
33
+ <input type="password" id="password" name="password" required>
34
+ </div>
35
+ <button type="submit" class="btn-login">Authenticate</button>
36
+ </form>
37
+ </div>
38
+ </body>
39
+ </html>