@raketa-cloud/admin 0.0.39 → 0.0.41

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 (32) hide show
  1. package/package.json +1 -1
  2. package/src/cloud/Categories.js +97 -0
  3. package/src/cloud/documents/categoryDocuments.js +2 -14
  4. package/src/handlers/categories/index.js +122 -0
  5. package/src/helpers/Inflector.js +3 -0
  6. package/src/index.js +1 -1
  7. package/src/routeCategoriesModule.js +16 -0
  8. package/src/setupCloud.js +2 -0
  9. package/src/tests/handlers/categories/create.test.js +94 -0
  10. package/src/tests/handlers/categories/create_form.test.js +26 -0
  11. package/src/tests/handlers/categories/destroy.test.js +53 -0
  12. package/src/tests/handlers/categories/edit_form.test.js +59 -0
  13. package/src/tests/handlers/categories/index.test.js +46 -0
  14. package/src/tests/handlers/categories/update.test.js +88 -0
  15. package/src/tests/support/factory.js +8 -0
  16. package/src/views/articles/create_form.ejs +1 -1
  17. package/src/views/articles/deletes/create_form.ejs +1 -1
  18. package/src/views/articles/index.ejs +1 -1
  19. package/src/views/articles/translations/create_form.ejs +1 -1
  20. package/src/views/categories/_form.ejs +38 -0
  21. package/src/views/categories/create_form.ejs +9 -0
  22. package/src/views/categories/edit_form.ejs +9 -0
  23. package/src/views/categories/index.ejs +35 -0
  24. package/src/views/events/create_form.ejs +1 -1
  25. package/src/views/events/deletes/create_form.ejs +1 -1
  26. package/src/views/events/index.ejs +1 -1
  27. package/src/views/events/translations/create_form.ejs +1 -1
  28. package/src/views/layouts/application.ejs +1 -1
  29. package/src/views/pages/create_form.ejs +1 -1
  30. package/src/views/pages/deletes/create_form.ejs +1 -1
  31. package/src/views/pages/index.ejs +1 -1
  32. package/src/views/pages/translations/create_form.ejs +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raketa-cloud/admin",
3
- "version": "0.0.39",
3
+ "version": "0.0.41",
4
4
  "main": "src/index.js",
5
5
  "scripts": {
6
6
  "dev": "npx tailwindcss -i ./src/styles/application.css -o ../../apps/admin/static/application.css --watch",
@@ -0,0 +1,97 @@
1
+ import RaketaClient from "./RaketaClient";
2
+ import {
3
+ CategoriesDocument,
4
+ CategoryDocument,
5
+ CreateCategoryDocument,
6
+ UpdateCategoryDocument,
7
+ DeleteCategoryDocumet,
8
+ } from "./documents/categoryDocuments";
9
+
10
+ export default class Categories extends RaketaClient {
11
+ async list(query, locale, categoryType) {
12
+ const vars = this.buildListInput(query, locale);
13
+
14
+ const { categories: items } = await this.query(CategoriesDocument, {
15
+ input: {
16
+ ...vars.input,
17
+ filters: { ...vars.input.filters, type: categoryType },
18
+ },
19
+ });
20
+
21
+ return items;
22
+ }
23
+
24
+ async all(query, locale, categoryType) {
25
+ const vars = this.buildAllInput(query, locale);
26
+
27
+ const { categories: items } = await this.query(CategoriesDocument, {
28
+ input: {
29
+ ...vars.input,
30
+ filters: { ...vars.input.filters, type: categoryType },
31
+ },
32
+ });
33
+
34
+ return items;
35
+ }
36
+
37
+ async find(id, locale) {
38
+ const { category: item } = await this.query(CategoryDocument, {
39
+ input: {
40
+ id,
41
+ locale,
42
+ },
43
+ });
44
+
45
+ return item;
46
+ }
47
+
48
+ async create(params, locale, categoryType) {
49
+ const {
50
+ createCategory: { errors, tag },
51
+ } = await this.query(CreateCategoryDocument, {
52
+ input: {
53
+ locale,
54
+ category: {
55
+ type: categoryType,
56
+ slug: params.slug,
57
+ name: params.name,
58
+ settings: JSON.stringify(params.settings || {}),
59
+ },
60
+ },
61
+ });
62
+
63
+ return { tag, errors };
64
+ }
65
+
66
+ async update(id, params, locale, categoryType) {
67
+ const {
68
+ updateCategory: { errors, category },
69
+ } = await this.query(UpdateCategoryDocument, {
70
+ input: {
71
+ id,
72
+ locale,
73
+ category: {
74
+ type: categoryType,
75
+ slug: params.slug,
76
+ name: params.name,
77
+ settings: JSON.stringify(params.settings || {}),
78
+ },
79
+ },
80
+ });
81
+
82
+ return { category, errors };
83
+ }
84
+
85
+ async delete(id, locale) {
86
+ const {
87
+ deleteCategory: { errors, tag },
88
+ } = await this.query(DeleteCategoryDocumet, {
89
+ input: {
90
+ id,
91
+ locale,
92
+ },
93
+ });
94
+
95
+ return { tag, errors };
96
+ }
97
+ }
@@ -1,17 +1,5 @@
1
- import { gql } from "../../graphql";
2
- import { errorsFragment, paginationFragment } from "./common";
3
-
4
- function categoryFragment() {
5
- return gql`
6
- {
7
- id
8
- type
9
- slug
10
- name
11
- settings
12
- }
13
- `;
14
- }
1
+ import { gql } from "graphql-request";
2
+ import { errorsFragment, paginationFragment, categoryFragment } from "./common";
15
3
 
16
4
  const CategoriesDocument = gql`
17
5
  query ($input: CategoriesInput!) {
@@ -0,0 +1,122 @@
1
+ const index = async (ctx) => {
2
+ const items = await ctx.client.categories.list(
3
+ ctx.query,
4
+ ctx.locale,
5
+ ctx.resourceConfig.categoryType
6
+ );
7
+
8
+ return {
9
+ assigns: {
10
+ items,
11
+ },
12
+ };
13
+ };
14
+
15
+ const create_form = async (ctx) => {
16
+ return {
17
+ assigns: {
18
+ resource: {},
19
+ errors: {},
20
+ _translations: [],
21
+ },
22
+ };
23
+ };
24
+
25
+ const create = async (ctx) => {
26
+ const params = { ...ctx.resourceParams.resource };
27
+ const resource = { ...params };
28
+
29
+ const { errors } = await ctx.client.categories.create(
30
+ resource,
31
+ ctx.locale,
32
+ ctx.resourceConfig.categoryType
33
+ );
34
+
35
+ if (errors) {
36
+ return {
37
+ assigns: {
38
+ resource,
39
+
40
+ errors: { resource: ctx.client.categories.formatErrors(errors) },
41
+ _translations: [],
42
+ },
43
+ };
44
+ }
45
+
46
+ return {
47
+ redirect: `/${ctx.locale}/${ctx.resourceConfig.categoryTypePlural}`,
48
+ };
49
+ };
50
+
51
+ const edit_form = async (ctx) => {
52
+ const { id } = ctx.router.req.param();
53
+ const resource = await ctx.client.categories.find(id, ctx.locale);
54
+
55
+ if (!resource) {
56
+ throw new Error(`Missing category #${id}`);
57
+ }
58
+
59
+ return {
60
+ assigns: {
61
+ resource,
62
+ errors: {},
63
+ _translations: [],
64
+ },
65
+ };
66
+ };
67
+
68
+ const update = async (ctx) => {
69
+ const { id } = ctx.params;
70
+ const params = ctx.resourceParams.resource;
71
+
72
+ const { errors } = await ctx.client.categories.update(
73
+ id,
74
+ {
75
+ name: params.name,
76
+ slug: params.slug,
77
+ settings: params.settings || {},
78
+ },
79
+ ctx.locale,
80
+ ctx.resourceConfig.categoryType
81
+ );
82
+
83
+ if (errors) {
84
+ return {
85
+ assigns: {
86
+ resource: { ...params, id },
87
+ errors: { resource: ctx.client.categories.formatErrors(errors) },
88
+ _translations: [],
89
+ },
90
+ };
91
+ }
92
+
93
+ return {
94
+ redirect: `/${ctx.locale}/${ctx.resourceConfig.categoryTypePlural}`,
95
+ };
96
+ };
97
+
98
+ const destroy = async (ctx) => {
99
+ const { id } = ctx.params;
100
+
101
+ const { errors } = await ctx.client.categories.delete(id, ctx.locale);
102
+
103
+ if (errors) {
104
+ throw `Failed to delete category #${id}.`;
105
+ }
106
+
107
+ return {
108
+ headers: {
109
+ "HX-redirect": `/${ctx.locale}/${ctx.resourceConfig.categoryTypePlural}`,
110
+ },
111
+ json: {},
112
+ };
113
+ };
114
+
115
+ export default {
116
+ index,
117
+ create_form,
118
+ create,
119
+ edit_form,
120
+ update,
121
+ destroy,
122
+ };
@@ -1,6 +1,9 @@
1
1
  const Inflector = {
2
2
  capitalize: (str) => str[0].toUpperCase() + str.slice(1),
3
3
  downcase: (str) => str.toLowerCase(),
4
+ humanize: (str) => {
5
+ return str.replaceAll(/[_-]+/g, " ");
6
+ },
4
7
  };
5
8
 
6
9
  export default Inflector;
package/src/index.js CHANGED
@@ -10,6 +10,7 @@ export { default as routeEventsModule } from "./routeEventsModule";
10
10
  export { default as routeSettings } from "./routeSettings";
11
11
  export { default as setupCloud } from "./setupCloud";
12
12
  export { default as routeStandard } from "./routeStandard";
13
+ export { default as routeCategoriesModule } from "./routeCategoriesModule";
13
14
 
14
15
  export { default as RaketaClient } from "./cloud/RaketaClient";
15
16
  export { default as Redirects } from "./cloud/Redirects";
@@ -89,4 +90,3 @@ export { default as EventsHandler } from "./handlers/events/EventsHandler";
89
90
 
90
91
  export { default as Factory } from "./tests/support/factory";
91
92
  export { default as Logger } from "./Logger";
92
-
@@ -0,0 +1,16 @@
1
+ import routeCollection from "./routeCollection";
2
+ import CategoriesHandler from "./handlers/categories";
3
+
4
+ function routeCategoriesModule(adapter, categoryType, categoryTypePlural) {
5
+ const resourceConfig = {
6
+ categoryType,
7
+ categoryTypePlural,
8
+ };
9
+
10
+ routeCollection(adapter, categoryTypePlural, CategoriesHandler, {
11
+ ...resourceConfig,
12
+ viewsFallback: "categories",
13
+ });
14
+ }
15
+
16
+ export default routeCategoriesModule;
package/src/setupCloud.js CHANGED
@@ -10,6 +10,7 @@ import ArticleChanges from "./cloud/ArticleChanges";
10
10
  import Events from "./cloud/Events";
11
11
  import EventRevisions from "./cloud/EventRevisions";
12
12
  import EventChanges from "./cloud/EventChanges";
13
+ import Categories from "./cloud/Categories";
13
14
 
14
15
  function setupCloud(ctx, getConfig) {
15
16
  if (!ctx?.authentication?.token) return;
@@ -43,6 +44,7 @@ function setupCloud(ctx, getConfig) {
43
44
  getConfig,
44
45
  ctx.authentication.token
45
46
  );
47
+ ctx.client.categories = new Categories(getConfig, ctx.authentication.token);
46
48
  }
47
49
 
48
50
  export default setupCloud;
@@ -0,0 +1,94 @@
1
+ import CategoriesHandler from "../../../handlers/categories";
2
+ import Categories from "../../../cloud/Categories";
3
+
4
+ import Factory from "../../support/factory";
5
+
6
+ function buildCtx(category) {
7
+ return {
8
+ client: {
9
+ categories: new Categories(() => ({}), "token"),
10
+ },
11
+ resourceParams: {
12
+ resource: {
13
+ slug: category.slug,
14
+ name: category.name,
15
+ settings: { ...category.settings },
16
+ },
17
+ },
18
+ locale: "en",
19
+ resourceConfig: {
20
+ categoryType: "cat",
21
+ categoryTypePlural: "cats",
22
+ },
23
+ };
24
+ }
25
+
26
+ describe("Categories create", () => {
27
+ test("create successful", async () => {
28
+ const category = Factory.build("category", { settings: { foo: "bar" } });
29
+ const ctx = buildCtx(category);
30
+
31
+ const mockCreate = jest
32
+ .spyOn(ctx.client.categories, "create")
33
+ .mockImplementation(() => ({ category: category, errors: null }));
34
+
35
+ const result = await CategoriesHandler.create(ctx);
36
+
37
+ expect(mockCreate).toHaveBeenCalledWith(
38
+ {
39
+ slug: category.slug,
40
+ name: category.name,
41
+ settings: category.settings,
42
+ },
43
+ "en",
44
+ "cat"
45
+ );
46
+
47
+ expect(result).toEqual({
48
+ redirect: "/en/cats",
49
+ });
50
+ });
51
+
52
+ test("create failed", async () => {
53
+ const category = Factory.build("category", {
54
+ slug: "",
55
+ settings: { foo: "bar" },
56
+ });
57
+ const ctx = buildCtx(category);
58
+
59
+ const mockCreate = jest
60
+ .spyOn(ctx.client.categories, "create")
61
+ .mockImplementation(() => ({
62
+ categories: null,
63
+ errors: [{ key: "url", message: "already used" }],
64
+ }));
65
+
66
+ const result = await CategoriesHandler.create(ctx);
67
+
68
+ expect(mockCreate).toHaveBeenCalledWith(
69
+ {
70
+ slug: "",
71
+ name: category.name,
72
+ settings: category.settings,
73
+ },
74
+ "en",
75
+ "cat"
76
+ );
77
+
78
+ expect(result).toEqual({
79
+ assigns: {
80
+ resource: {
81
+ slug: "",
82
+ name: category.name,
83
+ settings: category.settings,
84
+ },
85
+ errors: {
86
+ resource: {
87
+ url: "already used",
88
+ },
89
+ },
90
+ _translations: [],
91
+ },
92
+ });
93
+ });
94
+ });
@@ -0,0 +1,26 @@
1
+ import CategoriesHandler from "../../../handlers/categories";
2
+ import Categories from "../../../cloud/Categories";
3
+
4
+ function buildCtx() {
5
+ return {
6
+ client: {
7
+ categories: new Categories(() => ({}), "token"),
8
+ },
9
+ };
10
+ }
11
+
12
+ describe("Categories new", () => {
13
+ test("create_form", async () => {
14
+ const ctx = buildCtx();
15
+
16
+ const result = await CategoriesHandler.create_form(ctx);
17
+
18
+ expect(result).toEqual({
19
+ assigns: {
20
+ resource: {},
21
+ errors: {},
22
+ _translations: [],
23
+ },
24
+ });
25
+ });
26
+ });
@@ -0,0 +1,53 @@
1
+ import RedirectsHandler from "../../../handlers/RedirectsHandler";
2
+ import Redirects from "../../../cloud/Redirects";
3
+
4
+ import Factory from "../../support/factory";
5
+
6
+ function buildCtx() {
7
+ return {
8
+ client: {
9
+ redirects: new Redirects(() => ({}), "token"),
10
+ },
11
+ params: { id: 1 },
12
+ locale: "en",
13
+ };
14
+ }
15
+
16
+ describe("Redirects", () => {
17
+ test("destroy successful", async () => {
18
+ const redirect = Factory.build("redirect");
19
+ const ctx = buildCtx();
20
+
21
+ const mockDelete = jest
22
+ .spyOn(ctx.client.redirects, "delete")
23
+ .mockImplementation(() => ({ redirect: redirect, errors: null }));
24
+
25
+ const result = await RedirectsHandler.destroy(ctx);
26
+
27
+ expect(mockDelete).toHaveBeenCalledWith(1);
28
+
29
+ expect(result).toEqual({
30
+ headers: {
31
+ "HX-Redirect": "/en/redirects",
32
+ },
33
+ json: {},
34
+ });
35
+ });
36
+
37
+ test("destroy failed", async () => {
38
+ const ctx = buildCtx();
39
+
40
+ const mockDelete = jest
41
+ .spyOn(ctx.client.redirects, "delete")
42
+ .mockImplementation(() => ({
43
+ redirect: null,
44
+ errors: [{ key: "generalError", message: "Not found" }],
45
+ }));
46
+
47
+ await expect(() => RedirectsHandler.destroy(ctx)).rejects.toMatch(
48
+ "Failed to delete redirect #1. WTF?"
49
+ );
50
+
51
+ expect(mockDelete).toHaveBeenCalledWith(1);
52
+ });
53
+ });
@@ -0,0 +1,59 @@
1
+ import CategoriesHandler from "../../../handlers/categories";
2
+ import Categories from "../../../cloud/Categories";
3
+
4
+ import Factory from "../../support/factory";
5
+
6
+ function buildCtx() {
7
+ return {
8
+ client: {
9
+ categories: new Categories(() => ({}), "token"),
10
+ },
11
+ router: {
12
+ req: {
13
+ param: () => ({ id: 1 }),
14
+ },
15
+ },
16
+ resourceConfig: {
17
+ categoryType: "cat",
18
+ categoryTypePlural: "cats",
19
+ },
20
+ locale: "en",
21
+ };
22
+ }
23
+
24
+ describe("Categories edit", () => {
25
+ test("edit_form", async () => {
26
+ const category = Factory.build("category");
27
+ const ctx = buildCtx();
28
+
29
+ const mockFind = jest
30
+ .spyOn(ctx.client.categories, "find")
31
+ .mockImplementation(() => category);
32
+
33
+ const result = await CategoriesHandler.edit_form(ctx);
34
+
35
+ expect(mockFind).toHaveBeenCalledWith(1, "en");
36
+
37
+ expect(result).toEqual({
38
+ assigns: {
39
+ resource: { ...category },
40
+ errors: {},
41
+ _translations: [],
42
+ },
43
+ });
44
+ });
45
+
46
+ test("edit_form when a category does not exist", async () => {
47
+ const ctx = buildCtx();
48
+
49
+ const mockFind = jest
50
+ .spyOn(ctx.client.categories, "find")
51
+ .mockImplementation(() => null);
52
+
53
+ expect(async () => {
54
+ await CategoriesHandler.edit_form(ctx);
55
+ }).rejects.toThrow("Missing category #1");
56
+
57
+ expect(mockFind).toHaveBeenCalledWith(1, "en");
58
+ });
59
+ });
@@ -0,0 +1,46 @@
1
+ import CategoriesHandler from "../../../handlers/categories";
2
+ import Categories from "../../../cloud/Categories";
3
+
4
+ import Factory from "../../support/factory";
5
+
6
+ describe("Categories list", () => {
7
+ function buildCtx() {
8
+ return {
9
+ client: {
10
+ categories: new Categories(() => ({}), "token"),
11
+ },
12
+ query: {},
13
+ req: {
14
+ path: "/cats",
15
+ },
16
+ resourceConfig: {
17
+ categoryType: "cat",
18
+ categoryTypePlural: "cats",
19
+ },
20
+ locale: "en",
21
+ };
22
+ }
23
+
24
+ test("index", async () => {
25
+ const category = Factory.build("category");
26
+ const pagination = Factory.build("pagination");
27
+ const ctx = buildCtx();
28
+
29
+ const mockedList = jest
30
+ .spyOn(ctx.client.categories, "list")
31
+ .mockImplementation(() => ({ list: [category], pagination }));
32
+
33
+ const result = await CategoriesHandler.index(ctx);
34
+
35
+ expect(mockedList).toHaveBeenCalledWith({}, "en", "cat");
36
+
37
+ expect(result).toEqual({
38
+ assigns: {
39
+ items: {
40
+ list: [category],
41
+ pagination: pagination,
42
+ },
43
+ },
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,88 @@
1
+ import CategoriesHandler from "../../../handlers/categories";
2
+ import Categories from "../../../cloud/Categories";
3
+
4
+ function buildCtx(id, updateParams) {
5
+ return {
6
+ client: {
7
+ categories: new Categories(() => ({}), "token"),
8
+ },
9
+ params: { id },
10
+ resourceParams: {
11
+ resource: {
12
+ slug: updateParams.slug,
13
+ name: updateParams.name,
14
+ settings: updateParams.settings,
15
+ },
16
+ },
17
+ locale: "en",
18
+ resourceConfig: {
19
+ categoryType: "cat",
20
+ categoryTypePlural: "cats",
21
+ },
22
+ };
23
+ }
24
+
25
+ describe("Categories update", () => {
26
+ test("update successful", async () => {
27
+ const updateParams = {
28
+ name: "Updated cateogry",
29
+ slug: "updated",
30
+ settings: {
31
+ foo: "bar",
32
+ },
33
+ };
34
+ const ctx = buildCtx(1, updateParams);
35
+
36
+ const mockUpdate = jest
37
+ .spyOn(ctx.client.categories, "update")
38
+ .mockImplementation(() => ({
39
+ category: { ...updateParams, id: 1 },
40
+ errors: null,
41
+ }));
42
+
43
+ const result = await CategoriesHandler.update(ctx);
44
+
45
+ expect(mockUpdate).toHaveBeenCalledWith(1, updateParams, "en", "cat");
46
+
47
+ expect(result).toEqual({
48
+ redirect: "/en/cats",
49
+ });
50
+ });
51
+
52
+ test("update failed", async () => {
53
+ const updateParams = {
54
+ name: "Updated cateogry",
55
+ slug: "updated",
56
+ settings: {
57
+ foo: "bar",
58
+ },
59
+ };
60
+ const ctx = buildCtx(1, updateParams);
61
+
62
+ const mockUpdate = jest
63
+ .spyOn(ctx.client.categories, "update")
64
+ .mockImplementation(() => ({
65
+ category: null,
66
+ errors: [{ key: "slug", message: "already used" }],
67
+ }));
68
+
69
+ const result = await CategoriesHandler.update(ctx);
70
+
71
+ expect(mockUpdate).toHaveBeenCalledWith(1, updateParams, "en", "cat");
72
+
73
+ expect(result).toEqual({
74
+ assigns: {
75
+ resource: {
76
+ id: 1,
77
+ ...updateParams,
78
+ },
79
+ errors: {
80
+ resource: {
81
+ slug: "already used",
82
+ },
83
+ },
84
+ _translations: [],
85
+ },
86
+ });
87
+ });
88
+ });
@@ -122,6 +122,13 @@ const Tag = {
122
122
  name: "Tag",
123
123
  };
124
124
 
125
+ const CATEGORY = {
126
+ id: 42,
127
+ type: "entertainment",
128
+ name: "Movie",
129
+ slug: "movie",
130
+ };
131
+
125
132
  const FIXTURES = {
126
133
  redirect: {
127
134
  id: "1",
@@ -144,6 +151,7 @@ const FIXTURES = {
144
151
  event_change: { ...EVENT_CHANGE },
145
152
  settings: { value: {} },
146
153
  tag: Tag,
154
+ category: { ...CATEGORY },
147
155
  pagination: {
148
156
  currentPage: 1,
149
157
  pagesCount: 1,
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Create <%= Inflector.downcase(_context.resourceConfig.articleType) %></h1>
3
+ <h1 class="heading-1">Create <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.articleType)) %></h1>
4
4
  </div>
5
5
 
6
6
  <div class="grid gap-4 md:grid-cols-3">
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Delete <%= Inflector.downcase(_context.resourceConfig.articleType) %></h1>
3
+ <h1 class="heading-1">Delete <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.articleType)) %></h1>
4
4
  </div>
5
5
 
6
6
  <div class="grid gap-4 md:grid-cols-3">
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1"><%= Inflector.capitalize(_context.resourceConfig.articleTypePlural) %></h1>
3
+ <h1 class="heading-1"><%= Inflector.humanize(Inflector.capitalize(_context.resourceConfig.articleTypePlural)) %></h1>
4
4
 
5
5
  <div class="actions-toolbar">
6
6
  <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/new` %>" class="btn-primary">
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Create <%= Inflector.downcase(_context.resourceConfig.articleType) %> translation</h1>
3
+ <h1 class="heading-1">Create <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.articleType)) %> translation</h1>
4
4
  </div>
5
5
 
6
6
  <div class="grid gap-4 md:grid-cols-3">
@@ -0,0 +1,38 @@
1
+ <% form_for({ resource }, errors, (f) => { %>
2
+ <form action="<%= resource?.id ? `/${_context.locale}/${_context.resourceConfig.categoryTypePlural}/${resource.id}` : `/${_context.locale}/${_context.resourceConfig.categoryTypePlural}` %>" method="post" class="form">
3
+ <%- f.field('resource.name', { required: true }) %>
4
+ <%- f.field('resource.slug', { label: "URL", required: true }) %>
5
+
6
+ <div class="form-actions">
7
+ <div class="btn-toolbar">
8
+ <%- f.submit('Save record', { class: 'btn-primary' }) %>
9
+
10
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.categoryTypePlural}` %>" class="btn-secondary">Back to list</a>
11
+ </div>
12
+
13
+ <% if (resource?.id) {%>
14
+ <button hx-delete="<%= `/${_context.locale}/${_context.resourceConfig.categoryTypePlural}/${resource.id}` %>" hx-confirm="Are you sure?" class="btn-danger has-icon">
15
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
16
+ <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
17
+ </svg>
18
+
19
+ Delete record
20
+ </button>
21
+ <% } %>
22
+ </div>
23
+ </form>
24
+ <% }) %>
25
+
26
+ <div>
27
+ <div class="help">
28
+ <div class="flex items-center gap-1 mb-2">
29
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
30
+ <path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
31
+ </svg>
32
+
33
+ <h3 class="heading-3">Contextual help</h3>
34
+ </div>
35
+
36
+ <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore nostrum fugit sint sunt distinctio ut cum, corrupti ad error, quo labore minima. Reprehenderit quasi maxime repudiandae voluptatum commodi saepe molestiae?</p>
37
+ </div>
38
+ </div>
@@ -0,0 +1,9 @@
1
+ <div class="page-wrapper">
2
+ <div class="page-title">
3
+ <h1 class="heading-1">Create <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.categoryType)) %></h1>
4
+ </div>
5
+
6
+ <div class="grid gap-4 md:grid-cols-3">
7
+ <%- render_partial('_form.ejs') %>
8
+ </div>
9
+ </div>
@@ -0,0 +1,9 @@
1
+ <div class="page-wrapper">
2
+ <div class="page-title">
3
+ <h1 class="heading-1">Edit <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.categoryType)) %></h1>
4
+ </div>
5
+
6
+ <div class="grid gap-4 md:grid-cols-3">
7
+ <%- render_partial('_form.ejs') %>
8
+ </div>
9
+ </div>
@@ -0,0 +1,35 @@
1
+ <div class="page-wrapper">
2
+ <div class="page-title">
3
+ <h1 class="heading-1"><%= Inflector.humanize(Inflector.capitalize(_context.resourceConfig.categoryTypePlural)) %></h1>
4
+
5
+ <div class="actions-toolbar">
6
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.categoryTypePlural}/new` %>" class="btn-primary">
7
+ Create new
8
+ </a>
9
+ </div>
10
+ </div>
11
+
12
+ <table class="data-grid">
13
+ <thead>
14
+ <tr>
15
+ <th><%- sort_button("Name", "name", _context) %></th>
16
+ <th><%- sort_button("URL", "slug", _context) %></th>
17
+ </tr>
18
+ </thead>
19
+
20
+ <tbody>
21
+ <% items.list.forEach((item, idx)=> { %>
22
+ <tr>
23
+ <td class="w-1/2">
24
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.categoryTypePlural}/${item.id}/edit` %>" class="btn-text mr-1">
25
+ <%= item.name %>
26
+ </a>
27
+ </td>
28
+ <td><%= item.slug %></td>
29
+ </tr>
30
+ <% }); %>
31
+ </tbody>
32
+ </table>
33
+
34
+ <%- pagination(items.pagination, _context) %>
35
+ </div>
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Create <%= Inflector.downcase(_context.resourceConfig.eventType) %></h1>
3
+ <h1 class="heading-1">Create <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.eventType)) %></h1>
4
4
  </div>
5
5
 
6
6
  <div class="grid gap-4 md:grid-cols-3">
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Delete <%= Inflector.downcase(_context.resourceConfig.eventType) %></h1>
3
+ <h1 class="heading-1">Delete <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.eventType)) %></h1>
4
4
  </div>
5
5
 
6
6
  <div class="grid gap-4 md:grid-cols-3">
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1"><%= Inflector.capitalize(_context.resourceConfig.eventTypePlural) %></h1>
3
+ <h1 class="heading-1"><%= Inflector.humanize(Inflector.capitalize(_context.resourceConfig.eventTypePlural)) %></h1>
4
4
 
5
5
  <div class="actions-toolbar">
6
6
  <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/new` %>" class="btn-primary">
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Create <%= Inflector.downcase(_context.resourceConfig.eventType) %> translation</h1>
3
+ <h1 class="heading-1">Create <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.eventType)) %> translation</h1>
4
4
  </div>
5
5
 
6
6
  <div class="grid gap-4 md:grid-cols-3">
@@ -47,7 +47,7 @@
47
47
 
48
48
  <div class="pl-12 py-4 bg-raketa-background/50 group-[.expanded]:block hidden">
49
49
  <% menu.submenus.forEach(submenu => { %>
50
- <a href="<%= `/${_context.locale}/${submenu.link}` %>" class="block py-2 body-2 text-raketa-foreground/50 hover:text-raketa-foreground transition-all ease-in-out duration-300"><%= submenu.label %></a>
50
+ <a href="<%= `/${_context.locale}${submenu.link}` %>" class="block py-2 body-2 text-raketa-foreground/50 hover:text-raketa-foreground transition-all ease-in-out duration-300"><%= submenu.label %></a>
51
51
  <% }) %>
52
52
  </div>
53
53
  </details>
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Create <%= Inflector.downcase(_context.resourceConfig.pageType) %></h1>
3
+ <h1 class="heading-1">Create <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.pageType)) %></h1>
4
4
  </div>
5
5
 
6
6
  <div class="grid gap-4 md:grid-cols-3">
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Delete <%= Inflector.downcase(_context.resourceConfig.pageType) %></h1>
3
+ <h1 class="heading-1">Delete <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.pageType)) %></h1>
4
4
  </div>
5
5
 
6
6
  <div class="grid gap-4 md:grid-cols-3">
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1"><%= Inflector.capitalize(_context.resourceConfig.pageTypePlural) %></h1>
3
+ <h1 class="heading-1"><%= Inflector.humanize(Inflector.capitalize(_context.resourceConfig.pageTypePlural)) %></h1>
4
4
 
5
5
  <div class="actions-toolbar">
6
6
  <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/new` %>" class="btn-primary">
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Create <%= Inflector.downcase(_context.resourceConfig.pageType) %> translation</h1>
3
+ <h1 class="heading-1">Create <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.pageType)) %> translation</h1>
4
4
  </div>
5
5
 
6
6
  <div class="grid gap-4 md:grid-cols-3">