@gnar-engine/cli 1.0.4 → 1.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/bootstrap/deploy.localdev.yml +30 -3
- package/bootstrap/secrets.localdev.yml +15 -4
- package/bootstrap/services/control/src/config.js +4 -0
- package/bootstrap/services/page/Dockerfile +23 -0
- package/bootstrap/services/page/package.json +16 -0
- package/bootstrap/services/page/src/app.js +50 -0
- package/bootstrap/services/page/src/commands/block.handler.js +94 -0
- package/bootstrap/services/page/src/commands/page.handler.js +167 -0
- package/bootstrap/services/page/src/config.js +62 -0
- package/bootstrap/services/page/src/controllers/block.http.controller.js +87 -0
- package/bootstrap/services/page/src/controllers/message.controller.js +51 -0
- package/bootstrap/services/page/src/controllers/page.http.controller.js +89 -0
- package/bootstrap/services/page/src/policies/block.policy.js +50 -0
- package/bootstrap/services/page/src/policies/page.policy.js +49 -0
- package/bootstrap/services/page/src/schema/page.schema.js +139 -0
- package/bootstrap/services/page/src/services/block.service.js +83 -0
- package/bootstrap/services/page/src/services/page.service.js +83 -0
- package/bootstrap/services/portal/Dockerfile +20 -0
- package/bootstrap/services/portal/README.md +73 -0
- package/bootstrap/services/portal/index.html +13 -0
- package/bootstrap/services/portal/nginx.conf +5 -0
- package/bootstrap/services/portal/package.json +33 -0
- package/bootstrap/services/portal/public/vite.svg +1 -0
- package/bootstrap/services/portal/react-router.config.js +7 -0
- package/bootstrap/services/portal/src/App.jsx +16 -0
- package/bootstrap/services/portal/src/assets/gnar-engine-white-logo.svg +9 -0
- package/bootstrap/services/portal/src/assets/icon-agent.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-cog.svg +4 -0
- package/bootstrap/services/portal/src/assets/icon-delete.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-home.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-padlock.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-page.svg +6 -0
- package/bootstrap/services/portal/src/assets/icon-reports.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-user.svg +3 -0
- package/bootstrap/services/portal/src/assets/icon-users.svg +3 -0
- package/bootstrap/services/portal/src/assets/login-green-rad-back-1.jpg +0 -0
- package/bootstrap/services/portal/src/assets/react.svg +1 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.jsx +85 -0
- package/bootstrap/services/portal/src/components/CrudList/CrudList.less +59 -0
- package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.jsx +81 -0
- package/bootstrap/services/portal/src/components/CustomSelect/CustomSelect.less +0 -0
- package/bootstrap/services/portal/src/components/LoginForm/LoginForm.jsx +58 -0
- package/bootstrap/services/portal/src/components/PageBlockSwitch/PageBlockSwitch.jsx +129 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.jsx +33 -0
- package/bootstrap/services/portal/src/components/Sidebar/Sidebar.less +37 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.jsx +19 -0
- package/bootstrap/services/portal/src/components/Topbar/Topbar.less +22 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.jsx +33 -0
- package/bootstrap/services/portal/src/components/UserInfo/UserInfo.less +21 -0
- package/bootstrap/services/portal/src/css/style.css +711 -0
- package/bootstrap/services/portal/src/data/pages.data.js +10 -0
- package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.jsx +65 -0
- package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.less +102 -0
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.jsx +115 -0
- package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.less +43 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.jsx +124 -0
- package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.less +0 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.jsx +52 -0
- package/bootstrap/services/portal/src/elements/Repeater/Repeater.less +70 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.jsx +18 -0
- package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.less +37 -0
- package/bootstrap/services/portal/src/elements/SaveButton/SaveButton.jsx +45 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.jsx +63 -0
- package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.less +23 -0
- package/bootstrap/services/portal/src/elements/TextInput/TextInput.jsx +17 -0
- package/bootstrap/services/portal/src/layouts/Card/Card.jsx +15 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.jsx +29 -0
- package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.less +49 -0
- package/bootstrap/services/portal/src/main.jsx +51 -0
- package/bootstrap/services/portal/src/pages/BlockSinglePage/BlockSinglePage.jsx +277 -0
- package/bootstrap/services/portal/src/pages/BlocksPage/BlocksPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.jsx +11 -0
- package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.less +0 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.jsx +21 -0
- package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.less +51 -0
- package/bootstrap/services/portal/src/pages/PageSinglePage/PageSinglePage.jsx +338 -0
- package/bootstrap/services/portal/src/pages/PagesPage/PagesPage.jsx +23 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.jsx +9 -0
- package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.less +0 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.jsx +25 -0
- package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.less +0 -0
- package/bootstrap/services/portal/src/services/block.js +28 -0
- package/bootstrap/services/portal/src/services/client.js +67 -0
- package/bootstrap/services/portal/src/services/gravatar.js +14 -0
- package/bootstrap/services/portal/src/services/page.js +28 -0
- package/bootstrap/services/portal/src/services/storage.js +62 -0
- package/bootstrap/services/portal/src/services/user.js +41 -0
- package/bootstrap/services/portal/src/slices/authSlice.js +101 -0
- package/bootstrap/services/portal/src/store/configureStore.js +10 -0
- package/bootstrap/services/portal/src/style/cards.less +57 -0
- package/bootstrap/services/portal/src/style/global.less +204 -0
- package/bootstrap/services/portal/src/style/icons.less +21 -0
- package/bootstrap/services/portal/src/style/inputs.less +52 -0
- package/bootstrap/services/portal/src/style/main.less +28 -0
- package/bootstrap/services/portal/src/utils/utils.js +9 -0
- package/bootstrap/services/portal/vite.config.js +12 -0
- package/bootstrap/services/user/src/app.js +6 -1
- package/bootstrap/services/user/src/commands/user.handler.js +0 -3
- package/bootstrap/services/user/src/config.js +5 -1
- package/bootstrap/services/user/src/policies/user.policy.js +3 -1
- package/bootstrap/services/user/src/tests/commands/user.test.js +22 -0
- package/install-from-clone.sh +30 -0
- package/package.json +1 -1
- package/src/cli.js +8 -0
- package/src/dev/commands.js +10 -2
- package/src/dev/dev.service.js +147 -60
- package/src/provisioner/Dockerfile +27 -0
- package/src/provisioner/package.json +19 -0
- package/src/provisioner/src/app.js +56 -0
- package/src/provisioner/src/services/mongodb.js +58 -0
- package/src/provisioner/src/services/mysql.js +51 -0
- package/src/provisioner/src/services/secrets.js +84 -0
- package/src/scaffolder/commands.js +1 -1
- package/src/scaffolder/scaffolder.handler.js +40 -15
- package/templates/service/src/app.js.hbs +12 -1
- package/templates/service/src/commands/{{serviceName}}.handler.js.hbs +1 -1
- package/templates/service/src/mongodb.config.js.hbs +5 -1
- package/templates/service/src/mysql.config.js.hbs +4 -0
- package/bootstrap/services/user/src/tests/user.test.js +0 -126
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import Card from '../../layouts/Card/Card';
|
|
3
|
+
import SelectRepeater from '../../elements/SelectRepeater/SelectRepeater';
|
|
4
|
+
import SaveButton from '../../elements/SaveButton/SaveButton';
|
|
5
|
+
import TextInput from '../../elements/TextInput/TextInput';
|
|
6
|
+
import CustomSelect from '../../elements/CustomSelect/CustomSelect';
|
|
7
|
+
import PageBlockSwitch from '../../components/PageBlockSwitch/PageBlockSwitch';
|
|
8
|
+
import { pages } from '../../services/page.js';
|
|
9
|
+
import { blocks } from '../../services/block.js';
|
|
10
|
+
import { fieldTypes } from '../../data/pages.data.js';
|
|
11
|
+
import { useParams } from "react-router-dom";
|
|
12
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
13
|
+
|
|
14
|
+
function PageSinglePage() {
|
|
15
|
+
|
|
16
|
+
const { id } = useParams();
|
|
17
|
+
const [pageId, setPageId] = useState('');
|
|
18
|
+
const [pageDetails, setPageDetails] = useState({});
|
|
19
|
+
const [pageBlocks, setPageBlocks] = useState([]);
|
|
20
|
+
const [allBlocks, setAllBlocks] = useState([]);
|
|
21
|
+
const [errors, setErrors] = useState([]);
|
|
22
|
+
const [loading, setLoading] = useState(false);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
// fetch all blocks
|
|
26
|
+
(async () => {
|
|
27
|
+
try {
|
|
28
|
+
const blocksData = await blocks.getMany();
|
|
29
|
+
blocksData.blocks = blocksData.blocks.map(b => {
|
|
30
|
+
delete b.id;
|
|
31
|
+
return b;
|
|
32
|
+
})
|
|
33
|
+
setAllBlocks(blocksData.blocks || []);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('Error fetching blocks data:', error);
|
|
36
|
+
setErrors([error]);
|
|
37
|
+
}
|
|
38
|
+
})();
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
console.log('Current page blocks:', JSON.stringify(pageBlocks));
|
|
43
|
+
}, [pageBlocks]);
|
|
44
|
+
|
|
45
|
+
// fetch data if editing existing
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (id && id !== 'new') {
|
|
48
|
+
(async () => {
|
|
49
|
+
setLoading(true);
|
|
50
|
+
try {
|
|
51
|
+
let pageData = await pages.getSingle(id);
|
|
52
|
+
pageData = pageData.page;
|
|
53
|
+
setPageId(pageData.id);
|
|
54
|
+
setPageDetails({
|
|
55
|
+
pageName: pageData.name,
|
|
56
|
+
pageKey: pageData.key
|
|
57
|
+
});
|
|
58
|
+
setPageBlocks(pageData.blocks || []);
|
|
59
|
+
setErrors([]);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Error fetching page data:', error);
|
|
62
|
+
setErrors([error]);
|
|
63
|
+
}
|
|
64
|
+
setLoading(false);
|
|
65
|
+
})();
|
|
66
|
+
}
|
|
67
|
+
}, [id]);
|
|
68
|
+
|
|
69
|
+
const updatePageBlocks = (newBlock, parentInstanceId = null, fieldKey = null, remove = false) => {
|
|
70
|
+
|
|
71
|
+
if (!newBlock.instanceId) {
|
|
72
|
+
newBlock.instanceId = uuidv4();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let newPageBlocks = [];
|
|
76
|
+
let foundBlock = false;
|
|
77
|
+
const blocks = [...pageBlocks];
|
|
78
|
+
|
|
79
|
+
console.log('Remove', remove, newBlock);
|
|
80
|
+
|
|
81
|
+
const updateBlocksRecursively = (blocks) => {
|
|
82
|
+
blocks = blocks.map(block => {
|
|
83
|
+
// found the block, update it
|
|
84
|
+
if (newBlock.instanceId && block.instanceId === newBlock.instanceId) {
|
|
85
|
+
foundBlock = true;
|
|
86
|
+
if (remove) {
|
|
87
|
+
console.log('Removing block', newBlock);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return newBlock;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// check children recursively
|
|
94
|
+
if (block.fields && block.fields.length > 0) {
|
|
95
|
+
return {
|
|
96
|
+
...block,
|
|
97
|
+
fields: block.fields.map(field => {
|
|
98
|
+
if (field.type === 'repeater' && Array.isArray(field.value)) {
|
|
99
|
+
return {
|
|
100
|
+
...field,
|
|
101
|
+
value: updateBlocksRecursively(field.value)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return field;
|
|
105
|
+
})
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// otherwise return as is
|
|
110
|
+
return block;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// filter out any nulls (removed blocks)
|
|
114
|
+
return blocks.filter(b => b !== null);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
newPageBlocks = updateBlocksRecursively(blocks);
|
|
118
|
+
|
|
119
|
+
// add new block
|
|
120
|
+
if (!foundBlock) {
|
|
121
|
+
// add to parent
|
|
122
|
+
if (parentInstanceId && fieldKey) {
|
|
123
|
+
const addBlockToParentRecursively = (blocks) => {
|
|
124
|
+
return blocks.map(block => {
|
|
125
|
+
// found the parent block, add to its fields
|
|
126
|
+
if (block.instanceId === parentInstanceId) {
|
|
127
|
+
console.log('adding to parent instance ', block.instanceId, 'with', newBlock);
|
|
128
|
+
return {
|
|
129
|
+
...block,
|
|
130
|
+
fields: block.fields.map(field => {
|
|
131
|
+
console.log('checking field', field, fieldKey, field.repeaterType)
|
|
132
|
+
if (field.repeaterType && field.repeaterType === fieldKey) {
|
|
133
|
+
if (!Array.isArray(field.value)) {
|
|
134
|
+
field.value = [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log('WOOP');
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
...field,
|
|
141
|
+
value: [...field.value, newBlock]
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return field;
|
|
145
|
+
})
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// check children recursively
|
|
150
|
+
if (block.fields && block.fields.length > 0) {
|
|
151
|
+
return {
|
|
152
|
+
...block,
|
|
153
|
+
fields: block.fields.map(field => {
|
|
154
|
+
if (field.type === 'repeater' && Array.isArray(field.value)) {
|
|
155
|
+
return {
|
|
156
|
+
...field,
|
|
157
|
+
value: addBlockToParentRecursively(field.value)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return field;
|
|
161
|
+
})
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// otherwise return as is
|
|
166
|
+
return block;
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
newPageBlocks = addBlockToParentRecursively(newPageBlocks);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// otherwise top level
|
|
174
|
+
else {
|
|
175
|
+
newPageBlocks.push(newBlock);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
setPageBlocks(newPageBlocks);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const validateField = (e) => {
|
|
183
|
+
const value = e.target.value;
|
|
184
|
+
setErrors([]);
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const save = async () => {
|
|
189
|
+
setLoading(true);
|
|
190
|
+
console.log('Saving...', { pageId });
|
|
191
|
+
|
|
192
|
+
// create new
|
|
193
|
+
if (!pageId) {
|
|
194
|
+
try {
|
|
195
|
+
await pages.create({
|
|
196
|
+
name: pageDetails.pageName,
|
|
197
|
+
key: pageDetails.pageKey,
|
|
198
|
+
blocks: pageBlocks
|
|
199
|
+
})
|
|
200
|
+
setErrors([]);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error('Error creating page:', error);
|
|
203
|
+
setErrors([error]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// update existing
|
|
208
|
+
else {
|
|
209
|
+
try {
|
|
210
|
+
await pages.update(pageId, {
|
|
211
|
+
name: pageDetails.pageName,
|
|
212
|
+
key: pageDetails.pageKey,
|
|
213
|
+
blocks: pageBlocks
|
|
214
|
+
});
|
|
215
|
+
setErrors([]);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error creating page:', error);
|
|
218
|
+
setErrors([error]);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
setLoading(false);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<div className="single-crud-page">
|
|
227
|
+
<h1>Create / Update Page</h1>
|
|
228
|
+
|
|
229
|
+
<div className="flex-row top-bar">
|
|
230
|
+
<p className="single-crud-id">{pageId || 'Creating new page...'}</p>
|
|
231
|
+
<div className="button-group">
|
|
232
|
+
<button onClick={() => window.history.back()} className="secondary-btn">Back</button>
|
|
233
|
+
<button onClick={save}>Save</button>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div className="card-columns">
|
|
238
|
+
<div className="col-66">
|
|
239
|
+
<p className="instruction">Add blocks below, then add your content and save.</p>
|
|
240
|
+
|
|
241
|
+
{allBlocks &&
|
|
242
|
+
<SelectRepeater
|
|
243
|
+
items={pageBlocks}updatePageBlocks
|
|
244
|
+
setItem={(item, index, remove = false) => {
|
|
245
|
+
updatePageBlocks({
|
|
246
|
+
...item,
|
|
247
|
+
fields: item.fields.map(f => ({ ...f }))
|
|
248
|
+
}, null, null, remove);
|
|
249
|
+
}}
|
|
250
|
+
selectLabel="Add Block"
|
|
251
|
+
selectText="Select a block to add"
|
|
252
|
+
selectOptions={allBlocks}
|
|
253
|
+
selectLabelKey="name"
|
|
254
|
+
renderRow= {(item, index, updateItem) => (
|
|
255
|
+
<Card
|
|
256
|
+
title={`Block - ${item.name}`}
|
|
257
|
+
>
|
|
258
|
+
<PageBlockSwitch
|
|
259
|
+
block={item}
|
|
260
|
+
pageBlocks={pageBlocks}
|
|
261
|
+
updatePageBlocks={updatePageBlocks}
|
|
262
|
+
blockIndex={index}
|
|
263
|
+
allBlocks={allBlocks}
|
|
264
|
+
parentBlockInstanceId={null}
|
|
265
|
+
/>
|
|
266
|
+
</Card>
|
|
267
|
+
)}
|
|
268
|
+
/>
|
|
269
|
+
}
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<div className="col-33">
|
|
273
|
+
<Card
|
|
274
|
+
title="Page details"
|
|
275
|
+
>
|
|
276
|
+
<div className="flex-col">
|
|
277
|
+
<TextInput
|
|
278
|
+
label="Page Name"
|
|
279
|
+
placeholder="Enter page Name"
|
|
280
|
+
value={pageDetails.pageName || ''}
|
|
281
|
+
onChange={(e) => {
|
|
282
|
+
validateField(e)
|
|
283
|
+
setPageDetails({
|
|
284
|
+
...pageDetails,
|
|
285
|
+
pageName: e.target.value
|
|
286
|
+
})
|
|
287
|
+
}}
|
|
288
|
+
errorMessage="Invalid page name"
|
|
289
|
+
isValid={true}
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
<TextInput
|
|
293
|
+
label="Page Key"
|
|
294
|
+
placeholder="Enter page key"
|
|
295
|
+
value={pageDetails.pageKey || ''}
|
|
296
|
+
onChange={(e) => {
|
|
297
|
+
validateField(e)
|
|
298
|
+
setPageDetails({
|
|
299
|
+
...pageDetails,
|
|
300
|
+
pageKey: e.target.value
|
|
301
|
+
})
|
|
302
|
+
}}
|
|
303
|
+
errorMessage="Invalid page key"
|
|
304
|
+
isValid={true}
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
307
|
+
</Card>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div className="flex-row bottom-bar">
|
|
312
|
+
<div className="button-group">
|
|
313
|
+
<SaveButton
|
|
314
|
+
onClick={save}
|
|
315
|
+
itemName="Page"
|
|
316
|
+
loading={loading}
|
|
317
|
+
error={errors.length > 0}
|
|
318
|
+
isNew={!pageId}
|
|
319
|
+
/>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<div>
|
|
324
|
+
{errors.length > 0 && (
|
|
325
|
+
<div className="error-messages">
|
|
326
|
+
<ul>
|
|
327
|
+
{errors.map((error, index) => (
|
|
328
|
+
<li key={index}>{error.message}</li>
|
|
329
|
+
))}
|
|
330
|
+
</ul>
|
|
331
|
+
</div>
|
|
332
|
+
)}
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export default PageSinglePage;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import CrudList from '../../components/CrudList/CrudList';
|
|
2
|
+
import { pages } from '../../services/page.js';
|
|
3
|
+
|
|
4
|
+
function PagesPage() {
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<h1>Manage Pages</h1>
|
|
9
|
+
<CrudList
|
|
10
|
+
entityKey="pages"
|
|
11
|
+
fetchData={pages.getMany}
|
|
12
|
+
entitySingleName="Page"
|
|
13
|
+
entityPluralName="Pages"
|
|
14
|
+
columns={[
|
|
15
|
+
{ key: 'id', label: 'ID' },
|
|
16
|
+
{ key: 'name', label: 'Page name' }
|
|
17
|
+
]}
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default PagesPage;
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import CrudList from '../../components/CrudList/CrudList';
|
|
2
|
+
import { user } from '../../services/user.js';
|
|
3
|
+
|
|
4
|
+
function UsersPage() {
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<h1>Manage Users</h1>
|
|
9
|
+
<CrudList
|
|
10
|
+
entityKey="users"
|
|
11
|
+
fetchData={user.getMany}
|
|
12
|
+
entitySingleName="User"
|
|
13
|
+
entityPluralName="Users"
|
|
14
|
+
columns={[
|
|
15
|
+
{ key: 'id', label: 'ID' },
|
|
16
|
+
{ key: 'username', label: 'Username' },
|
|
17
|
+
{ key: 'email', label: 'Email' },
|
|
18
|
+
{ key: 'role', label: 'Role' }
|
|
19
|
+
]}
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default UsersPage;
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import client from './client.js';
|
|
2
|
+
|
|
3
|
+
export const blocks = {
|
|
4
|
+
getMany: async () => {
|
|
5
|
+
const { data } = await client.get('/blocks/');
|
|
6
|
+
return data;
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
getSingle: async ( id ) => {
|
|
10
|
+
const { data } = await client.get(`/blocks/${id}`);
|
|
11
|
+
return data;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
create: async ( block ) => {
|
|
15
|
+
const { data } = await client.post('/blocks/', { block });
|
|
16
|
+
return data;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
update: async ( id, block ) => {
|
|
20
|
+
const { data } = await client.post(`/blocks/${id}`, { block });
|
|
21
|
+
return data;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
delete: async ( id ) => {
|
|
25
|
+
await client.delete(`/users/${id}`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
import { getAuthToken, setAuthToken, removeAuthToken, removeAuthUser } from './storage.js';
|
|
4
|
+
|
|
5
|
+
// Determine the correct API URL based on the environment
|
|
6
|
+
const baseApiUrl = 'http://localhost';
|
|
7
|
+
|
|
8
|
+
const client = axios.create({
|
|
9
|
+
baseURL: baseApiUrl,
|
|
10
|
+
withCredentials: true,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Attach authorisation header to requests
|
|
14
|
+
client.interceptors.request.use(
|
|
15
|
+
(config) => {
|
|
16
|
+
// Authorization header
|
|
17
|
+
let authToken = getAuthToken();
|
|
18
|
+
|
|
19
|
+
if (authToken) {
|
|
20
|
+
config.headers['Authorization'] = `Bearer ${authToken}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return config;
|
|
24
|
+
},
|
|
25
|
+
(error) => {
|
|
26
|
+
return Promise.reject(error);
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
client.interceptors.response.use(
|
|
31
|
+
(response) => {
|
|
32
|
+
// update auth token if new one provided
|
|
33
|
+
const newAuthToken = response.headers['authorization'];
|
|
34
|
+
|
|
35
|
+
if (newAuthToken) {
|
|
36
|
+
const token = newAuthToken.split(' ')[1];
|
|
37
|
+
setAuthToken(token);
|
|
38
|
+
console.log('refreshed token');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return response;
|
|
42
|
+
},
|
|
43
|
+
(error) => {
|
|
44
|
+
// Log out if 401
|
|
45
|
+
if (error.response.status === 401) {
|
|
46
|
+
|
|
47
|
+
// remove auth token and user from storage
|
|
48
|
+
removeAuthToken();
|
|
49
|
+
removeAuthUser();
|
|
50
|
+
|
|
51
|
+
// redirect to login page
|
|
52
|
+
window.location.href = '/portal/login'
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (error.response.data.message) {
|
|
57
|
+
return Promise.reject({
|
|
58
|
+
message: error.response.data.message
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Promise.reject(error);
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
export default client;
|
|
67
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import CryptoJS from "crypto-js";
|
|
2
|
+
|
|
3
|
+
export function getGravatarUrl(email, options) {
|
|
4
|
+
const {
|
|
5
|
+
size = 200,
|
|
6
|
+
defaultImage = "mp",
|
|
7
|
+
rating = "g"
|
|
8
|
+
} = options;
|
|
9
|
+
|
|
10
|
+
const normalized = email.trim().toLowerCase();
|
|
11
|
+
const hash = CryptoJS.MD5(normalized).toString(CryptoJS.enc.Hex);
|
|
12
|
+
|
|
13
|
+
return `https://www.gravatar.com/avatar/${hash}?s=${size}&d=${defaultImage}&r=${rating}`;
|
|
14
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import client from './client.js';
|
|
2
|
+
|
|
3
|
+
export const pages = {
|
|
4
|
+
getMany: async () => {
|
|
5
|
+
const { data } = await client.get('/pages/');
|
|
6
|
+
return data;
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
getSingle: async ( id ) => {
|
|
10
|
+
const { data } = await client.get(`/pages/${id}`);
|
|
11
|
+
return data;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
create: async ( page ) => {
|
|
15
|
+
const { data } = await client.post('/pages/', { page });
|
|
16
|
+
return data;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
update: async ( id, page ) => {
|
|
20
|
+
const { data } = await client.post(`/pages/${id}`, { page });
|
|
21
|
+
return data;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
delete: async ( id ) => {
|
|
25
|
+
await client.delete(`/pages/${id}`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth token
|
|
3
|
+
*/
|
|
4
|
+
export function getAuthToken() {
|
|
5
|
+
if (typeof window === 'undefined') return null;
|
|
6
|
+
try {
|
|
7
|
+
return localStorage.getItem('GE_AUTH_TOKEN');
|
|
8
|
+
} catch (error) {
|
|
9
|
+
console.error('Error getting auth token:', error);
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setAuthToken(authToken) {
|
|
15
|
+
if (typeof window === 'undefined') return;
|
|
16
|
+
try {
|
|
17
|
+
localStorage.setItem('GE_AUTH_TOKEN', authToken);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error('Error setting auth token:', error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function removeAuthToken() {
|
|
24
|
+
if (typeof window === 'undefined') return;
|
|
25
|
+
try {
|
|
26
|
+
localStorage.removeItem('GE_AUTH_TOKEN');
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error removing auth token:', error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Auth user
|
|
34
|
+
*/
|
|
35
|
+
export function getAuthUser() {
|
|
36
|
+
if (typeof window === 'undefined') return null;
|
|
37
|
+
try {
|
|
38
|
+
return localStorage.getItem('GE_AUTH_USER');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error getting auth user:', error);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function setAuthUser(authUser) {
|
|
46
|
+
if (typeof window === 'undefined') return;
|
|
47
|
+
try {
|
|
48
|
+
localStorage.setItem('GE_AUTH_USER', authUser);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Error setting auth user:', error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function removeAuthUser() {
|
|
55
|
+
if (typeof window === 'undefined') return;
|
|
56
|
+
try {
|
|
57
|
+
localStorage.removeItem('GE_AUTH_USER');
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('Error removing auth user:', error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import client from './client.js';
|
|
2
|
+
|
|
3
|
+
export const user = {
|
|
4
|
+
authenticate: async ({ username, password }) => {
|
|
5
|
+
const { data } = await client.post('/authenticate/', { username, password });
|
|
6
|
+
return data;
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
getMany: async () => {
|
|
10
|
+
const { data } = await client.get('/users/');
|
|
11
|
+
return data;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
getUser: async ({ userId }) => {
|
|
15
|
+
const { data } = await client.get(`/users/${userId}`);
|
|
16
|
+
return data;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
createUser: async ({ user }) => {
|
|
20
|
+
const { data } = await client.post('/users/', { user });
|
|
21
|
+
return data;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
update: async ({ id, user }) => {
|
|
25
|
+
const { data } = await client.post(`/users/${id}`, { user });
|
|
26
|
+
return data;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
delete: async ({ userId }) => {
|
|
30
|
+
await client.delete(`/users/${userId}`);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
sendPasswordReset: async ({ email }) => {
|
|
34
|
+
await client.post('/users/request-password-reset', { email });
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
changePassword: async ({ email, token, password }) => {
|
|
38
|
+
await client.post('/users/change-password', { email, token, password });
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|