@gnar-engine/cli 1.0.3 → 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.
Files changed (259) hide show
  1. package/bootstrap/deploy.localdev.yml +44 -30
  2. package/bootstrap/secrets.localdev.yml +20 -14
  3. package/bootstrap/services/control/src/config.js +4 -0
  4. package/bootstrap/services/{agent → page}/Dockerfile +3 -3
  5. package/bootstrap/services/page/package.json +16 -0
  6. package/bootstrap/services/page/src/app.js +50 -0
  7. package/bootstrap/services/page/src/commands/block.handler.js +94 -0
  8. package/bootstrap/services/page/src/commands/page.handler.js +167 -0
  9. package/bootstrap/services/page/src/config.js +62 -0
  10. package/bootstrap/services/page/src/controllers/block.http.controller.js +87 -0
  11. package/bootstrap/services/page/src/controllers/message.controller.js +51 -0
  12. package/bootstrap/services/page/src/controllers/page.http.controller.js +89 -0
  13. package/bootstrap/services/page/src/policies/block.policy.js +50 -0
  14. package/bootstrap/services/page/src/policies/page.policy.js +49 -0
  15. package/bootstrap/services/page/src/schema/page.schema.js +139 -0
  16. package/bootstrap/services/page/src/services/block.service.js +83 -0
  17. package/bootstrap/services/page/src/services/page.service.js +83 -0
  18. package/bootstrap/services/portal/Dockerfile +7 -10
  19. package/bootstrap/services/portal/README.md +66 -15
  20. package/bootstrap/services/portal/index.html +13 -0
  21. package/bootstrap/services/portal/nginx.conf +5 -12
  22. package/bootstrap/services/portal/package.json +27 -53
  23. package/bootstrap/services/portal/public/vite.svg +1 -0
  24. package/bootstrap/services/portal/react-router.config.js +7 -0
  25. package/bootstrap/services/portal/src/App.jsx +16 -0
  26. package/bootstrap/services/portal/src/assets/gnar-engine-white-logo.svg +9 -0
  27. package/bootstrap/services/portal/src/assets/icon-agent.svg +6 -0
  28. package/bootstrap/services/portal/src/assets/icon-cog.svg +4 -0
  29. package/bootstrap/services/portal/src/assets/icon-delete.svg +3 -0
  30. package/bootstrap/services/portal/src/assets/icon-home.svg +3 -0
  31. package/bootstrap/services/portal/src/assets/icon-padlock.svg +3 -0
  32. package/bootstrap/services/portal/src/assets/icon-page.svg +6 -0
  33. package/bootstrap/services/portal/src/assets/icon-reports.svg +3 -0
  34. package/bootstrap/services/portal/src/assets/icon-user.svg +3 -0
  35. package/bootstrap/services/portal/src/assets/icon-users.svg +3 -0
  36. package/bootstrap/services/portal/src/assets/login-green-rad-back-1.jpg +0 -0
  37. package/bootstrap/services/portal/src/assets/react.svg +1 -0
  38. package/bootstrap/services/portal/src/components/CrudList/CrudList.jsx +85 -0
  39. package/bootstrap/services/portal/src/components/CrudList/CrudList.less +59 -0
  40. package/bootstrap/services/portal/src/{ui/customSelect → components/CustomSelect}/CustomSelect.jsx +21 -3
  41. package/bootstrap/services/portal/src/components/LoginForm/LoginForm.jsx +58 -0
  42. package/bootstrap/services/portal/src/components/PageBlockSwitch/PageBlockSwitch.jsx +129 -0
  43. package/bootstrap/services/portal/src/components/Sidebar/Sidebar.jsx +33 -0
  44. package/bootstrap/services/portal/src/components/Sidebar/Sidebar.less +37 -0
  45. package/bootstrap/services/portal/src/components/Topbar/Topbar.jsx +19 -0
  46. package/bootstrap/services/portal/src/components/Topbar/Topbar.less +22 -0
  47. package/bootstrap/services/portal/src/components/UserInfo/UserInfo.jsx +33 -0
  48. package/bootstrap/services/portal/src/components/UserInfo/UserInfo.less +21 -0
  49. package/bootstrap/services/portal/src/css/style.css +446 -742
  50. package/bootstrap/services/portal/src/data/pages.data.js +10 -0
  51. package/bootstrap/services/portal/src/elements/CustomSelect/CustomSelect.jsx +65 -0
  52. package/bootstrap/services/portal/src/{ui/customSelect/customSelect.less → elements/CustomSelect/CustomSelect.less} +17 -7
  53. package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.jsx +115 -0
  54. package/bootstrap/services/portal/src/elements/ImageInput/ImageInput.less +43 -0
  55. package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.jsx +124 -0
  56. package/bootstrap/services/portal/src/elements/ImageMultiInput/ImageMultiInput.less +0 -0
  57. package/bootstrap/services/portal/src/elements/Repeater/Repeater.jsx +52 -0
  58. package/bootstrap/services/portal/src/elements/Repeater/Repeater.less +70 -0
  59. package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.jsx +18 -0
  60. package/bootstrap/services/portal/src/elements/RichTextInput/RichTextInput.less +37 -0
  61. package/bootstrap/services/portal/src/elements/SaveButton/SaveButton.jsx +45 -0
  62. package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.jsx +63 -0
  63. package/bootstrap/services/portal/src/elements/SelectRepeater/SelectRepeater.less +23 -0
  64. package/bootstrap/services/portal/src/elements/TextInput/TextInput.jsx +17 -0
  65. package/bootstrap/services/portal/src/layouts/Card/Card.jsx +15 -0
  66. package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.jsx +29 -0
  67. package/bootstrap/services/portal/src/layouts/PortalLayout/PortalLayout.less +49 -0
  68. package/bootstrap/services/portal/src/main.jsx +51 -0
  69. package/bootstrap/services/portal/src/pages/BlockSinglePage/BlockSinglePage.jsx +277 -0
  70. package/bootstrap/services/portal/src/pages/BlocksPage/BlocksPage.jsx +23 -0
  71. package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.jsx +11 -0
  72. package/bootstrap/services/portal/src/pages/DashboardPage/DashboardPage.less +0 -0
  73. package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.jsx +21 -0
  74. package/bootstrap/services/portal/src/pages/LoginPage/LoginPage.less +51 -0
  75. package/bootstrap/services/portal/src/pages/PageSinglePage/PageSinglePage.jsx +338 -0
  76. package/bootstrap/services/portal/src/pages/PagesPage/PagesPage.jsx +23 -0
  77. package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.jsx +9 -0
  78. package/bootstrap/services/portal/src/pages/UserSinglePage/UserSinglePage.less +0 -0
  79. package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.jsx +25 -0
  80. package/bootstrap/services/portal/src/pages/UsersPage/UsersPage.less +0 -0
  81. package/bootstrap/services/portal/src/services/block.js +28 -0
  82. package/bootstrap/services/portal/src/services/client.js +67 -0
  83. package/bootstrap/services/portal/src/services/gravatar.js +14 -0
  84. package/bootstrap/services/portal/src/services/page.js +28 -0
  85. package/bootstrap/services/portal/src/services/storage.js +62 -0
  86. package/bootstrap/services/portal/src/services/user.js +41 -0
  87. package/bootstrap/services/portal/src/slices/authSlice.js +44 -14
  88. package/bootstrap/services/portal/src/store/configureStore.js +1 -3
  89. package/bootstrap/services/portal/src/style/cards.less +57 -0
  90. package/bootstrap/services/portal/src/{styles → style}/global.less +90 -45
  91. package/bootstrap/services/portal/src/style/icons.less +21 -0
  92. package/bootstrap/services/portal/src/style/inputs.less +52 -0
  93. package/bootstrap/services/portal/src/style/main.less +28 -0
  94. package/bootstrap/services/portal/src/utils/utils.js +9 -0
  95. package/bootstrap/services/portal/vite.config.js +12 -0
  96. package/bootstrap/services/user/Dockerfile +1 -1
  97. package/bootstrap/services/user/src/app.js +6 -1
  98. package/bootstrap/services/user/src/commands/user.handler.js +0 -3
  99. package/bootstrap/services/user/src/config.js +5 -1
  100. package/bootstrap/services/user/src/policies/user.policy.js +3 -1
  101. package/bootstrap/services/user/src/tests/commands/user.test.js +22 -0
  102. package/install-from-clone.sh +30 -0
  103. package/package.json +1 -1
  104. package/src/cli.js +8 -0
  105. package/src/config.js +2 -1
  106. package/src/dev/commands.js +10 -2
  107. package/src/dev/dev.service.js +167 -52
  108. package/src/provisioner/Dockerfile +27 -0
  109. package/src/provisioner/package.json +19 -0
  110. package/src/provisioner/src/app.js +56 -0
  111. package/src/provisioner/src/services/mongodb.js +58 -0
  112. package/src/provisioner/src/services/mysql.js +51 -0
  113. package/src/provisioner/src/services/secrets.js +84 -0
  114. package/src/scaffolder/commands.js +12 -5
  115. package/src/scaffolder/scaffolder.handler.js +256 -58
  116. package/templates/service/Dockerfile.hbs +4 -1
  117. package/templates/service/package.json.hbs +14 -16
  118. package/templates/service/src/app.js.hbs +53 -0
  119. package/templates/service/{commands → src/commands}/{{serviceName}}.handler.js.hbs +2 -3
  120. package/templates/service/src/mongodb.config.js.hbs +49 -0
  121. package/templates/service/{config.js.hbs → src/mysql.config.js.hbs} +10 -0
  122. package/{bootstrap/services/agent/src/schema/Agent.schema.js → templates/service/src/schema/{{serviceName}}.schema.js.hbs} +3 -3
  123. package/templates/service/src/services/mongodb.{{serviceName}}.service.js.hbs +70 -0
  124. package/bootstrap/services/agent/notes.md +0 -28
  125. package/bootstrap/services/agent/package.json +0 -16
  126. package/bootstrap/services/agent/src/app.js +0 -52
  127. package/bootstrap/services/agent/src/commands/agent.handler.js +0 -104
  128. package/bootstrap/services/agent/src/config.js +0 -52
  129. package/bootstrap/services/agent/src/controllers/http.controller.js +0 -44
  130. package/bootstrap/services/agent/src/controllers/message.controller.js +0 -51
  131. package/bootstrap/services/agent/src/db/migrations/01-init.js +0 -50
  132. package/bootstrap/services/agent/src/db/migrations/02-agent-service-init.js +0 -36
  133. package/bootstrap/services/agent/src/policies/agent.policy.js +0 -13
  134. package/bootstrap/services/agent/src/services/agent.service.js +0 -259
  135. package/bootstrap/services/agent/src/services/chatgpt.service.js +0 -46
  136. package/bootstrap/services/agent/src/services/manifest.service.js +0 -21
  137. package/bootstrap/services/portal/Dockerfile.remote +0 -40
  138. package/bootstrap/services/portal/public/favicon.ico +0 -0
  139. package/bootstrap/services/portal/public/gnar-white.png +0 -0
  140. package/bootstrap/services/portal/public/gnarengine-logo-black.png +0 -0
  141. package/bootstrap/services/portal/public/index.html +0 -43
  142. package/bootstrap/services/portal/public/logo192.png +0 -0
  143. package/bootstrap/services/portal/public/logo512.png +0 -0
  144. package/bootstrap/services/portal/public/manifest.json +0 -25
  145. package/bootstrap/services/portal/public/robots.txt +0 -3
  146. package/bootstrap/services/portal/src/App.js +0 -56
  147. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black.svg +0 -1
  148. package/bootstrap/services/portal/src/assets/Logo_Anchord_Black_Green.svg +0 -1
  149. package/bootstrap/services/portal/src/assets/Logo_Anchord_White_Green.svg +0 -1
  150. package/bootstrap/services/portal/src/assets/activity.svg +0 -3
  151. package/bootstrap/services/portal/src/assets/arrow.svg +0 -3
  152. package/bootstrap/services/portal/src/assets/bin-white.svg +0 -3
  153. package/bootstrap/services/portal/src/assets/bin.svg +0 -3
  154. package/bootstrap/services/portal/src/assets/check.svg +0 -3
  155. package/bootstrap/services/portal/src/assets/chevron.svg +0 -3
  156. package/bootstrap/services/portal/src/assets/contact.svg +0 -3
  157. package/bootstrap/services/portal/src/assets/dots-vertical.svg +0 -5
  158. package/bootstrap/services/portal/src/assets/eye-off.svg +0 -3
  159. package/bootstrap/services/portal/src/assets/eye.svg +0 -4
  160. package/bootstrap/services/portal/src/assets/gnar-engine-black.svg +0 -47
  161. package/bootstrap/services/portal/src/assets/gnar-engine-white.svg +0 -47
  162. package/bootstrap/services/portal/src/assets/gnar_engine.svg +0 -3
  163. package/bootstrap/services/portal/src/assets/gnarengine-logo-black.png +0 -0
  164. package/bootstrap/services/portal/src/assets/home.svg +0 -3
  165. package/bootstrap/services/portal/src/assets/link.svg +0 -3
  166. package/bootstrap/services/portal/src/assets/lock.svg +0 -3
  167. package/bootstrap/services/portal/src/assets/package.svg +0 -4
  168. package/bootstrap/services/portal/src/assets/raffle.svg +0 -3
  169. package/bootstrap/services/portal/src/assets/settings.svg +0 -4
  170. package/bootstrap/services/portal/src/assets/shopping-bag.svg +0 -3
  171. package/bootstrap/services/portal/src/assets/user-black.svg +0 -3
  172. package/bootstrap/services/portal/src/assets/user.svg +0 -3
  173. package/bootstrap/services/portal/src/assets/users.svg +0 -3
  174. package/bootstrap/services/portal/src/assets/wallet.svg +0 -3
  175. package/bootstrap/services/portal/src/data/data.js +0 -70
  176. package/bootstrap/services/portal/src/features/attributeFormRow/AttributeFormRow.jsx +0 -32
  177. package/bootstrap/services/portal/src/features/billingShipping/BillingShipping.jsx +0 -160
  178. package/bootstrap/services/portal/src/features/crud/crudEdit.less +0 -230
  179. package/bootstrap/services/portal/src/features/crud/crudList.less +0 -134
  180. package/bootstrap/services/portal/src/features/crud/crudPage.less +0 -31
  181. package/bootstrap/services/portal/src/features/crudContact/CrudContactList.jsx +0 -108
  182. package/bootstrap/services/portal/src/features/crudContact/CrudContactSingle.jsx +0 -243
  183. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderList.jsx +0 -109
  184. package/bootstrap/services/portal/src/features/crudOrder/CrudOrderSingle.jsx +0 -315
  185. package/bootstrap/services/portal/src/features/crudProducts/CrudProductList.jsx +0 -104
  186. package/bootstrap/services/portal/src/features/crudProducts/CrudProductSingle.jsx +0 -388
  187. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesList.jsx +0 -104
  188. package/bootstrap/services/portal/src/features/crudRaffles/CrudRafflesSingle.jsx +0 -208
  189. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionList.jsx +0 -110
  190. package/bootstrap/services/portal/src/features/crudSubscription/CrudSubscriptionSingle.jsx +0 -261
  191. package/bootstrap/services/portal/src/features/crudUser/CrudUserList.jsx +0 -107
  192. package/bootstrap/services/portal/src/features/crudUser/CrudUserSingle.jsx +0 -402
  193. package/bootstrap/services/portal/src/features/inventoryFormRow/InventoryFormRow.jsx +0 -30
  194. package/bootstrap/services/portal/src/features/lineItems/LineItems.jsx +0 -113
  195. package/bootstrap/services/portal/src/features/loginForm/LoginForm.jsx +0 -56
  196. package/bootstrap/services/portal/src/features/loginForm/loginForm.less +0 -56
  197. package/bootstrap/services/portal/src/features/notes/Notes.jsx +0 -18
  198. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetForm.jsx +0 -96
  199. package/bootstrap/services/portal/src/features/passwordReset/PasswordResetRequestForm.jsx +0 -74
  200. package/bootstrap/services/portal/src/features/priceFormRow/PriceFormRow.jsx +0 -102
  201. package/bootstrap/services/portal/src/features/priceFormRow/priceFormRow.less +0 -24
  202. package/bootstrap/services/portal/src/features/raffleEntriesList/RaffleEntriesList.jsx +0 -99
  203. package/bootstrap/services/portal/src/features/raffleProductFormRow/RaffleProductFormRow.jsx +0 -46
  204. package/bootstrap/services/portal/src/features/sidebar/Sidebar.jsx +0 -64
  205. package/bootstrap/services/portal/src/features/sidebar/sidebar.less +0 -49
  206. package/bootstrap/services/portal/src/features/skus/Skus.jsx +0 -109
  207. package/bootstrap/services/portal/src/features/subscriptionSchedule/SubscriptionSchedule.jsx +0 -44
  208. package/bootstrap/services/portal/src/features/taxonomyFormRow/TaxonomyFormRow.jsx +0 -32
  209. package/bootstrap/services/portal/src/features/user/User.jsx +0 -54
  210. package/bootstrap/services/portal/src/features/user/user.less +0 -57
  211. package/bootstrap/services/portal/src/includes/utilities.js +0 -259
  212. package/bootstrap/services/portal/src/index.js +0 -14
  213. package/bootstrap/services/portal/src/layouts/CrudLayout.jsx +0 -50
  214. package/bootstrap/services/portal/src/layouts/LoginLayout.jsx +0 -17
  215. package/bootstrap/services/portal/src/layouts/PortalLayout.jsx +0 -48
  216. package/bootstrap/services/portal/src/layouts/loginLayout.less +0 -33
  217. package/bootstrap/services/portal/src/layouts/portalLayout.less +0 -67
  218. package/bootstrap/services/portal/src/pages/contacts/Contacts.jsx +0 -199
  219. package/bootstrap/services/portal/src/pages/dashboard/Dashboard.jsx +0 -17
  220. package/bootstrap/services/portal/src/pages/integrations/Integrations.jsx +0 -10
  221. package/bootstrap/services/portal/src/pages/login/Login.jsx +0 -15
  222. package/bootstrap/services/portal/src/pages/login/login.less +0 -10
  223. package/bootstrap/services/portal/src/pages/orders/Orders.jsx +0 -199
  224. package/bootstrap/services/portal/src/pages/passwordReset/PasswordResetPage.jsx +0 -15
  225. package/bootstrap/services/portal/src/pages/passwordResetRequest/PasswordResetRequestPage.jsx +0 -15
  226. package/bootstrap/services/portal/src/pages/payments/Payments.jsx +0 -10
  227. package/bootstrap/services/portal/src/pages/portal/Portal.jsx +0 -43
  228. package/bootstrap/services/portal/src/pages/products/Products.jsx +0 -212
  229. package/bootstrap/services/portal/src/pages/raffleEntries/RaffleEntries.jsx +0 -124
  230. package/bootstrap/services/portal/src/pages/raffles/Raffles.jsx +0 -186
  231. package/bootstrap/services/portal/src/pages/reports/Reports.jsx +0 -10
  232. package/bootstrap/services/portal/src/pages/settings/Settings.jsx +0 -10
  233. package/bootstrap/services/portal/src/pages/subscriptions/Subscriptions.jsx +0 -199
  234. package/bootstrap/services/portal/src/pages/users/Users.jsx +0 -193
  235. package/bootstrap/services/portal/src/pages/users/users.less +0 -25
  236. package/bootstrap/services/portal/src/styles/inputs.less +0 -157
  237. package/bootstrap/services/portal/src/styles/main.less +0 -26
  238. package/bootstrap/services/portal/src/ui/collapsible/Collapsible.jsx +0 -97
  239. package/bootstrap/services/portal/src/ui/collapsible/collapsible.less +0 -23
  240. package/bootstrap/services/portal/src/ui/customCheckbox/CustomCheckbox.jsx +0 -17
  241. package/bootstrap/services/portal/src/ui/customCheckbox/customCheckbox.less +0 -42
  242. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelect.jsx +0 -63
  243. package/bootstrap/services/portal/src/ui/customMultiSelect/CustomMultiSelectPeriod.jsx +0 -63
  244. package/bootstrap/services/portal/src/ui/goBack/GoBack.jsx +0 -19
  245. package/bootstrap/services/portal/src/ui/loader/Loader.jsx +0 -12
  246. package/bootstrap/services/portal/src/ui/pagination/Pagination.jsx +0 -23
  247. package/bootstrap/services/portal/src/ui/repeater/Repeater.jsx +0 -29
  248. package/bootstrap/services/portal/src/ui/saveButton/SaveButton.jsx +0 -69
  249. package/bootstrap/services/user/src/db/seeders/development/02-portal-admin-user.js +0 -27
  250. package/bootstrap/services/user/src/tests/user.test.js +0 -126
  251. package/templates/service/app.js.hbs +0 -38
  252. package/templates/service/schema/{{serviceName}}.schema.js.hbs +0 -14
  253. /package/bootstrap/services/portal/src/{ui/saveButton/saveButton.less → components/CustomSelect/CustomSelect.less} +0 -0
  254. /package/templates/service/{controllers → src/controllers}/http.controller.js.hbs +0 -0
  255. /package/templates/service/{controllers → src/controllers}/message.controller.js.hbs +0 -0
  256. /package/templates/service/{db → src/mysql.db}/migrations/01-init.js.hbs +0 -0
  257. /package/templates/service/{db → src/mysql.db}/migrations/02-{{lowerCase serviceName}}-service-init.js.hbs +0 -0
  258. /package/templates/service/{policies → src/policies}/{{serviceName}}.policy.js.hbs +0 -0
  259. /package/templates/service/{services/{{serviceName}}.service.js.hbs → src/services/mysql.{{serviceName}}.service.js.hbs} +0 -0
@@ -0,0 +1,63 @@
1
+ import React from "react";
2
+ import CustomSelect from "../CustomSelect/CustomSelect";
3
+
4
+ /**
5
+ * SelectRepeater component allows users to add, update, and remove items from a list using a select dropdown.
6
+ *
7
+ * Use setItems to manage state setting here, or use setItem override to manage state setting higher up
8
+ */
9
+ const SelectRepeater = ({ items = [], setItems, setItem, renderRow, selectLabel, selectText, selectOptions, selectLabelKey }) => {
10
+
11
+ const addItem = (item) => {
12
+ if (setItem) {
13
+ setItem(item);
14
+ } else if (setItems) {
15
+ setItems([...items, item]);
16
+ }
17
+ }
18
+
19
+ const updateItem = (index, newItem) => {
20
+ if (setItem) {
21
+ setItem(newItem, index);
22
+ } else if (setItems) {
23
+ const newItems = [...items];
24
+ newItems[index] = newItem;
25
+ setItems(newItems);
26
+ }
27
+ }
28
+
29
+ const removeItem = (item, index) => {
30
+ if (setItem) {
31
+ const remove = true;
32
+ setItem(item, index, remove);
33
+ } else {
34
+ setItems(items.filter((_, i) => i !== index));
35
+ }
36
+ }
37
+
38
+ return (
39
+ <div className="repeater">
40
+ {items.length > 0 && items.map((item, index) =>
41
+ <div className="repeater-row" key={index}>
42
+ {renderRow(item, index, (newItem) => updateItem(index, newItem))}
43
+ <span onClick={() => removeItem(item, index)} className="icon-delete remove-repeater-row"></span>
44
+ </div>
45
+ )}
46
+ <div className="button-cont">
47
+ <CustomSelect
48
+ label={selectLabel}
49
+ placeholder={selectText}
50
+ name="repeater-select"
51
+ options={selectOptions}
52
+ labelKey={selectLabelKey}
53
+ setSelectedOption={(option) => {
54
+ addItem(option);
55
+ }}
56
+ selectedOption={null}
57
+ />
58
+ </div>
59
+ </div>
60
+ );
61
+ };
62
+
63
+ export default SelectRepeater;
@@ -0,0 +1,23 @@
1
+ .repeater-row {
2
+ display: flex;
3
+ flex-direction: row;
4
+ flex-wrap: nowrap;
5
+ position: relative;
6
+ align-items: center;
7
+ gap: 20px;
8
+ }
9
+
10
+ .remove-repeater-row {
11
+ display: inline-block;
12
+ width: 30px;
13
+ height: 30px;
14
+ background-repeat: no-repeat;
15
+ background-position: center center;
16
+ margin-bottom: 10px;
17
+ opacity: 0.6;
18
+
19
+ &:hover {
20
+ cursor: pointer;
21
+ opacity: 1;
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+
2
+ function TextInput({ label, value, onChange, placeholder }) {
3
+
4
+ return (
5
+ <div className="text-input">
6
+ {label && <label>{label}</label>}
7
+ <input
8
+ type="text"
9
+ value={value}
10
+ onChange={onChange}
11
+ placeholder={placeholder}
12
+ />
13
+ </div>
14
+ );
15
+ }
16
+
17
+ export default TextInput;
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+
3
+ function Card({ title, children }) {
4
+
5
+ return (
6
+ <div className="card">
7
+ <h2 className="card-title">{title}</h2>
8
+ <div className="card-content">
9
+ {children}
10
+ </div>
11
+ </div>
12
+ );
13
+ }
14
+
15
+ export default Card;
@@ -0,0 +1,29 @@
1
+ import { Outlet } from "react-router-dom";
2
+ import gnarEngineLogo from '../../assets/gnar-engine-white-logo.svg';
3
+ import Topbar from "../../components/Topbar/Topbar";
4
+ import Sidebar from "../../components/Sidebar/Sidebar";
5
+
6
+ function PortalLayout() {
7
+
8
+ return (
9
+ <>
10
+ <Topbar />
11
+ <div className="portal-main">
12
+ <div className="portal-main-inner flex-row">
13
+ <Sidebar />
14
+ <div className="portal-page">
15
+ <div className="portal-page-header">
16
+ <img src={gnarEngineLogo} className="logo" alt="Gnar Engine" />
17
+ </div>
18
+ <Outlet />
19
+ <div className="portal-page-footer">
20
+ <p>©2025 Gnar Engine. All rights reserved.</p>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </>
26
+ )
27
+ }
28
+
29
+ export default PortalLayout;
@@ -0,0 +1,49 @@
1
+ .portal-main {
2
+ background: @dark-2;
3
+ min-height: calc(100vh - 71px);
4
+ width: 100vw;
5
+ position: absolute;
6
+ top: 71px;
7
+ left: 0px;
8
+ border-top-left-radius: 15px;
9
+ border-top-right-radius: 15px;
10
+ overflow: hidden;
11
+
12
+ .portal-main-inner {
13
+ position: relative;
14
+ width: 100%;
15
+ min-height: calc(100vh - 71px);
16
+ gap: 0px;
17
+ }
18
+
19
+ .portal-page {
20
+ width: 100%;
21
+ padding: 45px 45px 70px 45px;
22
+ box-sizing: border-box;
23
+ position: relative;
24
+
25
+ .portal-page-header {
26
+ margin-bottom: 30px;
27
+
28
+ .logo {
29
+ max-width: 200px;
30
+ }
31
+ }
32
+
33
+ .portal-page-footer {
34
+ display: flex;
35
+ flex-direction: row-reverse;
36
+ width: 100%;
37
+ position: absolute;
38
+ box-sizing: border-box;
39
+ bottom: 30px;
40
+ left: 0px;
41
+ padding: 0px 45px;
42
+
43
+ p, span {
44
+ margin: 0px;
45
+ color: @mid-grey;
46
+ }
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,51 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './css/style.css'
4
+ import App from './App.jsx'
5
+ import { createBrowserRouter, RouterProvider } from 'react-router-dom'
6
+ import LoginPage from './pages/LoginPage/LoginPage.jsx'
7
+ import PortalLayout from './layouts/PortalLayout/PortalLayout.jsx'
8
+ import DashboardPage from './pages/DashboardPage/DashboardPage.jsx'
9
+ import UsersPage from './pages/UsersPage/UsersPage.jsx'
10
+ import UserSinglePage from './pages/UserSinglePage/UserSinglePage.jsx'
11
+ import PagesPage from './pages/PagesPage/PagesPage.jsx'
12
+ import PageSinglePage from './pages/PageSinglePage/PageSinglePage.jsx'
13
+ import BlocksPage from './pages/BlocksPage/BlocksPage.jsx'
14
+ import BlockSinglePage from './pages/BlockSinglePage/BlockSinglePage.jsx'
15
+
16
+ const router = createBrowserRouter([
17
+ {
18
+ path: "portal",
19
+ element: <App />,
20
+ children: [
21
+ {
22
+ path: "login",
23
+ element: <LoginPage />,
24
+ },
25
+ {
26
+ path: "",
27
+ element: <PortalLayout />,
28
+ children: [
29
+ { index: true, element: <DashboardPage /> },
30
+ { path: "dashboard", element: <DashboardPage /> },
31
+
32
+ // Users
33
+ { path: "users", element: <UsersPage /> },
34
+ { path: "users/:id", element: <UserSinglePage /> },
35
+
36
+ // CMS / Pages
37
+ { path: "pages", element: <PagesPage /> },
38
+ { path: "pages/:id", element: <PageSinglePage /> },
39
+ { path: "blocks", element: <BlocksPage /> },
40
+ { path: "blocks/:id", element: <BlockSinglePage /> }
41
+ ],
42
+ }
43
+ ]
44
+ }
45
+ ]);
46
+
47
+ createRoot(document.getElementById('root')).render(
48
+ <StrictMode>
49
+ <RouterProvider router={router} />
50
+ </StrictMode>
51
+ )
@@ -0,0 +1,277 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import Card from '../../layouts/Card/Card';
3
+ import Repeater from '../../elements/Repeater/Repeater';
4
+ import SaveButton from '../../elements/SaveButton/SaveButton';
5
+ import TextInput from '../../elements/TextInput/TextInput';
6
+ import CustomSelect from '../../elements/CustomSelect/CustomSelect';
7
+ import { blocks } from '../../services/block.js';
8
+ import { fieldTypes } from '../../data/pages.data.js';
9
+ import { useParams } from "react-router-dom";
10
+
11
+ function BlockSinglePage() {
12
+
13
+ const { id } = useParams();
14
+ const [blockId, setBlockId] = useState('');
15
+ const [blockFields, setBlockFields] = useState([]);
16
+ const [blockDetails, setBlockDetails] = useState({});
17
+ const [allBlocks, setAllBlocks] = useState([]);
18
+ const [errors, setErrors] = useState([]);
19
+ const [loading, setLoading] = useState(false);
20
+
21
+ useEffect(() => {
22
+ // fetch all blocks for repeater field type options
23
+ (async () => {
24
+ try {
25
+ const blocksData = await blocks.getMany();
26
+ const blockOptions = (blocksData.blocks || []).map(block => ({
27
+ name: block.name,
28
+ value: block.key
29
+ }));
30
+ setAllBlocks(blockOptions || []);
31
+ } catch (error) {
32
+ console.error('Error fetching blocks data:', error);
33
+ setErrors([error]);
34
+ }
35
+ })();
36
+ }, []);
37
+
38
+ // fetch data if editing existing
39
+ useEffect(() => {
40
+ if (id && id !== 'new') {
41
+ (async () => {
42
+ setLoading(true);
43
+ try {
44
+ let blockData = await blocks.getSingle(id);
45
+ blockData = blockData.block;
46
+ setBlockId(blockData.id);
47
+ setBlockDetails({
48
+ blockName: blockData.name,
49
+ blockKey: blockData.key
50
+ });
51
+ setBlockFields(blockData.fields || []);
52
+ } catch (error) {
53
+ console.error('Error fetching block data:', error);
54
+ setErrors([error]);
55
+ }
56
+ setLoading(false);
57
+ })();
58
+ }
59
+ }, [id]);
60
+
61
+ const validateField = (e) => {
62
+ const value = e.target.value;
63
+ setErrors([]);
64
+
65
+ }
66
+
67
+ const save = async () => {
68
+ setLoading(true);
69
+ console.log('Saving...', { blockId, blockFields, blockDetails });
70
+
71
+ // create new
72
+ if (!blockId) {
73
+ try {
74
+ await blocks.create({
75
+ name: blockDetails.blockName,
76
+ key: blockDetails.blockKey,
77
+ fields: blockFields
78
+ })
79
+ setErrors([]);
80
+ } catch (error) {
81
+ console.error('Error creating block:', error);
82
+ setErrors([error]);
83
+ }
84
+ }
85
+
86
+ // update existing
87
+ else {
88
+ confirm('Are you sure you would like to update this block? This could result in page content being removed when they are next updated.')
89
+
90
+ try {
91
+ await blocks.update(blockId, {
92
+ name: blockDetails.blockName,
93
+ key: blockDetails.blockKey,
94
+ fields: blockFields
95
+ });
96
+ setErrors([]);
97
+ } catch (error) {
98
+ console.error('Error creating block:', error);
99
+ setErrors([error]);
100
+ }
101
+ }
102
+
103
+ setLoading(false);
104
+ }
105
+
106
+ return (
107
+ <div className="single-crud-page">
108
+ <h1>Create / Update Block</h1>
109
+
110
+ <div className="flex-row top-bar">
111
+ <p className="single-crud-id">{blockId || 'Creating new block...'}</p>
112
+ <div className="button-group">
113
+ <button onClick={() => window.history.back()} className="secondary-btn">Back</button>
114
+ <button onClick={save}>Save</button>
115
+ </div>
116
+ </div>
117
+
118
+ <div className="card-columns">
119
+ <div className="col-66">
120
+ <Card
121
+ title="Block Fields"
122
+ >
123
+ <p className="instruction">Add fields to your block, save it and then use the block in your pages</p>
124
+
125
+ <Repeater
126
+ items={blockFields}
127
+ setItems={setBlockFields}
128
+ defaultItem= {{}}
129
+ buttonText= "Add Field"
130
+ renderRow= {(item, index, updateItem) => (
131
+
132
+ <div className="flex-row">
133
+ <TextInput
134
+ label="Field Name"
135
+ placeholder="Enter field name"
136
+ value={blockFields[index].name || ''}
137
+ onChange={(e) => {
138
+ validateField(e)
139
+ const blockField = { ...blockFields[index], name: e.target.value };
140
+ setBlockFields([
141
+ ...blockFields.slice(0, index),
142
+ blockField,
143
+ ...blockFields.slice(index + 1)
144
+ ]);
145
+ }}
146
+ errorMessage="Invalid field name"
147
+ isValid={true}
148
+ />
149
+
150
+ <TextInput
151
+ label="Field Key"
152
+ placeholder="Enter field key"
153
+ value={blockFields[index].key || ''}
154
+ onChange={(e) => {
155
+ validateField(e)
156
+ const blockField = { ...blockFields[index], key: e.target.value };
157
+ setBlockFields([
158
+ ...blockFields.slice(0, index),
159
+ blockField,
160
+ ...blockFields.slice(index + 1)
161
+ ]);
162
+ }}
163
+ errorMessage="Invalid field key"
164
+ isValid={true}
165
+ />
166
+
167
+ <CustomSelect
168
+ label="Field Type"
169
+ placeholder="Select Field Type"
170
+ name={`field-type-select-${index}`}
171
+ options={fieldTypes}
172
+ labelKey="name"
173
+ setSelectedOption={(option) => {
174
+ const blockField = { ...blockFields[index], type: option.value };
175
+ setBlockFields([
176
+ ...blockFields.slice(0, index),
177
+ blockField,
178
+ ...blockFields.slice(index + 1)
179
+ ]);
180
+ }}
181
+ selectedOption={fieldTypes.find(ft => ft.value === (blockFields[index].type || ''))}
182
+ />
183
+
184
+ {allBlocks && blockFields[index].type === 'repeater' &&
185
+ <CustomSelect
186
+ label="Repeater Type"
187
+ placeholder="Select Repeater Type"
188
+ name={`repeater-type-select-${index}`}
189
+ options={allBlocks}
190
+ labelKey="name"
191
+ setSelectedOption={(option) => {
192
+ const blockField = { ...blockFields[index], repeaterType: option.value };
193
+ console.log('blockField', blockField);
194
+
195
+ setBlockFields([
196
+ ...blockFields.slice(0, index),
197
+ blockField,
198
+ ...blockFields.slice(index + 1)
199
+ ]);
200
+ }}
201
+ selectedOption={allBlocks.find(ft => ft.value === (blockFields[index].repeaterType || ''))}
202
+ />
203
+
204
+ }
205
+ </div>
206
+ )}
207
+ />
208
+ </Card>
209
+ </div>
210
+
211
+ <div className="col-33">
212
+ <Card
213
+ title="Block details"
214
+ >
215
+ <div className="flex-col">
216
+ <TextInput
217
+ label="Block Name"
218
+ placeholder="Enter block Name"
219
+ value={blockDetails.blockName || ''}
220
+ onChange={(e) => {
221
+ validateField(e)
222
+ setBlockDetails({
223
+ ...blockDetails,
224
+ blockName: e.target.value
225
+ })
226
+ }}
227
+ errorMessage="Invalid block name"
228
+ isValid={true}
229
+ />
230
+
231
+ <TextInput
232
+ label="Block Key"
233
+ placeholder="Enter block key"
234
+ value={blockDetails.blockKey || ''}
235
+ onChange={(e) => {
236
+ validateField(e)
237
+ setBlockDetails({
238
+ ...blockDetails,
239
+ blockKey: e.target.value
240
+ })
241
+ }}
242
+ errorMessage="Invalid block key"
243
+ isValid={true}
244
+ />
245
+ </div>
246
+ </Card>
247
+ </div>
248
+ </div>
249
+
250
+ <div className="flex-row bottom-bar">
251
+ <div className="button-group">
252
+ <SaveButton
253
+ onClick={save}
254
+ itemName="Block"
255
+ loading={loading}
256
+ error={errors.length > 0}
257
+ isNew={!blockId}
258
+ />
259
+ </div>
260
+ </div>
261
+
262
+ <div>
263
+ {errors.length > 0 && (
264
+ <div className="error-messages">
265
+ <ul>
266
+ {errors.map((error, index) => (
267
+ <li key={index}>{error.message}</li>
268
+ ))}
269
+ </ul>
270
+ </div>
271
+ )}
272
+ </div>
273
+ </div>
274
+ )
275
+ }
276
+
277
+ export default BlockSinglePage;
@@ -0,0 +1,23 @@
1
+ import CrudList from '../../components/CrudList/CrudList';
2
+ import { blocks } from '../../services/block.js';
3
+
4
+ function BlocksPage() {
5
+
6
+ return (
7
+ <div>
8
+ <h1>Manage Blocks</h1>
9
+ <CrudList
10
+ entityKey="blocks"
11
+ fetchData={blocks.getMany}
12
+ entitySingleName="Block"
13
+ entityPluralName="Blocks"
14
+ columns={[
15
+ { key: 'id', label: 'ID' },
16
+ { key: 'name', label: 'Block name' }
17
+ ]}
18
+ />
19
+ </div>
20
+ )
21
+ }
22
+
23
+ export default BlocksPage;
@@ -0,0 +1,11 @@
1
+
2
+ function DashboardPage() {
3
+ return (
4
+ <div>
5
+ <h1>Dashboard</h1>
6
+ <p>Welcome to your Gnar Engine admin portal!</p>
7
+ </div>
8
+ );
9
+ }
10
+
11
+ export default DashboardPage;
@@ -0,0 +1,21 @@
1
+ import LoginForm from "../../components/LoginForm/LoginForm";
2
+ import gnarEngineLogo from '../../assets/gnar-engine-white-logo.svg';
3
+
4
+ function LoginPage() {
5
+
6
+ return (
7
+ <div className="login-page flex-row">
8
+ <div className="login-left-panel">
9
+ <img src={gnarEngineLogo} className="logo" alt="Gnar Engine" />
10
+ </div>
11
+ <div className="login-right-panel">
12
+ <div>
13
+ <h1>Admin Login</h1>
14
+ <LoginForm />
15
+ </div>
16
+ </div>
17
+ </div>
18
+ )
19
+ }
20
+
21
+ export default LoginPage;
@@ -0,0 +1,51 @@
1
+ .login-page {
2
+ width: 100%;
3
+ max-width: 100%;
4
+ height: 100vh;
5
+ gap: 0px;
6
+
7
+ .login-left-panel {
8
+ min-width: 50%;
9
+ background: url('../assets/login-green-rad-back-1.jpg');
10
+ background-size: cover;
11
+ background-position: center center;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ }
16
+
17
+ .login-right-panel {
18
+ min-width: 50%;
19
+ background: @dark-1;
20
+ padding: 135px;
21
+ box-sizing: border-box;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ }
26
+
27
+ .username,
28
+ .password {
29
+ padding-left: 50px;
30
+ position: relative;
31
+ background-repeat: no-repeat;
32
+ background-position: 12px center;
33
+ background-size: 18px 18px;
34
+ }
35
+
36
+ .username {
37
+ background-image: url('../assets/icon-user.svg');
38
+ }
39
+
40
+ .password {
41
+ background-image: url('../assets/icon-padlock.svg');
42
+ }
43
+
44
+ .login-form {
45
+ max-width: 450px;
46
+ }
47
+
48
+ .forgotten-password {
49
+ width: 100%;
50
+ }
51
+ }