@raketa-cloud/admin 1.8.22 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -965,6 +965,37 @@ select {
965
965
  color: rgb(185 28 28 / var(--tw-text-opacity));
966
966
  }
967
967
 
968
+ .btn-icon {
969
+ display: flex;
970
+ width: 1.5rem;
971
+ height: 1.5rem;
972
+ align-items: center;
973
+ justify-content: center;
974
+ border-radius: 9999px;
975
+ background-color: rgba(var(--primary), 0.1);
976
+ --tw-text-opacity: 1;
977
+ color: rgba(var(--primary), var(--tw-text-opacity));
978
+ transition-property: all;
979
+ transition-duration: 300ms;
980
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
981
+ }
982
+
983
+ .btn-icon:hover {
984
+ background-color: rgba(var(--primary), 0.2);
985
+ }
986
+
987
+ .btn-icon {
988
+ &:disabled {
989
+ cursor: not-allowed;
990
+ }
991
+ &:disabled {
992
+ opacity: 0.5;
993
+ }
994
+ &:disabled:hover {
995
+ background-color: rgba(var(--primary), 0.1);
996
+ }
997
+ }
998
+
968
999
  .btn-toolbar {
969
1000
  display: flex;
970
1001
  align-items: center;
@@ -1539,6 +1570,20 @@ select {
1539
1570
  gap: 1rem;
1540
1571
  }
1541
1572
 
1573
+ .toolbar-divider {
1574
+ margin-left: 0.25rem;
1575
+ margin-right: 0.25rem;
1576
+ height: 1.5rem;
1577
+ width: 1px;
1578
+ --tw-bg-opacity: 1;
1579
+ background-color: rgb(208 209 210 / var(--tw-bg-opacity));
1580
+ }
1581
+
1582
+ .toolbar-divider:where(.dark, .dark *) {
1583
+ --tw-bg-opacity: 1;
1584
+ background-color: rgb(64 64 64 / var(--tw-bg-opacity));
1585
+ }
1586
+
1542
1587
  /* Dashboard specific */
1543
1588
 
1544
1589
  .card-button {
@@ -2232,10 +2277,6 @@ select {
2232
2277
  background-color: rgba(var(--primary), var(--tw-bg-opacity));
2233
2278
  }
2234
2279
 
2235
- .bg-raketa-primary\/10 {
2236
- background-color: rgba(var(--primary), 0.1);
2237
- }
2238
-
2239
2280
  .bg-raketa-primary\/5 {
2240
2281
  background-color: rgba(var(--primary), 0.05);
2241
2282
  }
@@ -3366,10 +3407,6 @@ body {
3366
3407
  background-color: rgba(var(--primary), var(--tw-bg-opacity));
3367
3408
  }
3368
3409
 
3369
- .hover\:bg-raketa-primary\/20:hover {
3370
- background-color: rgba(var(--primary), 0.2);
3371
- }
3372
-
3373
3410
  .hover\:text-raketa-foreground:hover {
3374
3411
  --tw-text-opacity: 1;
3375
3412
  color: rgba(var(--foreground), var(--tw-text-opacity));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raketa-cloud/admin",
3
- "version": "1.8.22",
3
+ "version": "1.9.0",
4
4
  "main": "src/index.js",
5
5
  "files": [
6
6
  "src/",
@@ -107,6 +107,7 @@ const create = async (ctx) => {
107
107
  resource: {
108
108
  translations: blueprint.translations || {},
109
109
  languages: [],
110
+ workflow: ctx.resourceParams?.resource?.workflow,
110
111
  },
111
112
  blueprint,
112
113
  targetLocale: originalLocale,
@@ -167,6 +168,7 @@ const create = async (ctx) => {
167
168
  ...resource,
168
169
  translations: blueprint.translations || {},
169
170
  languages: formLanguages,
171
+ workflow: workflow,
170
172
  },
171
173
  blueprint,
172
174
  targetLocale: originalLocale,
@@ -211,6 +213,7 @@ const create = async (ctx) => {
211
213
  ...resource,
212
214
  translations: blueprint.translations || {},
213
215
  languages: formLanguages,
216
+ workflow: workflow,
214
217
  },
215
218
  blueprint,
216
219
  targetLocale: locale,
@@ -110,6 +110,14 @@ body {
110
110
  @apply text-red-600 hover:text-raketa-foreground dark:text-red-700;
111
111
  }
112
112
 
113
+ .btn-icon {
114
+ @apply flex items-center justify-center transition-all duration-300 ease-in-out rounded-full size-6 text-raketa-primary bg-raketa-primary/10 hover:bg-raketa-primary/20;
115
+
116
+ &:disabled {
117
+ @apply opacity-50 cursor-not-allowed hover:bg-raketa-primary/10;
118
+ }
119
+ }
120
+
113
121
  .btn-toolbar {
114
122
  @apply flex items-center gap-1;
115
123
 
@@ -318,6 +326,10 @@ body {
318
326
  @apply flex items-center gap-4;
319
327
  }
320
328
 
329
+ .toolbar-divider {
330
+ @apply w-px h-6 mx-1 bg-mono-50 dark:bg-neutral-700;
331
+ }
332
+
321
333
  /* Dashboard specific */
322
334
  .card-button {
323
335
  @apply flex items-center gap-2 mb-2 max-w-[400px] border p-4 rounded font-semibold text-raketa-primary hover:border-raketa-primary hover:bg-raketa-primary/5;
@@ -1,75 +1,68 @@
1
- <div>
2
- <%
3
- const hasActiveContentTask = utils.feature("contentTasks", _context) &&
4
- resource.activeContentTask &&
5
- !resource.activeContentTask.completedAt &&
6
- !resource.activeContentTask.failedAt;
7
- %>
8
- <div class="actions-toolbar">
9
- <form method="POST" action="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/${_context.resourceConfig.resourceType}_revisions` %>" class="inline-block">
10
- <% if (resource.revisionsCount >= _context?.maxOpenRevisions || hasActiveContentTask) { %>
11
- <button type="submit" class="btn-primary" disabled style="opacity: 50%;">
12
- Start revision
13
- </button>
14
- <% } else {%>
15
- <button type="submit" class="btn-primary">
16
- Start revision
17
- </button>
18
- <% } %>
19
- </form>
1
+ <%
2
+ const hasActiveContentTask = utils.feature("contentTasks", _context) &&
3
+ resource.activeContentTask &&
4
+ !resource.activeContentTask.completedAt &&
5
+ !resource.activeContentTask.failedAt;
20
6
 
21
- <% if (hasActiveContentTask) { %>
22
- <button type="button" class="btn-text has-icon" disabled style="opacity: 50%; cursor: not-allowed;">
23
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
24
- <path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
25
- </svg>
7
+ const resourceName = Inflector.humanize(Inflector.downcase(_context.resourceConfig.resourceType));
8
+ %>
9
+ <div class="actions-toolbar gap-2">
10
+ <%- render_partial('./_shared/_translations_strip.ejs', { render_partial }) %>
11
+
12
+ <span class="toolbar-divider"></span>
26
13
 
27
- Change URL
14
+ <form action="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/clones` %>" method="post" class="inline-flex">
15
+ <button type="submit" class="btn-icon" title="Clone <%= resourceName %>" aria-label="Clone <%= resourceName %>" <%- hasActiveContentTask ? 'disabled' : '' %>>
16
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-4">
17
+ <path d="M7 3.5A1.5 1.5 0 0 1 8.5 2h3.879a1.5 1.5 0 0 1 1.06.44l3.122 3.12A1.5 1.5 0 0 1 17 6.622V12.5a1.5 1.5 0 0 1-1.5 1.5h-1v-3.379a3 3 0 0 0-.879-2.121L10.5 5.379A3 3 0 0 0 8.379 4.5H7v-1Z" />
18
+ <path d="M4.5 6A1.5 1.5 0 0 0 3 7.5v9A1.5 1.5 0 0 0 4.5 18h7a1.5 1.5 0 0 0 1.5-1.5v-5.879a1.5 1.5 0 0 0-.44-1.06L9.44 6.439A1.5 1.5 0 0 0 8.378 6H4.5Z" />
19
+ </svg>
28
20
  </button>
29
- <% } else { %>
30
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/urls/new` %>" class="btn-text has-icon">
21
+ </form>
22
+
23
+ <details class="relative unstyled-details">
24
+ <summary class="btn-icon cursor-pointer" title="More actions" aria-label="More actions">
31
25
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
32
- <path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
26
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
33
27
  </svg>
28
+ </summary>
34
29
 
35
- Change URL
36
- </a>
37
- <% } %>
38
-
39
- <form action="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/clones` %>" method="post" class="inline-block">
40
- <button type="submit" class="btn-text has-icon" <%= hasActiveContentTask ? 'disabled style="opacity: 50%;"' : '' %>>
30
+ <div class="absolute right-0 z-20 flex flex-col gap-4 p-4 mt-2 border rounded shadow-md bg-raketa-background dark:border-neutral-500">
31
+ <% if (hasActiveContentTask) { %>
32
+ <button type="button" class="btn-text has-icon whitespace-nowrap" disabled style="opacity: 50%; cursor: not-allowed;">
41
33
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
42
- <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" />
34
+ <path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
43
35
  </svg>
44
36
 
45
- Clone <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.resourceType)) %>
37
+ Change URL
46
38
  </button>
47
- </form>
48
-
49
- <% if (hasActiveContentTask) { %>
50
- <button type="button" class="btn-danger has-icon" disabled style="opacity: 50%; cursor: not-allowed;">
51
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
52
- <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" />
53
- </svg>
39
+ <% } else { %>
40
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/urls/new` %>" class="btn-text has-icon whitespace-nowrap">
41
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
42
+ <path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
43
+ </svg>
54
44
 
55
- Delete <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.resourceType)) %>
56
- </button>
57
- <% } else { %>
58
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/deletes/new` %>" class="btn-danger has-icon">
59
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
60
- <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" />
61
- </svg>
45
+ Change URL
46
+ </a>
47
+ <% } %>
62
48
 
63
- Delete <%= Inflector.humanize(Inflector.downcase(_context.resourceConfig.resourceType)) %>
64
- </a>
65
- <% } %>
66
- </div>
49
+ <% if (hasActiveContentTask) { %>
50
+ <button type="button" class="btn-danger has-icon whitespace-nowrap" disabled style="opacity: 50%; cursor: not-allowed;">
51
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
52
+ <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" />
53
+ </svg>
67
54
 
68
- <% if (_context.query?.revisions_count_error) { %>
69
- <div class="text-red-600">Cannot open a new revision. Current limit is <%= _context.maxOpenRevisions %>. </div>
70
- <% } %>
55
+ Delete <%= resourceName %>
56
+ </button>
57
+ <% } else { %>
58
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/deletes/new` %>" class="btn-danger has-icon whitespace-nowrap">
59
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
60
+ <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" />
61
+ </svg>
71
62
 
72
- <% if (hasActiveContentTask) { %>
73
- <div class="text-orange-700 mt-2 dark:text-orange-300">Actions are disabled while a translation task is in progress.</div>
74
- <% } %>
75
- </div>
63
+ Delete <%= resourceName %>
64
+ </a>
65
+ <% } %>
66
+ </div>
67
+ </details>
68
+ </div>
@@ -1,4 +1,4 @@
1
- <div class="flex flex-wrap items-center gap-1 pb-8">
1
+ <div class="flex items-center gap-1">
2
2
  <%
3
3
  const availableLocales = Object.keys(_context.locales);
4
4
  const totalPossibleTranslations = availableLocales.length - 1;
@@ -49,7 +49,7 @@
49
49
  <% }); %>
50
50
 
51
51
  <% if (canBeTranslated) { %>
52
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/translations/new` %>" title="Translate" aria-label="Translate" class="flex items-center justify-center transition-all duration-300 ease-in-out rounded-full size-6 text-raketa-primary bg-raketa-primary/10 hover:bg-raketa-primary/20">
52
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/translations/new` %>" title="Translate" aria-label="Translate" class="btn-icon">
53
53
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" class="size-3.5">
54
54
  <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
55
55
  </svg>
@@ -1,11 +1,9 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
3
  <%- render_partial('./_shared/_title.ejs') %>
4
- <%- render_partial('./_shared/_toolbar.ejs') %>
4
+ <%- render_partial('./_shared/_toolbar.ejs', { render_partial }) %>
5
5
  </div>
6
6
 
7
- <%- render_partial('./_shared/_translations_strip.ejs') %>
8
-
9
7
  <%
10
8
  const openedRevisions = []
11
9
  const scheduledRevisions = []
@@ -43,6 +41,23 @@
43
41
  resource.revisionsCount >= _context?.maxOpenRevisions;
44
42
  const canStartRevision = !atRevisionLimit && !hasActiveContentTask;
45
43
  %>
44
+
45
+ <div class="pb-4">
46
+ <% if (_context.query?.revisions_count_error) { %>
47
+ <div class="text-red-600">Cannot open a new revision. Current limit is <%= _context.maxOpenRevisions %>. </div>
48
+ <% } %>
49
+
50
+ <% if (hasActiveContentTask) { %>
51
+ <div class="mb-2 text-orange-700 dark:text-orange-300">Actions are disabled while a translation task is in progress.</div>
52
+ <% } %>
53
+
54
+ <form method="POST" action="<%= `/${_context.locale}/${_context.resourceConfig.resourceTypePlural}/${resource.id}/${_context.resourceConfig.resourceType}_revisions` %>" class="inline-block">
55
+ <button type="submit" class="btn-primary" <%- canStartRevision ? '' : 'disabled style="opacity: 50%;"' %> <% if (atRevisionLimit && !hasActiveContentTask) { %> title="Open revision limit reached (<%= _context.maxOpenRevisions %>)" <% } %>>
56
+ Start revision
57
+ </button>
58
+ </form>
59
+ </div>
60
+
46
61
  <% if (openedRevisions.length) { %>
47
62
  <table class="data-grid alt">
48
63
  <thead>
@@ -36,15 +36,15 @@
36
36
  <select name="resource[workflow][type]" id="workflow">
37
37
  <option value="">Default (no workflow)</option>
38
38
 
39
- <option value="LOKALISE_TO_AI_RT_HUM" <%= resource.workflow === 'LOKALISE_TO_AI_RT_HUM' ? 'selected' : '' %>>
39
+ <option value="LOKALISE_TO_AI_RT_HUM" <%= resource.workflow?.type === 'LOKALISE_TO_AI_RT_HUM' ? 'selected' : '' %>>
40
40
  <%= content_tasks.workflowsMap("LOKALISE_TO_AI_RT_HUM".toLowerCase()) %>
41
41
  </option>
42
42
 
43
- <option value="LOKALISE_TT_HUM" <%= resource.workflow === 'LOKALISE_TT_HUM' ? 'selected' : '' %>>
43
+ <option value="LOKALISE_TT_HUM" <%= resource.workflow?.type === 'LOKALISE_TT_HUM' ? 'selected' : '' %>>
44
44
  <%= content_tasks.workflowsMap("LOKALISE_TT_HUM".toLowerCase()) %>
45
45
  </option>
46
46
 
47
- <option value="LOKALISE_TT_AI" <%= resource.workflow === 'LOKALISE_TT_AI' ? 'selected' : '' %>>
47
+ <option value="LOKALISE_TT_AI" <%= resource.workflow?.type === 'LOKALISE_TT_AI' ? 'selected' : '' %>>
48
48
  <%= content_tasks.workflowsMap("LOKALISE_TT_AI".toLowerCase()) %>
49
49
  </option>
50
50
  </select>
@@ -55,49 +55,85 @@
55
55
 
56
56
  <div class="mb-4" id="due_date_wrapper">
57
57
  <label for="due_date" class="font-semibold hover:cursor-pointer body-3">Due date (UTC)</label>
58
- <input type="datetime-local" name="resource[workflow][due_date]" id="due_date" />
58
+ <input type="datetime-local" name="resource[workflow][due_date]" id="due_date" value="<%= resource.workflow?.due_date || "" %>" />
59
+
59
60
  <p class="text-sm text-raketa-foreground/50 mt-1">
60
- Select a workflow for the translation process. Lokalise workflow initiates a translation task for external translation.
61
+ Select a due date for the translation. Weekends are not a valid choice.
61
62
  </p>
62
63
  </div>
63
64
 
64
65
  <script>
65
- (function () {
66
- const workflowSelect = document.getElementById("workflow");
67
- const dueDateWrapper = document.getElementById("due_date_wrapper");
68
- const dueDateInput = document.getElementById("due_date");
69
-
70
- function getTwoDaysFromNowString() {
71
- const date = new Date();
72
- date.setDate(date.getDate() + 2);
73
- const year = date.getFullYear();
74
- const month = String(date.getMonth() + 1).padStart(2, "0");
75
- const day = String(date.getDate()).padStart(2, "0");
76
- const hours = String(date.getHours()).padStart(2, "0");
77
- const minutes = String(date.getMinutes()).padStart(2, "0");
78
- return `${year}-${month}-${day}T${hours}:${minutes}`;
79
- }
80
-
81
- function toggleDueDate() {
82
- if (!workflowSelect || !dueDateWrapper || !dueDateInput) return;
83
- const minDate = getTwoDaysFromNowString();
84
- dueDateInput.min = minDate;
85
-
86
- const isDefault = !workflowSelect.value;
87
- dueDateWrapper.style.display = isDefault ? "none" : "block";
88
-
89
- if (isDefault) {
90
- dueDateInput.value = "";
91
- } else if (!dueDateInput.value) {
92
- dueDateInput.value = minDate;
66
+ window.addEventListener("load", (_event) => {
67
+ (function () {
68
+ const workflowSelect = document.getElementById("workflow");
69
+ const dueDateWrapper = document.getElementById("due_date_wrapper");
70
+ const dueDateInput = document.getElementById("due_date");
71
+
72
+ function validateInputValue(dInput) {
73
+ if (dInput.value === "") {
74
+ dInput.setCustomValidity('');
75
+ return;
76
+ }
77
+
78
+ const selectedDate = new Date(dInput.value);
79
+ const day = selectedDate.getDay();
80
+
81
+ if (day === 0 || day === 6) {
82
+ dInput.setCustomValidity('Weekends are not allowed. Please select a weekday.')
83
+ selectedDate.value = '';
84
+ } else {
85
+ dInput.setCustomValidity('');
86
+ }
93
87
  }
94
- }
95
88
 
96
- if (workflowSelect) {
97
- workflowSelect.addEventListener("change", toggleDueDate);
98
- toggleDueDate();
99
- }
100
- })();
89
+ dueDateInput.addEventListener('change', function(e) {
90
+ validateInputValue(dueDateInput);
91
+ });
92
+
93
+ function getTwoDaysFromNowString() {
94
+ const date = new Date();
95
+
96
+
97
+ date.setDate(date.getDate() + 2);
98
+
99
+ // Skip weekends.
100
+ while(date.getDay() === 0 || date.getDay() === 6) {
101
+ date.setDate(date.getDate() + 1);
102
+ }
103
+
104
+ const year = date.getFullYear();
105
+ const month = String(date.getMonth() + 1).padStart(2, "0");
106
+ const day = String(date.getDate()).padStart(2, "0");
107
+ const hours = String(date.getHours()).padStart(2, "0");
108
+ const minutes = String(date.getMinutes()).padStart(2, "0");
109
+ return `${year}-${month}-${day}T${hours}:${minutes}`;
110
+ }
111
+
112
+ function toggleDueDate() {
113
+ if (!workflowSelect || !dueDateWrapper || !dueDateInput) return;
114
+ const minDate = getTwoDaysFromNowString();
115
+ dueDateInput.min = minDate;
116
+
117
+ const isDefault = !workflowSelect.value;
118
+ dueDateWrapper.style.display = isDefault ? "none" : "block";
119
+
120
+ if (isDefault) {
121
+ dueDateInput.value = "";
122
+ } else if (!dueDateInput.value) {
123
+ dueDateInput.value = minDate;
124
+ }
125
+
126
+ // Trigger the on change so hints are visible together with the validation.
127
+ const changeEvent = new Event('change', { bubbles: true });
128
+ dueDateInput.dispatchEvent(changeEvent);
129
+ }
130
+
131
+ if (workflowSelect) {
132
+ workflowSelect.addEventListener("change", toggleDueDate);
133
+ toggleDueDate();
134
+ }
135
+ })();
136
+ })
101
137
  </script>
102
138
  <% } %>
103
139